Skip to content
This repository has been archived by the owner on May 3, 2022. It is now read-only.

shipperctl: introduce shipperctl chart render #374

Merged
merged 7 commits into from
Jan 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,13 @@ build-all: $(foreach os,$(OS),build/shipperctl.$(os)-amd64.tar.gz) build/sha256s
build:
mkdir -p build

build/shipper-state-metrics.%-amd64: cmd/shipper-state-metrics/*.go $(PKG)
build/shipper-state-metrics.%-amd64: $(PKG) cmd/shipper-state-metrics/*
GOOS=$* GOARCH=amd64 go build $(LDFLAGS) -o build/shipper-state-metrics.$*-amd64 cmd/shipper-state-metrics/*.go

build/shipper.%-amd64: cmd/shipper/*.go $(PKG)
build/shipper.%-amd64: $(PKG) cmd/shipper/*
GOOS=$* GOARCH=amd64 go build $(LDFLAGS) -o build/shipper.$*-amd64 cmd/shipper/*.go

build/shipperctl.%-amd64: cmd/shipperctl/*.go $(PKG)
build/shipperctl.%-amd64: $(PKG) cmd/shipperctl/**/*
nodo marked this conversation as resolved.
Show resolved Hide resolved
GOOS=$* GOARCH=amd64 go build $(LDFLAGS) -o build/shipperctl.$*-amd64 cmd/shipperctl/*.go

build/e2e.test: $(PKG) test/e2e/*
Expand Down
25 changes: 13 additions & 12 deletions cmd/shipperctl/cmd/backup/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"

"github.com/bookingcom/shipper/cmd/shipperctl/config"
shipper "github.com/bookingcom/shipper/pkg/apis/shipper/v1alpha1"
)

Expand All @@ -17,7 +18,7 @@ var (
managementClusterContext string
verboseFlag bool

BackupCmd = &cobra.Command{
Cmd = &cobra.Command{
Use: "backup",
Short: "Backup and restore Shipper applications and releases",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -48,23 +49,23 @@ func init() {
kubeConfigFlagName := "kubeconfig"
fileFlagName := "file"

BackupCmd.PersistentFlags().StringVar(&kubeConfigFile, kubeConfigFlagName, "~/.kube/config", "The path to the Kubernetes configuration file")
if err := BackupCmd.MarkPersistentFlagFilename(kubeConfigFlagName, "yaml"); err != nil {
BackupCmd.Printf("warning: could not mark %q for filename autocompletion: %s\n", kubeConfigFlagName, err)
config.RegisterFlag(Cmd.PersistentFlags(), &kubeConfigFile)
if err := Cmd.MarkPersistentFlagFilename(kubeConfigFlagName, "yaml"); err != nil {
Cmd.Printf("warning: could not mark %q for filename autocompletion: %s\n", kubeConfigFlagName, err)
}

BackupCmd.PersistentFlags().StringVar(&managementClusterContext, "management-cluster-context", "", "The name of the context to use to communicate with the management cluster. defaults to the current one")
BackupCmd.PersistentFlags().BoolVarP(&verboseFlag, "verbose", "v", false, "Prints the list of backup items")
BackupCmd.PersistentFlags().StringVar(&outputFormat, "format", "yaml", "Output format. One of: json|yaml")
Cmd.PersistentFlags().StringVar(&managementClusterContext, "management-cluster-context", "", "The name of the context to use to communicate with the management cluster. defaults to the current one")
Cmd.PersistentFlags().BoolVarP(&verboseFlag, "verbose", "v", false, "Prints the list of backup items")
Cmd.PersistentFlags().StringVar(&outputFormat, "format", "yaml", "Output format. One of: json|yaml")

BackupCmd.PersistentFlags().StringVarP(&backupFile, fileFlagName, "f", "backup.yaml", "The path to a backup file")
err := BackupCmd.MarkPersistentFlagFilename(fileFlagName, "yaml")
Cmd.PersistentFlags().StringVarP(&backupFile, fileFlagName, "f", "backup.yaml", "The path to a backup file")
err := Cmd.MarkPersistentFlagFilename(fileFlagName, "yaml")
if err != nil {
BackupCmd.Printf("warning: could not mark %q for filename yaml autocompletion: %s\n", fileFlagName, err)
Cmd.Printf("warning: could not mark %q for filename yaml autocompletion: %s\n", fileFlagName, err)
}
err = BackupCmd.MarkPersistentFlagFilename(fileFlagName, "json")
err = Cmd.MarkPersistentFlagFilename(fileFlagName, "json")
if err != nil {
BackupCmd.Printf("warning: could not mark %q for filename json autocompletion: %s\n", fileFlagName, err)
Cmd.Printf("warning: could not mark %q for filename json autocompletion: %s\n", fileFlagName, err)
}
}

Expand Down
11 changes: 3 additions & 8 deletions cmd/shipperctl/cmd/backup/prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"

"github.com/bookingcom/shipper/cmd/shipperctl/configurator"
"github.com/bookingcom/shipper/cmd/shipperctl/config"
"github.com/bookingcom/shipper/cmd/shipperctl/release"
"github.com/bookingcom/shipper/cmd/shipperctl/ui"
shipperclientset "github.com/bookingcom/shipper/pkg/client/clientset/versioned"
Expand All @@ -26,16 +26,11 @@ var (
)

func init() {
BackupCmd.AddCommand(prepareBackupCmd)
Cmd.AddCommand(prepareBackupCmd)
}

func runPrepareCommand(cmd *cobra.Command, args []string) error {
kubeClient, err := configurator.NewKubeClientFromKubeConfig(kubeConfigFile, managementClusterContext)
if err != nil {
return err
}

shipperClient, err := configurator.NewShipperClientFromKubeConfig(kubeConfigFile, managementClusterContext)
kubeClient, shipperClient, err := config.Load(kubeConfigFile, managementClusterContext)
if err != nil {
return err
}
Expand Down
11 changes: 3 additions & 8 deletions cmd/shipperctl/cmd/backup/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/yaml"

"github.com/bookingcom/shipper/cmd/shipperctl/configurator"
"github.com/bookingcom/shipper/cmd/shipperctl/config"
"github.com/bookingcom/shipper/cmd/shipperctl/ui"
shipper "github.com/bookingcom/shipper/pkg/apis/shipper/v1alpha1"
shipperclientset "github.com/bookingcom/shipper/pkg/client/clientset/versioned"
Expand All @@ -35,16 +35,11 @@ var (
)

func init() {
BackupCmd.AddCommand(restoreBackupCmd)
Cmd.AddCommand(restoreBackupCmd)
}

func runRestoreCommand(cmd *cobra.Command, args []string) error {
kubeClient, err := configurator.NewKubeClientFromKubeConfig(kubeConfigFile, managementClusterContext)
if err != nil {
return err
}

shipperClient, err := configurator.NewShipperClientFromKubeConfig(kubeConfigFile, managementClusterContext)
kubeClient, shipperClient, err := config.Load(kubeConfigFile, managementClusterContext)
if err != nil {
return err
}
Expand Down
88 changes: 88 additions & 0 deletions cmd/shipperctl/cmd/chart/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package chart

import (
"fmt"
"io/ioutil"
"os"

"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/bookingcom/shipper/cmd/shipperctl/config"
shipper "github.com/bookingcom/shipper/pkg/apis/shipper/v1alpha1"
)

var (
appName string
fileName string

renderAppCmd = &cobra.Command{
Use: "app",
Short: "render Shipper Charts for an Application",
RunE: renderChartFromApp,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if appName == "" && fileName == "" {
cmd.Printf("error: must have *one* of the flags: appName, filename\n")
os.Exit(1)
}
if appName != "" && fileName != "" {
cmd.Printf("error: must have *one* of the flags: appName, filename\n")
os.Exit(1)
}
},
}
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure, but I wonder if there is a better way to not have these variables global?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see it's consistent with the rest of the commands, but I was a bit confused by this line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial command was getting an appName, pulled it from the cluster and used the chart from it.
I wanted it to be possible to git a yaml of an application that does not exist in the cluster yet.
So with this command you can either give an app name or an application manifest (yaml file name)
The meaning of the variables is in their description in lines 40-41.
Other Go command line code is specifying the flags in a different location so there is no need for global variables. I opted for consistency, but I tend to agree that these global variables are not the best choice. I can start with these commands and have another PR to refactor the rest of shipperctl commands. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation, I think it makes sense to keep it like that for now. Would you consider this as a potential refactoring for the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course! I wanted to do it by now but didn't have the time.
Go's way of doing CLIs is not so gracious, but I have seen some examples of nice ones when I was looking into this (shipperctl decommission shenanigans)


func init() {
fileFlagName := "filename"

renderAppCmd.Flags().StringVar(&appName, "appName", "", "The name of an existing application to render chart for")
renderAppCmd.Flags().StringVar(&fileName, fileFlagName, "", "An application manifest in the current context to render chart for (e.g. `applicastion.yaml`)")
err := renderAppCmd.MarkFlagFilename(fileFlagName, "yaml")
if err != nil {
renderAppCmd.Printf("warning: could not mark %q for filename yaml autocompletion: %s\n", fileFlagName, err)
}

renderCmd.AddCommand(renderAppCmd)
}

func renderChartFromApp(cmd *cobra.Command, args []string) error {
c, err := newChartRenderConfig()
if err != nil {
return err
}
_, shipperClient, err := config.Load(kubeConfigFile, managementClusterContext)
if err != nil {
return err
}
var application shipper.Application
if appName != "" {
applicationPointer, err := shipperClient.ShipperV1alpha1().Applications(namespace).Get(appName, metav1.GetOptions{})
if err != nil {
return err
}
application = *applicationPointer
} else {
appYaml, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}

if err := yaml.Unmarshal(appYaml, &application); err != nil {
return err
}

}

c.ReleaseName = fmt.Sprintf("%s-%s-%d", application.Name, "foobar", 0)
c.ChartSpec = application.Spec.Template.Chart
c.ChartValues = application.Spec.Template.Values
rendered, err := render(c)
if err != nil {
return err
}

cmd.Println(rendered)
return nil
}
51 changes: 51 additions & 0 deletions cmd/shipperctl/cmd/chart/release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package chart

import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/bookingcom/shipper/cmd/shipperctl/config"
)

var (
releaseName string

renderRelCmd = &cobra.Command{
Use: "release bikerental-7abf46d4-0",
Short: "render Shipper Charts for a Release",
RunE: renderChartFromRel,
Args: cobra.ExactArgs(1),
}
)

func init() {
renderCmd.AddCommand(renderRelCmd)
}

func renderChartFromRel(cmd *cobra.Command, args []string) error {
releaseName = args[0]
c, err := newChartRenderConfig()
if err != nil {
return err
}
_, shipperClient, err := config.Load(kubeConfigFile, managementClusterContext)
if err != nil {
return err
}

rel, err := shipperClient.ShipperV1alpha1().Releases(namespace).Get(releaseName, metav1.GetOptions{})
if err != nil {
return err
}
c.ReleaseName = rel.Name
c.ChartSpec = rel.Spec.Environment.Chart
c.ChartValues = rel.Spec.Environment.Values

rendered, err := render(c)
if err != nil {
return err
}

cmd.Println(rendered)
return nil
}
99 changes: 99 additions & 0 deletions cmd/shipperctl/cmd/chart/render.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package chart

import (
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"

"github.com/bookingcom/shipper/cmd/shipperctl/config"
"github.com/bookingcom/shipper/cmd/shipperctl/configurator"
shipper "github.com/bookingcom/shipper/pkg/apis/shipper/v1alpha1"
shipperchart "github.com/bookingcom/shipper/pkg/chart"
"github.com/bookingcom/shipper/pkg/chart/repo"
)

var (
namespace string
kubeConfigFile string
managementClusterContext string

Command = &cobra.Command{
Use: "chart",
Short: "operate on Shipper Charts",
}

renderCmd = &cobra.Command{
Use: "render",
Short: "render Helm Charts for an Application or Release",
}
)

type ChartRenderConfig struct {
ChartSpec shipper.Chart
ChartValues *shipper.ChartValues
Namespace string
ReleaseName string
}

func init() {
const kubeConfigFlagName = "kubeconfig"
config.RegisterFlag(Command.PersistentFlags(), &kubeConfigFile)
if err := Command.MarkPersistentFlagFilename(kubeConfigFlagName, "yaml"); err != nil {
Command.Printf("warning: could not mark %q for filename autocompletion: %s\n", kubeConfigFlagName, err)
}
Command.PersistentFlags().StringVar(&managementClusterContext, "management-cluster-context", "", "The name of the context to use to communicate with the management cluster. defaults to the current one")
Command.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "The namespace where the app lives")

Command.AddCommand(renderCmd)
}

func render(c ChartRenderConfig) (string, error) {
chartFetcher := newFetchChartFunc()
chart, err := chartFetcher(&c.ChartSpec)
if err != nil {
return "", err
}

rendered, err := shipperchart.Render(
chart,
c.ReleaseName,
c.Namespace,
c.ChartValues,
)
if err != nil {
return "", err
}

return strings.Join(rendered, "%s\n---\n"), nil
}

func newChartRenderConfig() (ChartRenderConfig, error) {
c := ChartRenderConfig{
}
if namespace == "" {
clientConfig, _, err := configurator.ClientConfig(kubeConfigFile, managementClusterContext)
if err != nil {
return c, err
}
namespace, _, err = clientConfig.Namespace()
if err != nil {
return c, err
}
}
c.Namespace = namespace

return c, nil
}

func newFetchChartFunc() repo.ChartFetcher {
stopCh := make(<-chan struct{})

repoCatalog := repo.NewCatalog(
repo.DefaultFileCacheFactory(filepath.Join(os.TempDir(), "chart-cache")),
repo.DefaultRemoteFetcher,
stopCh)

return repo.FetchChartFunc(repoCatalog)
}
Loading