diff --git a/Makefile b/Makefile index fcd598a92..8b1e77d3d 100644 --- a/Makefile +++ b/Makefile @@ -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/**/* GOOS=$* GOARCH=amd64 go build $(LDFLAGS) -o build/shipperctl.$*-amd64 cmd/shipperctl/*.go build/e2e.test: $(PKG) test/e2e/* diff --git a/cmd/shipperctl/cmd/backup/backup.go b/cmd/shipperctl/cmd/backup/backup.go index 399a41f0a..5fe57057b 100644 --- a/cmd/shipperctl/cmd/backup/backup.go +++ b/cmd/shipperctl/cmd/backup/backup.go @@ -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" ) @@ -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) { @@ -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) } } diff --git a/cmd/shipperctl/cmd/backup/prepare.go b/cmd/shipperctl/cmd/backup/prepare.go index 0a8454da8..b8b087750 100644 --- a/cmd/shipperctl/cmd/backup/prepare.go +++ b/cmd/shipperctl/cmd/backup/prepare.go @@ -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" @@ -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 } diff --git a/cmd/shipperctl/cmd/backup/restore.go b/cmd/shipperctl/cmd/backup/restore.go index d117f1abc..7fc8de0aa 100644 --- a/cmd/shipperctl/cmd/backup/restore.go +++ b/cmd/shipperctl/cmd/backup/restore.go @@ -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" @@ -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 } diff --git a/cmd/shipperctl/cmd/chart/app.go b/cmd/shipperctl/cmd/chart/app.go new file mode 100644 index 000000000..686a60992 --- /dev/null +++ b/cmd/shipperctl/cmd/chart/app.go @@ -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) + } + }, + } +) + +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 +} diff --git a/cmd/shipperctl/cmd/chart/release.go b/cmd/shipperctl/cmd/chart/release.go new file mode 100644 index 000000000..f90d7d434 --- /dev/null +++ b/cmd/shipperctl/cmd/chart/release.go @@ -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 +} diff --git a/cmd/shipperctl/cmd/chart/render.go b/cmd/shipperctl/cmd/chart/render.go new file mode 100644 index 000000000..15059069f --- /dev/null +++ b/cmd/shipperctl/cmd/chart/render.go @@ -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) +} diff --git a/cmd/shipperctl/cmd/chart/render_test.go b/cmd/shipperctl/cmd/chart/render_test.go new file mode 100644 index 000000000..e728e8b31 --- /dev/null +++ b/cmd/shipperctl/cmd/chart/render_test.go @@ -0,0 +1,84 @@ +package chart + +import ( + "fmt" + "net/url" + "reflect" + "testing" + + shipper "github.com/bookingcom/shipper/pkg/apis/shipper/v1alpha1" + shippererrors "github.com/bookingcom/shipper/pkg/errors" + shippertesting "github.com/bookingcom/shipper/pkg/testing" +) + +func TestRender(t *testing.T) { + tests := []struct { + Name string + C ChartRenderConfig + ExpectedYaml string + ExpectedErr error + }{ + { + Name: "Working chart", + C: ChartRenderConfig{ + ChartSpec: shipper.Chart{ + Name: "nginx", + Version: "0.0.1", + RepoURL: "https://raw.githubusercontent.com/bookingcom/shipper/master/test/e2e/testdata", + }, + ChartValues: &shipper.ChartValues{ + "replicaCount": 1, + }, + Namespace: "", + ReleaseName: "super-server-foobar-0", + }, + ExpectedYaml: "apiVersion: v1\nkind: Service\nmetadata:\n name: nginx\n labels:\n app: super-server-foobar-0-nginx\n chart: \"nginx-0.0.1\"\n release: \"super-server-foobar-0\"\n heritage: \"Tiller\"\nspec:\n type: ClusterIP\n ports:\n - name: http\n port: 80\n targetPort: http\n selector:\n shipper-app: super-server-foobar-0-nginx%s\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: super-server-foobar-0-nginx\n labels:\n app: super-server-foobar-0-nginx\n chart: \"nginx-0.0.1\"\n release: \"super-server-foobar-0\"\n heritage: \"Tiller\"\nspec:\n selector:\n matchLabels:\n shipper-app: super-server-foobar-0-nginx\n shipper-release: \"super-server-foobar-0\"\n replicas: 1\n template:\n metadata:\n labels:\n app: super-server-foobar-0-nginx\n chart: \"nginx-0.0.1\"\n release: \"super-server-foobar-0\"\n heritage: \"Tiller\"\n spec:\n containers:\n - name: super-server-foobar-0-nginx\n image: \"docker.io/bitnami/nginx:1.14.2\"\n imagePullPolicy: \"IfNotPresent\"\n ports:\n - name: http\n containerPort: 8080\n livenessProbe:\n httpGet:\n path: /\n port: http\n initialDelaySeconds: 30\n timeoutSeconds: 5\n failureThreshold: 6\n readinessProbe:\n httpGet:\n path: /\n port: http\n initialDelaySeconds: 5\n timeoutSeconds: 3\n periodSeconds: 5\n volumeMounts:\n volumes:", + ExpectedErr: nil, + }, + { + Name: "Invalid chart template", + C: ChartRenderConfig{ + ChartSpec: shipper.Chart{ + Name: "invalid-chart-template", + Version: "0.0.1", + RepoURL: "https://raw.githubusercontent.com/bookingcom/shipper/master/test/e2e/testdata", + }, + ChartValues: &shipper.ChartValues{}, + Namespace: "", + ReleaseName: "", + }, + ExpectedYaml: "", + ExpectedErr: fmt.Errorf("could not render the chart: render error in \"invalid-chart-template/templates/deployment.yaml\": template: invalid-chart-template/templates/deployment.yaml:4:20: executing \"invalid-chart-template/templates/deployment.yaml\" at <{{template \"some-nonsense.fullname\" .}}>: template \"some-nonsense.fullname\" not defined"), + }, + { + Name: "Invalid chart url", + C: ChartRenderConfig{ + ChartSpec: shipper.Chart{ + Name: "0", + Version: "0.0.1", + RepoURL: "0", + }, + ChartValues: &shipper.ChartValues{}, + Namespace: "", + ReleaseName: "", + }, + ExpectedYaml: "", + ExpectedErr: shippererrors.NewChartRepoInternalError(&url.Error{ + Op: "parse", + URL: "0", + Err: fmt.Errorf("invalid URI for request"), + }), + }, + } + + for _, test := range tests { + actualYaml, actualErr := render(test.C) + if !reflect.DeepEqual(actualErr, test.ExpectedErr) { + t.Fatalf("expected error \n%v\ngot error \n%v", test.ExpectedErr, actualErr) + } + eq, diff := shippertesting.DeepEqualDiff(actualYaml, test.ExpectedYaml) + if !eq { + t.Fatalf("rendered chart differ from expected:\n%s", diff) + } + } +} diff --git a/cmd/shipperctl/cmd/decommission.go b/cmd/shipperctl/cmd/clean/decommission.go similarity index 81% rename from cmd/shipperctl/cmd/decommission.go rename to cmd/shipperctl/cmd/clean/decommission.go index dc88d58ab..f42520b2a 100644 --- a/cmd/shipperctl/cmd/decommission.go +++ b/cmd/shipperctl/cmd/clean/decommission.go @@ -1,4 +1,4 @@ -package cmd +package clean import ( "fmt" @@ -11,7 +11,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" shipper "github.com/bookingcom/shipper/pkg/apis/shipper/v1alpha1" @@ -20,13 +20,16 @@ import ( const ( decommissionedClustersFlagName = "decommissionedClusters" + kubeConfigFlagName = "kubeconfig" ) var ( - clusters []string - dryrun bool + clusters []string + dryrun bool + kubeConfigFile string + managementClusterContext string - CleanCmd = &cobra.Command{ + Cmd = &cobra.Command{ Use: "clean", Short: "clean Shipper objects", } @@ -49,28 +52,23 @@ type releaseAndFilteredAnnotations struct { func init() { // Flags common to all commands under `shipperctl clean` - CleanCmd.PersistentFlags().StringVar(&kubeConfigFile, kubeConfigFlagName, "~/.kube/config", "The path to the Kubernetes configuration file") - if err := CleanCmd.MarkPersistentFlagFilename(kubeConfigFlagName, "yaml"); err != nil { - CleanCmd.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) } - CleanCmd.PersistentFlags().BoolVar(&dryrun, "dryrun", false, "If true, only prints the objects that will be modified/deleted") - CleanCmd.PersistentFlags().StringVar(&managementClusterContext, "management-cluster-context", "", "The name of the context to use to communicate with the management cluster. defaults to the current one") - CleanCmd.PersistentFlags().StringSliceVar(&clusters, decommissionedClustersFlagName, clusters, "List of decommissioned clusters. (Required)") - if err := CleanCmd.MarkPersistentFlagRequired(decommissionedClustersFlagName); err != nil { - CleanCmd.Printf("warning: could not mark %q as required: %s\n", decommissionedClustersFlagName, err) + Cmd.PersistentFlags().BoolVar(&dryrun, "dryrun", false, "If true, only prints the objects that will be modified/deleted") + 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().StringSliceVar(&clusters, decommissionedClustersFlagName, clusters, "List of decommissioned clusters. (Required)") + if err := Cmd.MarkPersistentFlagRequired(decommissionedClustersFlagName); err != nil { + Cmd.Printf("warning: could not mark %q as required: %s\n", decommissionedClustersFlagName, err) } - CleanCmd.AddCommand(cleanDeadClustersCmd) + Cmd.AddCommand(cleanDeadClustersCmd) } func runCleanCommand(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 } diff --git a/cmd/shipperctl/cmd/clusters.go b/cmd/shipperctl/cmd/clusters/clusters.go similarity index 98% rename from cmd/shipperctl/cmd/clusters.go rename to cmd/shipperctl/cmd/clusters/clusters.go index 928b0ee8e..0e91489ce 100644 --- a/cmd/shipperctl/cmd/clusters.go +++ b/cmd/shipperctl/cmd/clusters/clusters.go @@ -1,4 +1,4 @@ -package cmd +package clusters import ( "crypto/rand" @@ -52,7 +52,7 @@ var ( RunE: runJoinClustersCommand, } - ClustersCmd = &cobra.Command{ + Cmd = &cobra.Command{ Use: "clusters", Short: "manage Shipper clusters", } @@ -74,7 +74,7 @@ const ( func init() { // Flags common to all commands under `shipperctl clusters` for _, cmd := range []*cobra.Command{joinCmd, setupMgmtCmd} { - cmd.Flags().StringVar(&kubeConfigFile, kubeConfigFlagName, "~/.kube/config", "the path to the Kubernetes configuration file") + config.RegisterFlag(cmd.Flags(), &kubeConfigFile) if err := cmd.MarkFlagFilename(kubeConfigFlagName, "yaml"); err != nil { cmd.Printf("warning: could not mark %q for filename autocompletion: %s\n", kubeConfigFlagName, err) } @@ -97,8 +97,8 @@ func init() { setupCmd.AddCommand(setupMgmtCmd) - ClustersCmd.AddCommand(setupCmd) - ClustersCmd.AddCommand(joinCmd) + Cmd.AddCommand(setupCmd) + Cmd.AddCommand(joinCmd) } func runSetupMgmtClusterCommand(cmd *cobra.Command, args []string) error { diff --git a/cmd/shipperctl/cmd/list.go b/cmd/shipperctl/cmd/list/list.go similarity index 73% rename from cmd/shipperctl/cmd/list.go rename to cmd/shipperctl/cmd/list/list.go index 45501de2d..9c15901c4 100644 --- a/cmd/shipperctl/cmd/list.go +++ b/cmd/shipperctl/cmd/list/list.go @@ -1,4 +1,4 @@ -package cmd +package list import ( "bytes" @@ -13,19 +13,23 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" - "github.com/bookingcom/shipper/cmd/shipperctl/configurator" + "github.com/bookingcom/shipper/cmd/shipperctl/config" "github.com/bookingcom/shipper/cmd/shipperctl/release" shipper "github.com/bookingcom/shipper/pkg/apis/shipper/v1alpha1" ) const ( - clustersFlagName = "clusters" + clustersFlagName = "clusters" + kubeConfigFlagName = "kubeconfig" ) var ( - printOption string + clusters []string + printOption string + kubeConfigFile string + managementClusterContext string - ListCmd = &cobra.Command{ + Cmd = &cobra.Command{ Use: "list", Short: "lists Shipper releases that are scheduled *only* on given clusters", PersistentPreRun: func(cmd *cobra.Command, args []string) { @@ -60,17 +64,17 @@ type outputRelease struct { func init() { // Flags common to all commands under `shipperctl count` - ListCmd.PersistentFlags().StringVar(&kubeConfigFile, kubeConfigFlagName, "~/.kube/config", "The path to the Kubernetes configuration file") - if err := ListCmd.MarkPersistentFlagFilename(kubeConfigFlagName, "yaml"); err != nil { - ListCmd.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) } - ListCmd.PersistentFlags().StringVar(&managementClusterContext, "management-cluster-context", "", "The name of the context to use to communicate with the management cluster. defaults to the current one") - ListCmd.PersistentFlags().StringSliceVar(&clusters, clustersFlagName, clusters, "List of comma separated clusters to list releases that are scheduled *only* on those clusters. If empty, will list without filtering") - ListCmd.PersistentFlags().StringVarP(&printOption, "output", "o", "", "Output format. One of: json|yaml. (Optional) defaults to verbose") + 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().StringSliceVar(&clusters, clustersFlagName, clusters, "List of comma separated clusters to list releases that are scheduled *only* on those clusters. If empty, will list without filtering") + Cmd.PersistentFlags().StringVarP(&printOption, "output", "o", "", "Output format. One of: json|yaml. (Optional) defaults to verbose") - ListCmd.AddCommand(countContendersCmd) - ListCmd.AddCommand(countReleasesCmd) + Cmd.AddCommand(countContendersCmd) + Cmd.AddCommand(countReleasesCmd) for _, command := range []*cobra.Command{countContendersCmd, countReleasesCmd} { command.SetOutput(os.Stdout) } @@ -78,12 +82,7 @@ func init() { func runCountContenderCommand(cmd *cobra.Command, args []string) error { counter := 0 - 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 } @@ -132,12 +131,7 @@ func runCountContenderCommand(cmd *cobra.Command, args []string) error { func runCountReleasesCommand(cmd *cobra.Command, args []string) error { counter := 0 - 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 } diff --git a/cmd/shipperctl/config/kubeconfig.go b/cmd/shipperctl/config/kubeconfig.go new file mode 100644 index 000000000..9038df216 --- /dev/null +++ b/cmd/shipperctl/config/kubeconfig.go @@ -0,0 +1,27 @@ +package config + +import ( + "github.com/spf13/pflag" + + "k8s.io/client-go/kubernetes" + + "github.com/bookingcom/shipper/cmd/shipperctl/configurator" + shipperclientset "github.com/bookingcom/shipper/pkg/client/clientset/versioned" +) + +func RegisterFlag(flags *pflag.FlagSet, kubeConfigFile *string) { + flags.StringVar(kubeConfigFile, "kubeconfig", "~/.kube/config", "the path to the Kubernetes configuration file") +} + +func Load(kubeConfigFile, managementClusterContext string) (kubernetes.Interface, shipperclientset.Interface, error) { + kubeClient, err := configurator.NewKubeClientFromKubeConfig(kubeConfigFile, managementClusterContext) + if err != nil { + return nil, nil, err + } + + shipperClient, err := configurator.NewShipperClientFromKubeConfig(kubeConfigFile, managementClusterContext) + if err != nil { + return nil, nil, err + } + return kubeClient, shipperClient, nil +} diff --git a/cmd/shipperctl/configurator/cluster.go b/cmd/shipperctl/configurator/cluster.go index c70be933b..d50ae7e08 100644 --- a/cmd/shipperctl/configurator/cluster.go +++ b/cmd/shipperctl/configurator/cluster.go @@ -508,9 +508,17 @@ func (c *Cluster) CreateOrUpdateValidatingWebhookService(namespace string) error } func loadKubeConfig(kubeConfig, context string) (*rest.Config, error) { + clientConfig, config, err := ClientConfig(kubeConfig, context) + if err != nil { + return config, err + } + return clientConfig.ClientConfig() +} + +func ClientConfig(kubeConfig string, context string) (clientcmd.ClientConfig, *rest.Config, error) { path, err := homedir.Expand(kubeConfig) if err != nil { - return nil, err + return nil, nil, err } overrides := &clientcmd.ConfigOverrides{CurrentContext: context} @@ -518,8 +526,9 @@ func loadKubeConfig(kubeConfig, context string) (*rest.Config, error) { overrides.CurrentContext = context } - return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( &clientcmd.ClientConfigLoadingRules{ExplicitPath: path}, overrides, - ).ClientConfig() + ) + return clientConfig, nil, nil } diff --git a/cmd/shipperctl/main.go b/cmd/shipperctl/main.go index 0e59ff78c..dbd5d0201 100644 --- a/cmd/shipperctl/main.go +++ b/cmd/shipperctl/main.go @@ -3,9 +3,12 @@ package main import ( "flag" "fmt" + "github.com/bookingcom/shipper/cmd/shipperctl/cmd/chart" + "github.com/bookingcom/shipper/cmd/shipperctl/cmd/clean" + "github.com/bookingcom/shipper/cmd/shipperctl/cmd/clusters" + "github.com/bookingcom/shipper/cmd/shipperctl/cmd/list" "os" - "github.com/bookingcom/shipper/cmd/shipperctl/cmd" "github.com/bookingcom/shipper/cmd/shipperctl/cmd/backup" "github.com/spf13/cobra" ) @@ -18,10 +21,11 @@ var rootCmd = &cobra.Command{ } func init() { - rootCmd.AddCommand(cmd.ClustersCmd) - rootCmd.AddCommand(cmd.ListCmd) - rootCmd.AddCommand(cmd.CleanCmd) - rootCmd.AddCommand(backup.BackupCmd) + rootCmd.AddCommand(clusters.Cmd) + rootCmd.AddCommand(list.Cmd) + rootCmd.AddCommand(clean.Cmd) + rootCmd.AddCommand(backup.Cmd) + rootCmd.AddCommand(chart.Command) } func main() { diff --git a/go.mod b/go.mod index b5349ace0..3bba64fb9 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/satori/go.uuid v1.2.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/cobra v1.0.0 + github.com/spf13/pflag v1.0.5 k8s.io/api v0.17.12 k8s.io/apiextensions-apiserver v0.17.12 k8s.io/apimachinery v0.17.12