From 5d3a24a0c3879924f847e073af77e81d303aeb8e Mon Sep 17 00:00:00 2001 From: Camila Macedo Date: Tue, 21 Jul 2020 09:44:30 +0100 Subject: [PATCH] remmoval: helm legacy scaffold --- changelog/fragments/cmd-legacy-helm.yaml | 33 + changelog/fragments/remove-legacy-helm.yaml | 18 + cmd/operator-sdk/add/api.go | 59 +- cmd/operator-sdk/new/cmd.go | 87 +- go.mod | 1 - internal/flags/apiflags/flags.go | 62 +- internal/flags/apiflags/flags_test.go | 120 +-- internal/scaffold/helm/api.go | 85 -- internal/scaffold/helm/chart.go | 274 ------ internal/scaffold/helm/chart_test.go | 253 ------ internal/scaffold/helm/dockerfile.go | 49 -- internal/scaffold/helm/init.go | 90 -- internal/scaffold/helm/operator.go | 69 -- internal/scaffold/helm/role.go | 226 ----- internal/scaffold/helm/role_test.go | 212 ----- .../testdata/testcharts/test-chart-1.2.0.tgz | Bin 2732 -> 0 bytes .../testdata/testcharts/test-chart-1.2.3.tgz | Bin 2731 -> 0 bytes .../testcharts/test-chart/.helmignore | 22 - .../testdata/testcharts/test-chart/Chart.yaml | 21 - .../testcharts/test-chart/templates/NOTES.txt | 21 - .../test-chart/templates/_helpers.tpl | 63 -- .../test-chart/templates/deployment.yaml | 55 -- .../test-chart/templates/ingress.yaml | 37 - .../test-chart/templates/service.yaml | 15 - .../test-chart/templates/serviceaccount.yaml | 8 - .../templates/tests/test-connection.yaml | 15 - .../testcharts/test-chart/values.yaml | 66 -- internal/scaffold/role_test.go | 786 ------------------ .../testroles/invalid_role/deploy/role.yaml | 0 .../valid_clusterrole/mergedRole.yaml | 82 -- .../testroles/valid_role1/mergedRole.yaml | 82 -- .../testroles/valid_role2/mergedRole.yaml | 112 --- .../testroles/valid_role3/mergedRole.yaml | 113 --- .../testroles/valid_role4/mergedRole.yaml | 113 --- .../testroles/valid_role5/mergedRole.yaml | 113 --- .../testroles/valid_role6/mergedRole.yaml | 113 --- internal/util/projutil/project_util.go | 19 +- pkg/helm/watches/watches.go | 87 -- pkg/helm/watches/watches_test.go | 278 +------ .../content/en/docs/cli/operator-sdk_new.md | 48 +- 40 files changed, 106 insertions(+), 3801 deletions(-) create mode 100644 changelog/fragments/cmd-legacy-helm.yaml create mode 100644 changelog/fragments/remove-legacy-helm.yaml delete mode 100644 internal/scaffold/helm/api.go delete mode 100644 internal/scaffold/helm/chart.go delete mode 100644 internal/scaffold/helm/chart_test.go delete mode 100644 internal/scaffold/helm/dockerfile.go delete mode 100644 internal/scaffold/helm/init.go delete mode 100644 internal/scaffold/helm/operator.go delete mode 100644 internal/scaffold/helm/role.go delete mode 100644 internal/scaffold/helm/role_test.go delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart-1.2.0.tgz delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart-1.2.3.tgz delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart/.helmignore delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart/Chart.yaml delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart/templates/NOTES.txt delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart/templates/_helpers.tpl delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart/templates/deployment.yaml delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart/templates/ingress.yaml delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart/templates/service.yaml delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart/templates/serviceaccount.yaml delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart/templates/tests/test-connection.yaml delete mode 100644 internal/scaffold/helm/testdata/testcharts/test-chart/values.yaml delete mode 100644 internal/scaffold/testdata/testroles/invalid_role/deploy/role.yaml delete mode 100644 internal/scaffold/testdata/testroles/valid_clusterrole/mergedRole.yaml delete mode 100644 internal/scaffold/testdata/testroles/valid_role1/mergedRole.yaml delete mode 100644 internal/scaffold/testdata/testroles/valid_role2/mergedRole.yaml delete mode 100644 internal/scaffold/testdata/testroles/valid_role3/mergedRole.yaml delete mode 100644 internal/scaffold/testdata/testroles/valid_role4/mergedRole.yaml delete mode 100644 internal/scaffold/testdata/testroles/valid_role5/mergedRole.yaml delete mode 100644 internal/scaffold/testdata/testroles/valid_role6/mergedRole.yaml diff --git a/changelog/fragments/cmd-legacy-helm.yaml b/changelog/fragments/cmd-legacy-helm.yaml new file mode 100644 index 0000000000..400c96a2da --- /dev/null +++ b/changelog/fragments/cmd-legacy-helm.yaml @@ -0,0 +1,33 @@ +# entries is a list of entries to include in +# release notes and/or the migration guide +entries: + - description: > + The commands `operator-sdk new --type=helm` and `operator-sdk add api` are no longer supported for Helm-based Operators. + + # kind is one of: + # - addition + # - change + # - deprecation + # - removal + # - bugfix + kind: "removal" + + # Is this a breaking change? + breaking: true + + # NOTE: ONLY USE `pull_request_override` WHEN ADDING THIS + # FILE FOR A PREVIOUSLY MERGED PULL_REQUEST! + # + # The generator auto-detects the PR number from the commit + # message in which this file was originally added. + # + # What is the pull request number (without the "#")? + # pull_request_override: 0 + + + # Migration can be defined to automatically add a section to + # the migration guide. This is required for breaking changes. + migration: + header: New and api subcommands are no longer supported for Helm-based Operators. + body: > + TBD diff --git a/changelog/fragments/remove-legacy-helm.yaml b/changelog/fragments/remove-legacy-helm.yaml new file mode 100644 index 0000000000..893feb33ca --- /dev/null +++ b/changelog/fragments/remove-legacy-helm.yaml @@ -0,0 +1,18 @@ +# entries is a list of entries to include in +# release notes and/or the migration guide +entries: + - description: > + The Helm legacy layout is no longer supported. + + kind: "removal" + + # Is this a breaking change? + breaking: true + + # Migration can be defined to automatically add a section to + # the migration guide. This is required for breaking changes. + migration: + header: Migrate your Helm project to the new layout + body: > + TBD + diff --git a/cmd/operator-sdk/add/api.go b/cmd/operator-sdk/add/api.go index a34ad37643..68ee4d48fd 100644 --- a/cmd/operator-sdk/add/api.go +++ b/cmd/operator-sdk/add/api.go @@ -26,7 +26,6 @@ import ( "github.com/operator-framework/operator-sdk/internal/genutil" "github.com/operator-framework/operator-sdk/internal/scaffold" "github.com/operator-framework/operator-sdk/internal/scaffold/ansible" - "github.com/operator-framework/operator-sdk/internal/scaffold/helm" "github.com/operator-framework/operator-sdk/internal/scaffold/input" "github.com/operator-framework/operator-sdk/internal/util/projutil" ) @@ -54,12 +53,6 @@ For Ansible-based operators: - Creates resource folder under /roles. - watches.yaml is updated with new resource. - deploy/role.yaml will be updated with apiGroup for new API. - -For Helm-based operators: - - Creates resource folder under /helm-charts. - - watches.yaml is updated with new resource. - - deploy/role.yaml will be updated to reflact new rules for the incoming API. - CRD's are generated, or updated if they exist for a particular group + version + kind, under deploy/crds/__crd.yaml; OpenAPI V3 validation YAML is generated as a 'validation' object.`, @@ -71,38 +64,6 @@ is generated as a 'validation' object.`, $ operator-sdk add api \ --api-version=app.example.com/v1alpha1 \ --kind=AppService - -# Helm Example: - $ operator-sdk add api \ - --api-version=app.example.com/v1alpha1 \ - --kind=AppService - - $ operator-sdk add api \ - --api-version=app.example.com/v1alpha1 \ - --kind=AppService - --helm-chart=myrepo/app - - $ operator-sdk add api \ - --helm-chart=myrepo/app - - $ operator-sdk add api \ - --helm-chart=myrepo/app \ - --helm-chart-version=1.2.3 - - $ operator-sdk add api \ - --helm-chart=app \ - --helm-chart-repo=https://charts.mycompany.com/ - - $ operator-sdk add api \ - --helm-chart=app \ - --helm-chart-repo=https://charts.mycompany.com/ \ - --helm-chart-version=1.2.3 - - $ operator-sdk add api \ - --helm-chart=/path/to/local/chart-directories/app/ - - $ operator-sdk add api \ - --helm-chart=/path/to/local/chart-archives/app-1.2.3.tgz `, RunE: apiRun, } @@ -131,28 +92,12 @@ func apiRun(cmd *cobra.Command, args []string) error { switch operatorType { case projutil.OperatorTypeGo: return fmt.Errorf("the `add api` command is not supported for Go operators") + case projutil.OperatorTypeHelm: + return fmt.Errorf("the `add api` command is not supported for Helm operators") case projutil.OperatorTypeAnsible: if err := doAnsibleAPIScaffold(); err != nil { return err } - case projutil.OperatorTypeHelm: - absProjectPath := projutil.MustGetwd() - projectName := filepath.Base(absProjectPath) - cfg := input.Config{ - AbsProjectPath: absProjectPath, - ProjectName: projectName, - } - createOpts := helm.CreateChartOptions{ - ResourceAPIVersion: apiFlags.APIVersion, - ResourceKind: apiFlags.Kind, - Chart: apiFlags.HelmChartRef, - Version: apiFlags.HelmChartVersion, - Repo: apiFlags.HelmChartRepo, - CRDVersion: apiFlags.CrdVersion, - } - if err := helm.API(cfg, createOpts); err != nil { - return err - } } log.Info("API generation complete.") return nil diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index d4397c0d71..96c7192818 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -28,7 +28,6 @@ import ( "github.com/operator-framework/operator-sdk/internal/genutil" "github.com/operator-framework/operator-sdk/internal/scaffold" "github.com/operator-framework/operator-sdk/internal/scaffold/ansible" - "github.com/operator-framework/operator-sdk/internal/scaffold/helm" "github.com/operator-framework/operator-sdk/internal/scaffold/input" "github.com/operator-framework/operator-sdk/internal/util/projutil" ) @@ -56,46 +55,14 @@ generates a default directory layout based on the input . $ operator-sdk new app-operator --type=ansible \ --api-version=app.example.com/v1alpha1 \ --kind=AppService - - # Helm project - $ operator-sdk new app-operator --type=helm \ - --api-version=app.example.com/v1alpha1 \ - --kind=AppService - - $ operator-sdk new app-operator --type=helm \ - --api-version=app.example.com/v1alpha1 \ - --kind=AppService \ - --helm-chart=myrepo/app - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=myrepo/app - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=myrepo/app \ - --helm-chart-version=1.2.3 - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=app \ - --helm-chart-repo=https://charts.mycompany.com/ - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=app \ - --helm-chart-repo=https://charts.mycompany.com/ \ - --helm-chart-version=1.2.3 - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=/path/to/local/chart-directories/app/ - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=/path/to/local/chart-archives/app-1.2.3.tgz `, RunE: newFunc, } - newCmd.Flags().StringVar(&operatorType, "type", "", - "Type of operator to initialize (choices: \"ansible\" or \"helm\")") - if err := newCmd.MarkFlagRequired("type"); err != nil { - log.Fatalf("Failed to mark `type` flag for `new` subcommand as required") + newCmd.Flags().StringVar(&operatorType, "type", "ansible", + "Type of operator to initialize (choices: \"ansible\")") + if err := newCmd.Flags().MarkHidden("type"); err != nil { + log.Fatalf("Failed to mark `type` flag for `new` subcommand as hidden") } // todo(camilamacedo86): remove before 1.0.0 @@ -129,7 +96,7 @@ func newFunc(cmd *cobra.Command, args []string) error { return err } mustBeNewProject() - if err := verifyFlags(); err != nil { + if err := apiFlags.VerifyCommonFlags(operatorType); err != nil { return err } @@ -140,35 +107,6 @@ func newFunc(cmd *cobra.Command, args []string) error { if err := doAnsibleScaffold(); err != nil { log.Fatal(err) } - case projutil.OperatorTypeHelm: - // create the project dir - err := os.MkdirAll(projectName, 0755) - if err != nil { - log.Fatal(err) - } - // go inside of the project dir - err = os.Chdir(filepath.Join(projutil.MustGetwd(), projectName)) - if err != nil { - log.Fatal(err) - } - - cfg := input.Config{ - AbsProjectPath: filepath.Join(projutil.MustGetwd()), - ProjectName: projectName, - } - - createOpts := helm.CreateChartOptions{ - ResourceAPIVersion: apiFlags.APIVersion, - ResourceKind: apiFlags.Kind, - Chart: apiFlags.HelmChartRef, - Version: apiFlags.HelmChartVersion, - Repo: apiFlags.HelmChartRepo, - CRDVersion: apiFlags.CrdVersion, - } - - if err := helm.Init(cfg, createOpts); err != nil { - log.Fatal(err) - } } //todo: remove before 1.0.0 if gitInit { @@ -302,21 +240,6 @@ func doAnsibleScaffold() error { return nil } -func verifyFlags() error { - if operatorType != projutil.OperatorTypeAnsible && operatorType != projutil.OperatorTypeHelm { - return fmt.Errorf("value of --type can only be `ansible`, or `helm`: %v", - projutil.ErrUnknownOperatorType{Type: operatorType}) - } - if operatorType != projutil.OperatorTypeAnsible && generatePlaybook { - return fmt.Errorf("value of --generate-playbook can only be used with --type `ansible`") - } - if err := apiFlags.VerifyCommonFlags(operatorType); err != nil { - return err - } - - return nil -} - // todo(camilamacedo86): remove before 1.0.0 // Deprecated: the git-init flag was deprecated since has no need to make the command run the git init. // users are allowed to easily do that when they wish. This func is just used here to run the git-init diff --git a/go.mod b/go.mod index 3e6b8d7ded..aa29cb4245 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ require ( github.com/operator-framework/api v0.3.8 github.com/operator-framework/operator-registry v1.12.6-0.20200611222234-275301b779f8 github.com/prometheus/client_golang v1.5.1 - github.com/prometheus/common v0.9.1 github.com/rogpeppe/go-internal v1.5.0 github.com/sergi/go-diff v1.0.0 github.com/sirupsen/logrus v1.5.0 diff --git a/internal/flags/apiflags/flags.go b/internal/flags/apiflags/flags.go index 7be4d5c2ca..6bc1ef9568 100644 --- a/internal/flags/apiflags/flags.go +++ b/internal/flags/apiflags/flags.go @@ -21,17 +21,13 @@ import ( "github.com/spf13/pflag" gencrd "github.com/operator-framework/operator-sdk/internal/generate/crd" - "github.com/operator-framework/operator-sdk/internal/util/projutil" ) type APIFlags struct { - SkipGeneration bool - APIVersion string - Kind string - CrdVersion string - HelmChartRef string - HelmChartVersion string - HelmChartRepo string + SkipGeneration bool + APIVersion string + Kind string + CrdVersion string } // AddTo - Add the reconcile period and watches file flags to the the flagset @@ -49,49 +45,23 @@ func (f *APIFlags) AddTo(flagSet *pflag.FlagSet) { flagSet.StringVar(&f.CrdVersion, "crd-version", gencrd.DefaultCRDVersion, "CRD version to generate") - flagSet.StringVar(&f.HelmChartRef, "helm-chart", "", - "Initialize helm operator with existing helm chart (, /, or local path). Valid only for --type helm") - - flagSet.StringVar(&f.HelmChartVersion, "helm-chart-version", "", - "Specific version of the helm chart (default is latest version). Valid only for --type helm") - - flagSet.StringVar(&f.HelmChartRepo, "helm-chart-repo", "", - "Chart repository URL for the requested helm chart, Valid only for --type helm") - } // VerifyCommonFlags func is used to verify flags common to both "new" and "add api" commands. func (f *APIFlags) VerifyCommonFlags(operatorType string) error { - - if len(f.HelmChartRef) != 0 { - if operatorType != projutil.OperatorTypeHelm { - return fmt.Errorf("value of --helm-chart can only be used with --type=helm") - } - } else if len(f.HelmChartRepo) != 0 { - return fmt.Errorf("value of --helm-chart-repo can only be used with --type=helm and --helm-chart") - } else if len(f.HelmChartVersion) != 0 { - return fmt.Errorf("value of --helm-chart-version can only be used with --type=helm and --helm-chart") + if len(f.APIVersion) == 0 { + return fmt.Errorf("value of --api-version must not have empty value") } - - // --api-version and --kind are required with --type=ansible, --type=helm , with one exception. - // If --type=helm and --helm-chart is set, --api-version and --kind are optional. If left unset, - // sane defaults are used when the specified helm chart is created. - if (operatorType == projutil.OperatorTypeAnsible || operatorType == projutil.OperatorTypeHelm) && - len(f.HelmChartRef) == 0 { - if len(f.APIVersion) == 0 { - return fmt.Errorf("value of --api-version must not have empty value") - } - if len(f.Kind) == 0 { - return fmt.Errorf("value of --kind must not have empty value") - } - kindFirstLetter := string(f.Kind[0]) - if kindFirstLetter != strings.ToUpper(kindFirstLetter) { - return fmt.Errorf("value of --kind must start with an uppercase letter") - } - if strings.Count(f.APIVersion, "/") != 1 { - return fmt.Errorf("value of --api-version has wrong format (%v);"+ - " format must be $GROUP_NAME/$VERSION (e.g app.example.com/v1alpha1)", f.APIVersion) - } + if len(f.Kind) == 0 { + return fmt.Errorf("value of --kind must not have empty value") + } + kindFirstLetter := string(f.Kind[0]) + if kindFirstLetter != strings.ToUpper(kindFirstLetter) { + return fmt.Errorf("value of --kind must start with an uppercase letter") + } + if strings.Count(f.APIVersion, "/") != 1 { + return fmt.Errorf("value of --api-version has wrong format (%v);"+ + " format must be $GROUP_NAME/$VERSION (e.g app.example.com/v1alpha1)", f.APIVersion) } return nil } diff --git a/internal/flags/apiflags/flags_test.go b/internal/flags/apiflags/flags_test.go index 9b5f771bb3..b24b9ccd31 100644 --- a/internal/flags/apiflags/flags_test.go +++ b/internal/flags/apiflags/flags_test.go @@ -32,13 +32,10 @@ func TestAddTo(t *testing.T) { // Populate FlagSet name: "Populate FlagSet", apiFlags: APIFlags{ - APIVersion: "app.example.com/v1alpha1", - Kind: "AppService", - SkipGeneration: false, - CrdVersion: "v1", - HelmChartRef: "stable/app", - HelmChartVersion: "1.0.0", - HelmChartRepo: "https://charts.mycompany.com/", + APIVersion: "app.example.com/v1alpha1", + Kind: "AppService", + SkipGeneration: false, + CrdVersion: "v1", }, validate: func(apiFlags APIFlags, flagSet *pflag.FlagSet) error { val, err := flagSet.GetString("api-version") @@ -73,22 +70,6 @@ func TestAddTo(t *testing.T) { return fmt.Errorf("crdVersion does not match") } - val, err = flagSet.GetString("helm-chart-repo") - if err != nil { - return err - } - if apiFlags.HelmChartRepo != val { - return fmt.Errorf("helmChartRepo does not match") - } - - val, err = flagSet.GetString("helm-chart-version") - if err != nil { - return err - } - if apiFlags.HelmChartVersion != val { - return fmt.Errorf("helmChartVersion does not match") - } - return nil }, }, @@ -125,36 +106,6 @@ func TestVerifyCommonFlags(t *testing.T) { operatorType: "go", expError: "", }, - { - // Invalid Go API Flags - name: "Invalid Go API Flags-HelmChartRef", - apiFlags: APIFlags{ - APIVersion: "app.example.com/v1alpha1", - HelmChartRef: "stable/repo", - }, - operatorType: "go", - expError: "value of --helm-chart can only be used with --type=helm", - }, - { - // Invalid Go API Flags - name: "Invalid Go API Flags-HelmChartRepo", - apiFlags: APIFlags{ - APIVersion: "app.example.com/v1alpha1", - HelmChartRepo: "https://charts.mycompany.com/", - }, - operatorType: "go", - expError: "value of --helm-chart-repo can only be used with --type=helm and --helm-chart", - }, - { - // Invalid Go API Flags - name: "Invalid Go API Flags-HelmChartVersion", - apiFlags: APIFlags{ - APIVersion: "app.example.com/v1alpha1", - HelmChartVersion: "1.2.0", - }, - operatorType: "go", - expError: "value of --helm-chart-version can only be used with --type=helm and --helm-chart", - }, { // Valid Ansible API Flags name: "Valid Ansible API Flags", @@ -194,69 +145,6 @@ func TestVerifyCommonFlags(t *testing.T) { operatorType: "ansible", expError: "value of --api-version must not have empty value", }, - { - // Invalid Ansible API Flags - name: "Invalid Ansible API Flags-HelmChartVersion is used", - apiFlags: APIFlags{ - APIVersion: "app.example.com/v1alpha1", - Kind: "App", - HelmChartVersion: "1.2.0", - }, - operatorType: "ansible", - expError: "value of --helm-chart-version can only be used with --type=helm and --helm-chart", - }, - { - // Invalid Ansible API Flags - name: "Invalid Ansible API Flags-HelmChartRepo is used", - apiFlags: APIFlags{ - APIVersion: "app.example.com/v1alpha1", - Kind: "App", - HelmChartRepo: "https://charts.mycompany.com/", - }, - operatorType: "ansible", - expError: "value of --helm-chart-repo can only be used with --type=helm and --helm-chart", - }, - { - // Valid HELM API Flags - name: "Valid HELM API Flags", - apiFlags: APIFlags{ - APIVersion: "app.example.com/v1alpha1", - Kind: "App", - SkipGeneration: true, - }, - operatorType: "helm", - expError: "", - }, - { - // Valid HELM API Flags - name: "Valid HELM API Flags-Helmchart used", - apiFlags: APIFlags{ - HelmChartRef: "stable/repo", - }, - operatorType: "helm", - expError: "", - }, - { - // Valid HELM API Flags - name: "Valid HELM API Flags-Helm specific flags", - apiFlags: APIFlags{ - HelmChartRef: "stable/repo", - HelmChartRepo: "https://charts.mycompany.com/", - HelmChartVersion: "1.2.0", - }, - operatorType: "helm", - expError: "", - }, - { - // Invalid HELM API Flags - name: "Invalid HELM API Flags-no HelmChartRef provided", - apiFlags: APIFlags{ - HelmChartRepo: "https://charts.mycompany.com/", - HelmChartVersion: "1.2.0", - }, - operatorType: "helm", - expError: "value of --helm-chart-repo can only be used with --type=helm and --helm-chart", - }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { diff --git a/internal/scaffold/helm/api.go b/internal/scaffold/helm/api.go deleted file mode 100644 index b9b06b0f71..0000000000 --- a/internal/scaffold/helm/api.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2020 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helm - -import ( - "fmt" - "path/filepath" - - "github.com/prometheus/common/log" - "k8s.io/client-go/discovery" - "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/yaml" - - "github.com/operator-framework/operator-sdk/internal/genutil" - "github.com/operator-framework/operator-sdk/internal/scaffold" - "github.com/operator-framework/operator-sdk/internal/scaffold/input" - "github.com/operator-framework/operator-sdk/pkg/helm/watches" -) - -// TODO -// Consolidate scaffold func to be used by both "new" and "add api" commands. -func API(cfg input.Config, createOpts CreateChartOptions) error { - - r, chart, err := CreateChart(cfg.AbsProjectPath, createOpts) - if err != nil { - return fmt.Errorf("failed to create helm chart: %v", err) - } - - valuesPath := filepath.Join("", HelmChartsDir, chart.Name(), "values.yaml") - - rawValues, err := yaml.Marshal(chart.Values) - if err != nil { - return fmt.Errorf("failed to get raw chart values: %v", err) - } - crSpec := fmt.Sprintf("# Default values copied from %s\n\n%s", valuesPath, rawValues) - - // update watch.yaml for the given resource. - watchesFile := filepath.Join(cfg.AbsProjectPath, watches.WatchesFile) - if err := watches.UpdateForResource(watchesFile, r, chart.Name()); err != nil { - return fmt.Errorf("failed to update watches.yaml: %w", err) - } - - s := &scaffold.Scaffold{} - err = s.Execute(&cfg, - &scaffold.CR{ - Resource: r, - Spec: crSpec, - }, - ) - if err != nil { - log.Fatalf("API scaffold failed: %v", err) - } - if err = genutil.GenerateCRDNonGo("", *r, createOpts.CRDVersion); err != nil { - return err - } - - roleScaffold := DefaultRoleScaffold - - if k8sCfg, err := config.GetConfig(); err != nil { - log.Warnf("Using default RBAC rules: failed to get Kubernetes config: %s", err) - } else if dc, err := discovery.NewDiscoveryClientForConfig(k8sCfg); err != nil { - log.Warnf("Using default RBAC rules: failed to create Kubernetes discovery client: %s", err) - } else { - roleScaffold = GenerateRoleScaffold(dc, chart) - } - - if err = scaffold.MergeRoleForResource(r, cfg.AbsProjectPath, roleScaffold); err != nil { - return fmt.Errorf("failed to merge rules in the RBAC manifest for resource (%v, %v): %v", - r.APIVersion, r.Kind, err) - } - - return nil -} diff --git a/internal/scaffold/helm/chart.go b/internal/scaffold/helm/chart.go deleted file mode 100644 index 23b4b892b1..0000000000 --- a/internal/scaffold/helm/chart.go +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helm - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/iancoleman/strcase" - log "github.com/sirupsen/logrus" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" - - "github.com/operator-framework/operator-sdk/internal/scaffold" -) - -const ( - - // HelmChartsDir is the relative directory within an SDK project where Helm - // charts are stored. - HelmChartsDir string = "helm-charts" - - // DefaultAPIVersion is the Kubernetes CRD API Version used for fetched - // charts when the --api-version flag is not specified - DefaultAPIVersion string = "helm.operator-sdk/v1alpha1" -) - -// CreateChartOptions is used to configure how a Helm chart is scaffolded -// for a new Helm operator project. -type CreateChartOptions struct { - // ResourceAPIVersion defines the Kubernetes GroupVersion to be associated - // with the created chart. - ResourceAPIVersion string - - // ResourceKind defines the Kubernetes Kind to be associated with the - // created chart. - ResourceKind string - - // Chart is a chart reference for a local or remote chart. - Chart string - - // Repo is a URL to a custom chart repository. - Repo string - - // Version is the version of the chart to fetch. - Version string - - // CRDVersion is the version of CRD.API which will be used to generate the CRDS - CRDVersion string -} - -// CreateChart scaffolds a new helm chart for the project rooted in projectDir -// based on the passed opts. -// -// It returns a scaffold.Resource that can be used by the caller to create -// other related files. opts.ResourceAPIVersion and opts.ResourceKind are -// used to create the resource and must be specified if opts.Chart is empty. -// -// If opts.Chart is not empty, opts.ResourceAPIVersion and opts.Kind can be -// left unset: opts.ResourceAPIVersion defaults to "charts.helm.k8s.io/v1alpha1" -// and opts.ResourceKind is deduced from the specified opts.Chart. -// -// CreateChart also returns a chart.Chart that references the newly created -// chart. -// -// If opts.Chart is empty, CreateChart scaffolds the default chart from helm's -// default template. -// -// If opts.Chart is a local file, CreateChart verifies that it is a valid helm -// chart archive and unpacks it into the project's helm charts directory. -// -// If opts.Chart is a local directory, CreateChart verifies that it is a valid -// helm chart directory and copies it into the project's helm charts directory. -// -// For any other value of opts.Chart, CreateChart attempts to fetch the helm chart -// from a remote repository. -// -// If opts.Repo is not specified, the following chart reference formats are supported: -// -// - /: Fetch the helm chart named chartName from the helm -// chart repository named repoName, as specified in the -// $HELM_HOME/repositories/repositories.yaml file. -// -// - : Fetch the helm chart archive at the specified URL. -// -// If opts.Repo is specified, only one chart reference format is supported: -// -// - : Fetch the helm chart named chartName in the helm chart repository -// specified by opts.Repo -// -// If opts.Version is not set, CreateChart will fetch the latest available version of -// the helm chart. Otherwise, CreateChart will fetch the specified version. -// opts.Version is not used when opts.Chart itself refers to a specific version, for -// example when it is a local path or a URL. -// -// CreateChart returns an error if an error occurs creating the scaffold.Resource or -// creating the chart. -func CreateChart(projectDir string, opts CreateChartOptions) (*scaffold.Resource, *chart.Chart, error) { - chartsDir := filepath.Join(projectDir, HelmChartsDir) - err := os.MkdirAll(chartsDir, 0755) - if err != nil { - return nil, nil, fmt.Errorf("failed to create helm-charts directory: %v", err) - } - - var ( - r *scaffold.Resource - c *chart.Chart - ) - - // If we don't have a helm chart reference, scaffold the default chart - // from Helm's default template. Otherwise, fetch it. - if len(opts.Chart) == 0 { - r, c, err = scaffoldChart(chartsDir, opts.ResourceAPIVersion, opts.ResourceKind) - if err != nil { - return nil, nil, fmt.Errorf("failed to scaffold default chart: %v", err) - } - } else { - r, c, err = fetchChart(chartsDir, opts) - if err != nil { - return nil, nil, fmt.Errorf("failed to fetch chart: %v", err) - } - } - - relChartPath := filepath.Join(HelmChartsDir, c.Name()) - absChartPath := filepath.Join(projectDir, relChartPath) - if err := fetchChartDependencies(absChartPath); err != nil { - return nil, nil, fmt.Errorf("failed to fetch chart dependencies: %v", err) - } - - // Reload chart in case dependencies changed - c, err = loader.Load(absChartPath) - if err != nil { - return nil, nil, fmt.Errorf("failed to load chart: %v", err) - } - - log.Infof("Created %s", relChartPath) - return r, c, nil -} - -func scaffoldChart(destDir, apiVersion, kind string) (*scaffold.Resource, *chart.Chart, error) { - r, err := scaffold.NewResource(apiVersion, kind) - if err != nil { - return nil, nil, err - } - - chartPath, err := chartutil.Create(r.LowerKind, destDir) - if err != nil { - return nil, nil, err - } - - chart, err := loader.Load(chartPath) - if err != nil { - return nil, nil, err - } - return r, chart, nil -} - -func fetchChart(destDir string, opts CreateChartOptions) (*scaffold.Resource, *chart.Chart, error) { - var ( - chart *chart.Chart - err error - ) - - if _, err = os.Stat(opts.Chart); err == nil { - chart, err = createChartFromDisk(destDir, opts.Chart) - } else { - chart, err = createChartFromRemote(destDir, opts) - } - if err != nil { - return nil, nil, err - } - - chartName := chart.Name() - if len(opts.ResourceAPIVersion) == 0 { - opts.ResourceAPIVersion = DefaultAPIVersion - } - if len(opts.ResourceKind) == 0 { - opts.ResourceKind = strcase.ToCamel(chartName) - } - - r, err := scaffold.NewResource(opts.ResourceAPIVersion, opts.ResourceKind) - if err != nil { - return nil, nil, err - } - return r, chart, nil -} - -func createChartFromDisk(destDir, source string) (*chart.Chart, error) { - chart, err := loader.Load(source) - if err != nil { - return nil, err - } - - // Save it into our project's helm-charts directory. - if err := chartutil.SaveDir(chart, destDir); err != nil { - return nil, err - } - return chart, nil -} - -func createChartFromRemote(destDir string, opts CreateChartOptions) (*chart.Chart, error) { - settings := cli.New() - getters := getter.All(settings) - c := downloader.ChartDownloader{ - Out: os.Stderr, - Getters: getters, - RepositoryConfig: settings.RepositoryConfig, - RepositoryCache: settings.RepositoryCache, - } - - if opts.Repo != "" { - chartURL, err := repo.FindChartInRepoURL(opts.Repo, opts.Chart, opts.Version, "", "", "", getters) - if err != nil { - return nil, err - } - opts.Chart = chartURL - } - - tmpDir, err := ioutil.TempDir("", "osdk-helm-chart") - if err != nil { - return nil, err - } - defer func() { - if err := os.RemoveAll(tmpDir); err != nil { - log.Errorf("Failed to remove temporary directory %s: %s", tmpDir, err) - } - }() - - chartArchive, _, err := c.DownloadTo(opts.Chart, opts.Version, tmpDir) - if err != nil { - return nil, err - } - - return createChartFromDisk(destDir, chartArchive) -} - -func fetchChartDependencies(chartPath string) error { - settings := cli.New() - getters := getter.All(settings) - - out := &bytes.Buffer{} - man := &downloader.Manager{ - Out: out, - ChartPath: chartPath, - Getters: getters, - RepositoryConfig: settings.RepositoryConfig, - RepositoryCache: settings.RepositoryCache, - } - if err := man.Build(); err != nil { - fmt.Println(out.String()) - return err - } - return nil -} diff --git a/internal/scaffold/helm/chart_test.go b/internal/scaffold/helm/chart_test.go deleted file mode 100644 index 5c00b4db4b..0000000000 --- a/internal/scaffold/helm/chart_test.go +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helm_test - -import ( - "fmt" - "os" - "path/filepath" - "testing" - - "github.com/operator-framework/operator-sdk/internal/scaffold" - "github.com/operator-framework/operator-sdk/internal/scaffold/helm" - - "github.com/stretchr/testify/assert" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/repo/repotest" -) - -func TestCreateChart(t *testing.T) { - srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz") - if err != nil { - t.Fatalf("Failed to create new temp server: %s", err) - } - defer srv.Stop() - - if err := srv.LinkIndices(); err != nil { - t.Fatalf("Failed to link server indices: %s", err) - } - - const ( - chartName = "test-chart" - latestVersion = "1.2.3" - previousVersion = "1.2.0" - nonExistentVersion = "0.0.1" - customAPIVersion = "example.com/v1" - customKind = "MyApp" - customExpectName = "myapp" - expectDerivedKind = "TestChart" - ) - - testCases := []createChartTestCase{ - { - name: "from scaffold no apiVersion", - expectErr: true, - }, - { - name: "from scaffold no kind", - expectErr: true, - }, - { - name: "version without helm chart", - helmChartVersion: latestVersion, - expectErr: true, - }, - { - name: "repo without helm chart", - helmChartRepo: srv.URL(), - expectErr: true, - }, - { - name: "non-existent version", - helmChart: "test/" + chartName, - helmChartVersion: nonExistentVersion, - expectErr: true, - }, - { - name: "from scaffold with apiVersion and kind", - apiVersion: customAPIVersion, - kind: customKind, - expectResource: mustNewResource(t, customAPIVersion, customKind), - expectChartName: customExpectName, - expectChartVersion: "0.1.0", - }, - { - name: "from directory", - helmChart: filepath.Join(".", "testdata", "testcharts", chartName), - expectResource: mustNewResource(t, helm.DefaultAPIVersion, expectDerivedKind), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from archive", - helmChart: filepath.Join(".", "testdata", "testcharts", fmt.Sprintf("%s-%s.tgz", chartName, latestVersion)), - expectResource: mustNewResource(t, helm.DefaultAPIVersion, expectDerivedKind), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from url", - helmChart: fmt.Sprintf("%s/%s-%s.tgz", srv.URL(), chartName, latestVersion), - expectResource: mustNewResource(t, helm.DefaultAPIVersion, expectDerivedKind), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from repo and name implicit latest", - helmChart: "test/" + chartName, - expectResource: mustNewResource(t, helm.DefaultAPIVersion, expectDerivedKind), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from repo and name implicit latest with apiVersion", - helmChart: "test/" + chartName, - apiVersion: customAPIVersion, - expectResource: mustNewResource(t, customAPIVersion, expectDerivedKind), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from repo and name implicit latest with kind", - helmChart: "test/" + chartName, - kind: customKind, - expectResource: mustNewResource(t, helm.DefaultAPIVersion, customKind), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from repo and name implicit latest with apiVersion and kind", - helmChart: "test/" + chartName, - apiVersion: customAPIVersion, - kind: customKind, - expectResource: mustNewResource(t, customAPIVersion, customKind), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from repo and name explicit latest", - helmChart: "test/" + chartName, - helmChartVersion: latestVersion, - expectResource: mustNewResource(t, helm.DefaultAPIVersion, expectDerivedKind), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from repo and name explicit previous", - helmChart: "test/" + chartName, - helmChartVersion: previousVersion, - expectResource: mustNewResource(t, helm.DefaultAPIVersion, expectDerivedKind), - expectChartName: chartName, - expectChartVersion: previousVersion, - }, - { - name: "from name and repo url implicit latest", - helmChart: chartName, - helmChartRepo: srv.URL(), - expectResource: mustNewResource(t, helm.DefaultAPIVersion, expectDerivedKind), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from name and repo url explicit latest", - helmChart: chartName, - helmChartRepo: srv.URL(), - helmChartVersion: latestVersion, - expectResource: mustNewResource(t, helm.DefaultAPIVersion, expectDerivedKind), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from name and repo url explicit previous", - helmChart: chartName, - helmChartRepo: srv.URL(), - helmChartVersion: previousVersion, - expectResource: mustNewResource(t, helm.DefaultAPIVersion, expectDerivedKind), - expectChartName: chartName, - expectChartVersion: previousVersion, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runTestCase(t, srv.Root(), tc) - }) - } -} - -type createChartTestCase struct { - name string - - apiVersion string - kind string - helmChart string - helmChartVersion string - helmChartRepo string - - expectResource *scaffold.Resource - expectChartName string - expectChartVersion string - expectErr bool -} - -func mustNewResource(t *testing.T, apiVersion, kind string) *scaffold.Resource { - r, err := scaffold.NewResource(apiVersion, kind) - if err != nil { - t.Fatalf("Could not create resource for apiVersion=%s kind=%s: %s", apiVersion, kind, err) - } - return r -} - -func runTestCase(t *testing.T, testDir string, tc createChartTestCase) { - outputDir := filepath.Join(testDir, "output") - assert.NoError(t, os.Mkdir(outputDir, 0755)) - defer os.RemoveAll(outputDir) - - os.Setenv("XDG_CONFIG_HOME", filepath.Join(testDir, ".config")) - os.Setenv("XDG_CACHE_HOME", filepath.Join(testDir, ".cache")) - os.Setenv("HELM_REPOSITORY_CONFIG", filepath.Join(testDir, "repositories.yaml")) - os.Setenv("HELM_REPOSITORY_CACHE", filepath.Join(testDir)) - defer os.Unsetenv("XDG_CONFIG_HOME") - defer os.Unsetenv("XDG_CACHE_HOME") - defer os.Unsetenv("HELM_REPOSITORY_CONFIG") - defer os.Unsetenv("HELM_REPOSITORY_CACHE") - - opts := helm.CreateChartOptions{ - ResourceAPIVersion: tc.apiVersion, - ResourceKind: tc.kind, - Chart: tc.helmChart, - Version: tc.helmChartVersion, - Repo: tc.helmChartRepo, - } - resource, chart, err := helm.CreateChart(outputDir, opts) - if tc.expectErr { - assert.Error(t, err) - return - } - - if !assert.NoError(t, err) { - return - } - assert.Equal(t, tc.expectResource, resource) - assert.Equal(t, tc.expectChartName, chart.Name()) - assert.Equal(t, tc.expectChartVersion, chart.Metadata.Version) - - loadedChart, err := loader.Load(filepath.Join(outputDir, helm.HelmChartsDir, chart.Name())) - if err != nil { - t.Fatalf("Could not load chart from expected location: %s", err) - } - - assert.Equal(t, loadedChart, chart) -} diff --git a/internal/scaffold/helm/dockerfile.go b/internal/scaffold/helm/dockerfile.go deleted file mode 100644 index a77368ae31..0000000000 --- a/internal/scaffold/helm/dockerfile.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helm - -import ( - "path/filepath" - "strings" - - "github.com/operator-framework/operator-sdk/internal/scaffold" - "github.com/operator-framework/operator-sdk/internal/scaffold/input" - "github.com/operator-framework/operator-sdk/version" -) - -// Dockerfile specifies the Helm Dockerfile scaffold -type Dockerfile struct { - input.Input - - HelmChartsDir string - ImageTag string -} - -// GetInput gets the scaffold execution input -func (d *Dockerfile) GetInput() (input.Input, error) { - if d.Path == "" { - d.Path = filepath.Join(scaffold.BuildDir, scaffold.DockerfileFile) - } - d.HelmChartsDir = HelmChartsDir - d.ImageTag = strings.TrimSuffix(version.Version, "+git") - d.TemplateBody = dockerFileHelmTmpl - return d.Input, nil -} - -const dockerFileHelmTmpl = `FROM quay.io/operator-framework/helm-operator:{{.ImageTag}} - -COPY watches.yaml ${HOME}/watches.yaml -COPY {{.HelmChartsDir}}/ ${HOME}/{{.HelmChartsDir}}/ -` diff --git a/internal/scaffold/helm/init.go b/internal/scaffold/helm/init.go deleted file mode 100644 index c5c4a3e6ba..0000000000 --- a/internal/scaffold/helm/init.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2020 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helm - -import ( - "fmt" - "path/filepath" - - "github.com/prometheus/common/log" - "k8s.io/client-go/discovery" - "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/yaml" - - "github.com/operator-framework/operator-sdk/internal/genutil" - "github.com/operator-framework/operator-sdk/internal/scaffold" - "github.com/operator-framework/operator-sdk/internal/scaffold/input" - "github.com/operator-framework/operator-sdk/pkg/helm/watches" -) - -// todo: refactory it to work in the kb layout/design -// Init will perform the helm Scaffold to init a project -func Init(cfg input.Config, createOpts CreateChartOptions) error { - - resource, chart, err := CreateChart(cfg.AbsProjectPath, createOpts) - if err != nil { - return fmt.Errorf("failed to create helm chart: %v", err) - } - - valuesPath := filepath.Join("", HelmChartsDir, chart.Name(), "values.yaml") - - rawValues, err := yaml.Marshal(chart.Values) - if err != nil { - return fmt.Errorf("failed to get raw chart values: %v", err) - } - crSpec := fmt.Sprintf("# Default values copied from %s\n\n%s", valuesPath, rawValues) - - roleScaffold := DefaultRoleScaffold - if k8sCfg, err := config.GetConfig(); err != nil { - log.Warnf("Using default RBAC rules: failed to get Kubernetes config: %s", err) - } else if dc, err := discovery.NewDiscoveryClientForConfig(k8sCfg); err != nil { - log.Warnf("Using default RBAC rules: failed to create Kubernetes discovery client: %s", err) - } else { - roleScaffold = GenerateRoleScaffold(dc, chart) - } - - // update watch.yaml for the given resource. - watchesFile := filepath.Join(cfg.AbsProjectPath, watches.WatchesFile) - if err := watches.UpdateForResource(watchesFile, resource, chart.Name()); err != nil { - return fmt.Errorf("failed to create watches.yaml: %w", err) - } - - s := &scaffold.Scaffold{} - err = s.Execute(&cfg, - &Dockerfile{}, - &scaffold.ServiceAccount{}, - &roleScaffold, - &scaffold.RoleBinding{IsClusterScoped: roleScaffold.IsClusterScoped}, - &Operator{}, - &scaffold.CR{ - Resource: resource, - Spec: crSpec, - }, - ) - if err != nil { - return fmt.Errorf("new helm scaffold failed: %v", err) - } - - // nolint:staticcheck - if err = genutil.GenerateCRDNonGo("", *resource, createOpts.CRDVersion); err != nil { - return err - } - - if err := scaffold.UpdateRoleForResource(resource, cfg.AbsProjectPath); err != nil { - return fmt.Errorf("failed to update the RBAC manifest for resource (%v, %v): %v", - resource.APIVersion, resource.Kind, err) - } - return nil -} diff --git a/internal/scaffold/helm/operator.go b/internal/scaffold/helm/operator.go deleted file mode 100644 index 8c04fc5c07..0000000000 --- a/internal/scaffold/helm/operator.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helm - -import ( - "path/filepath" - - "github.com/operator-framework/operator-sdk/internal/scaffold" - "github.com/operator-framework/operator-sdk/internal/scaffold/input" -) - -// Operator specifies the Helm operator.yaml manifest scaffold -type Operator struct { - input.Input -} - -// GetInput gets the scaffold execution input -func (s *Operator) GetInput() (input.Input, error) { - if s.Path == "" { - s.Path = filepath.Join(scaffold.DeployDir, scaffold.OperatorYamlFile) - } - s.TemplateBody = operatorTemplate - return s.Input, nil -} - -const operatorTemplate = `apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{.ProjectName}} -spec: - replicas: 1 - selector: - matchLabels: - name: {{.ProjectName}} - template: - metadata: - labels: - name: {{.ProjectName}} - spec: - serviceAccountName: {{.ProjectName}} - containers: - - name: {{.ProjectName}} - # Replace this with the built image name - image: REPLACE_IMAGE - imagePullPolicy: Always - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: OPERATOR_NAME - value: "{{.ProjectName}}" -` diff --git a/internal/scaffold/helm/role.go b/internal/scaffold/helm/role.go deleted file mode 100644 index b13a59b7bf..0000000000 --- a/internal/scaffold/helm/role.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helm - -import ( - "fmt" - "path/filepath" - "sort" - "strings" - - "github.com/operator-framework/operator-sdk/internal/scaffold" - - log "github.com/sirupsen/logrus" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/releaseutil" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "sigs.k8s.io/yaml" -) - -// roleDiscoveryInterface is an interface that contains just the discovery -// methods needed by the Helm role scaffold generator. Requiring just this -// interface simplifies testing. -type roleDiscoveryInterface interface { - ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) -} - -var DefaultRoleScaffold = scaffold.Role{ - IsClusterScoped: false, - SkipDefaultRules: false, - CustomRules: []rbacv1.PolicyRule{ - // We need this rule so tiller can read namespaces to ensure they exist - { - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - Verbs: []string{"get"}, - }, - - // We need this rule for leader election and release state storage to work - { - APIGroups: []string{""}, - Resources: []string{"configmaps", "secrets"}, - Verbs: []string{rbacv1.VerbAll}, - }, - - // We need this rule for creating Kubernetes events - { - APIGroups: []string{""}, - Resources: []string{"events"}, - Verbs: []string{"create"}, - }, - }, -} - -// GenerateRoleScaffold generates a role scaffold from the provided helm chart. It -// renders a release manifest using the chart's default values and uses the Kubernetes -// discovery API to lookup each resource in the resulting manifest. -// The role scaffold will have IsClusterScoped=true if the chart lists cluster scoped resources -func GenerateRoleScaffold(dc roleDiscoveryInterface, chart *chart.Chart) scaffold.Role { - log.Info("Generating RBAC rules") - - roleScaffold := DefaultRoleScaffold - clusterResourceRules, namespacedResourceRules, err := generateRoleRules(dc, chart) - if err != nil { - log.Warnf("Using default RBAC rules: failed to generate RBAC rules: %s", err) - return roleScaffold - } - - roleScaffold.SkipDefaultRules = true - - // Use a ClusterRole if cluster scoped resources are listed in the chart - if len(clusterResourceRules) > 0 { - log.Info("Scaffolding ClusterRole and ClusterRolebinding for cluster scoped resources in the helm chart") - roleScaffold.IsClusterScoped = true - } - roleScaffold.CustomRules = append(roleScaffold.CustomRules, append(clusterResourceRules, - namespacedResourceRules...)...) - - log.Warn("The RBAC rules generated in deploy/role.yaml are based on the chart's default manifest." + - " Some rules may be missing for resources that are only enabled with custom values, and" + - " some existing rules may be overly broad. Double check the rules generated in deploy/role.yaml" + - " to ensure they meet the operator's permission requirements.") - - return roleScaffold -} - -func generateRoleRules(dc roleDiscoveryInterface, chart *chart.Chart) ([]rbacv1.PolicyRule, - []rbacv1.PolicyRule, error) { - _, serverResources, err := dc.ServerGroupsAndResources() - if err != nil { - return nil, nil, fmt.Errorf("failed to get server resources: %v", err) - } - - manifests, err := getDefaultManifests(chart) - if err != nil { - return nil, nil, fmt.Errorf("failed to get default manifest: %v", err) - } - - // Use maps of sets of resources, keyed by their group. This helps us - // de-duplicate resources within a group as we traverse the manifests. - clusterGroups := map[string]map[string]struct{}{} - namespacedGroups := map[string]map[string]struct{}{} - - for _, m := range manifests { - name := m.Name - content := strings.TrimSpace(m.Content) - - // Ignore NOTES.txt, helper manifests, and empty manifests. - b := filepath.Base(name) - if b == "NOTES.txt" { - continue - } - if strings.HasPrefix(b, "_") { - continue - } - if content == "" || content == "---" { - continue - } - - // Extract the gvk from the template - resource := unstructured.Unstructured{} - err := yaml.Unmarshal([]byte(content), &resource) - if err != nil { - log.Warnf("Skipping rule generation for %s. Failed to parse manifest: %s", name, err) - continue - } - groupVersion := resource.GetAPIVersion() - group := resource.GroupVersionKind().Group - kind := resource.GroupVersionKind().Kind - - // If we don't have the group or the kind, we won't be able to - // create a valid role rule, log a warning and continue. - if groupVersion == "" { - log.Warnf("Skipping rule generation for %s. Failed to determine resource apiVersion.", name) - continue - } - if kind == "" { - log.Warnf("Skipping rule generation for %s. Failed to determine resource kind.", name) - continue - } - - if resourceName, namespaced, ok := getResource(serverResources, groupVersion, kind); ok { - if !namespaced { - if clusterGroups[group] == nil { - clusterGroups[group] = map[string]struct{}{} - } - clusterGroups[group][resourceName] = struct{}{} - } else { - if namespacedGroups[group] == nil { - namespacedGroups[group] = map[string]struct{}{} - } - namespacedGroups[group][resourceName] = struct{}{} - } - } else { - log.Warnf("Skipping rule generation for %s. Failed to determine resource scope for %s.", - name, resource.GroupVersionKind()) - continue - } - } - - // convert map[string]map[string]struct{} to []rbacv1.PolicyRule - clusterRules := buildRulesFromGroups(clusterGroups) - namespacedRules := buildRulesFromGroups(namespacedGroups) - - return clusterRules, namespacedRules, nil -} - -func getDefaultManifests(c *chart.Chart) ([]releaseutil.Manifest, error) { - install := action.NewInstall(&action.Configuration{}) - install.DryRun = true - install.ReleaseName = "RELEASE-NAME" - install.Replace = true - install.ClientOnly = true - rel, err := install.Run(c, nil) - if err != nil { - return nil, fmt.Errorf("failed to render chart templates: %v", err) - } - _, manifests, err := releaseutil.SortManifests(releaseutil.SplitManifests(rel.Manifest), - chartutil.DefaultVersionSet, releaseutil.InstallOrder) - return manifests, err -} - -func getResource(namespacedResourceList []*metav1.APIResourceList, groupVersion, kind string) (string, bool, bool) { - for _, apiResourceList := range namespacedResourceList { - if apiResourceList.GroupVersion == groupVersion { - for _, apiResource := range apiResourceList.APIResources { - if apiResource.Kind == kind { - return apiResource.Name, apiResource.Namespaced, true - } - } - } - } - return "", false, false -} - -func buildRulesFromGroups(groups map[string]map[string]struct{}) []rbacv1.PolicyRule { - rules := []rbacv1.PolicyRule{} - for group, resourceNames := range groups { - resources := []string{} - for resource := range resourceNames { - resources = append(resources, resource) - } - sort.Strings(resources) - rules = append(rules, rbacv1.PolicyRule{ - APIGroups: []string{group}, - Resources: resources, - Verbs: []string{rbacv1.VerbAll}, - }) - } - return rules -} diff --git a/internal/scaffold/helm/role_test.go b/internal/scaffold/helm/role_test.go deleted file mode 100644 index 548dc1d50a..0000000000 --- a/internal/scaffold/helm/role_test.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helm_test - -import ( - "errors" - "fmt" - "testing" - - "github.com/operator-framework/operator-sdk/internal/scaffold/helm" - - "github.com/stretchr/testify/assert" - "helm.sh/helm/v3/pkg/chart" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestGenerateRoleScaffold(t *testing.T) { - validDiscoveryClient := &mockRoleDiscoveryClient{ - serverGroupsAndResources: func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) { - return simpleGroupList(), simpleResourcesList(), nil - }, - } - - brokenDiscoveryClient := &mockRoleDiscoveryClient{ - serverGroupsAndResources: func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) { - return nil, nil, errors.New("no server resources") - }, - } - - testCases := []roleScaffoldTestCase{ - { - name: "fallback to default with unparsable template", - chart: failChart(), - expectSkipDefaultRules: false, - expectIsClusterScoped: false, - expectLenCustomRules: 3, - }, - { - name: "skip rule for unknown API", - chart: unknownAPIChart(), - expectSkipDefaultRules: true, - expectIsClusterScoped: false, - expectLenCustomRules: 4, - }, - { - name: "namespaced manifest", - chart: namespacedChart(), - expectSkipDefaultRules: true, - expectIsClusterScoped: false, - expectLenCustomRules: 4, - }, - { - name: "cluster scoped manifest", - chart: clusterScopedChart(), - expectSkipDefaultRules: true, - expectIsClusterScoped: true, - expectLenCustomRules: 5, - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("%s with valid discovery client", tc.name), func(t *testing.T) { - roleScaffold := helm.GenerateRoleScaffold(validDiscoveryClient, tc.chart) - assert.Equal(t, tc.expectSkipDefaultRules, roleScaffold.SkipDefaultRules) - assert.Equal(t, tc.expectLenCustomRules, len(roleScaffold.CustomRules)) - assert.Equal(t, tc.expectIsClusterScoped, roleScaffold.IsClusterScoped) - }) - - t.Run(fmt.Sprintf("%s with broken discovery client", tc.name), func(t *testing.T) { - roleScaffold := helm.GenerateRoleScaffold(brokenDiscoveryClient, tc.chart) - assert.Equal(t, false, roleScaffold.SkipDefaultRules) - assert.Equal(t, 3, len(roleScaffold.CustomRules)) - assert.Equal(t, false, roleScaffold.IsClusterScoped) - }) - } -} - -type mockRoleDiscoveryClient struct { - serverGroupsAndResources func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) -} - -func (dc *mockRoleDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) { - return dc.serverGroupsAndResources() -} - -func simpleGroupList() []*metav1.APIGroup { - return []*metav1.APIGroup{ - { - Name: "example", - }, - { - Name: "example2", - }, - } -} - -func simpleResourcesList() []*metav1.APIResourceList { - return []*metav1.APIResourceList{ - { - GroupVersion: "v1", - APIResources: []metav1.APIResource{ - { - Name: "namespaces", - Kind: "Namespace", - Namespaced: false, - }, - { - Name: "pods", - Kind: "Pod", - Namespaced: true, - }, - }, - }, - } -} - -type roleScaffoldTestCase struct { - name string - chart *chart.Chart - expectSkipDefaultRules bool - expectIsClusterScoped bool - expectLenCustomRules int -} - -func failChart() *chart.Chart { - return &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "broken", - }, - Templates: []*chart.File{ - {Name: "broken1.yaml", Data: []byte(`invalid {{ template`)}, - }, - } -} - -func unknownAPIChart() *chart.Chart { - return &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "unknown", - }, - Templates: []*chart.File{ - {Name: "unknown1.yaml", Data: testUnknownData("unknown1")}, - {Name: "pod1.yaml", Data: testPodData("pod1")}, - }, - } -} - -func namespacedChart() *chart.Chart { - return &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "namespaced", - }, - Templates: []*chart.File{ - {Name: "pod1.yaml", Data: testPodData("pod1")}, - {Name: "pod2.yaml", Data: testPodData("pod2")}, - }, - } -} - -func clusterScopedChart() *chart.Chart { - return &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "clusterscoped", - }, - Templates: []*chart.File{ - {Name: "pod1.yaml", Data: testPodData("pod1")}, - {Name: "pod2.yaml", Data: testPodData("pod2")}, - {Name: "ns1.yaml", Data: testNamespaceData("ns1")}, - {Name: "ns2.yaml", Data: testNamespaceData("ns2")}, - }, - } -} - -func testUnknownData(name string) []byte { - return []byte(fmt.Sprintf(`apiVersion: my-test-unknown.unknown.com/v1alpha1 -kind: UnknownKind -metadata: - name: %s`, name), - ) -} - -func testPodData(name string) []byte { - return []byte(fmt.Sprintf(`apiVersion: v1 -kind: Pod -metadata: - name: %s -spec: - containers: - - name: test - image: test`, name), - ) -} - -func testNamespaceData(name string) []byte { - return []byte(fmt.Sprintf(`apiVersion: v1 -kind: Namespace -metadata: - name: %s`, name), - ) -} diff --git a/internal/scaffold/helm/testdata/testcharts/test-chart-1.2.0.tgz b/internal/scaffold/helm/testdata/testcharts/test-chart-1.2.0.tgz deleted file mode 100644 index c32376b22ec31842868de5593a4e8a5b0dee0f6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2732 zcmV;d3RCqTiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI^|Z`(K$@3TI|oZ3a(+?xK=BrOEwp_dkW!Szz3>GiNEih`EL zHa9e>B`GI9*Z0{EBqd9h<=9QL$t_?$II=vP84fwaA7>bY+O*;^Ri=GvKgoob~>H<-%j`C(el~7@4o1DUp#`&9qZB9GHs}O)cIjq^}+o~8co?JRGLW< z!L%39G+ln}lI}6-1PSU`vDCPCui@{=C*T$g45fm9WCK(J4eCJ92}V#ZsKCrpjDV&o zXEC+?gK5dTM>@en4}RYsRs4TSd4~EA2msss|M+mt{|}G${(mox4u?!IGmBup2!%#sFX}wQ;m9_Za!8)52>yNjVZO zW6A>nfRviC7XVlak7<~Aw03p z3(?6MwexRF3s6!M=gj`?*qC%aw}PJ3`xiRG$4cfJsjE3pV6m#!@*CSsl? z2w~~8qzKue8|?F%c4z&XZkDsYm4?jV#3`o@HJK(fN$gCZdzXJ%gie8YEZy8i?e>#g zjp)hJS{n@iPSWIWlv5F-y26pYoHuUWVNkL ztsCw}e_U>$J2+(j{hP1numAbBX0cQzyDoVSAx%?qxjfVmCfl~-$p&kdmi&3WBNN(K zi=EU3T0?F0uL&BOP(vN>iZ#PeOfyp$wcC41I;2Co(W}m@j$PWBMwrP=RpWRV02HRM zCTb0(x}qw9CxxxRc8^|ls*&-b_oRKhzt)KVzK)Tns5CL@T^t3s;lHC^{rq=y-0ke~ z-+i?CynO&uHi*=MifsXI~u zUCOY#-3)%uC}%^4iH(g;c@q2^J$FZMtPHEGh5^RZhEgpjSjlerArBeyL<3a_oK2X) z#715-W*VLiX3iXM&Mz#j1q&9`Jtx805R?!8p2>1^^I0_ajv2UOoLhX7X{)EUm?q;q z*Y+msmOFZ3lnOMjAL*Ag8wqx@y4|OXFHhVEqLbYxwxZ|K7@o`OHo4Swn9!7xOutXv(z>{uPnjJ+*+r}6Q6co-TdZ@#Zz%QKz{QayU%drh^hwn&v_OX zzJbD&TYMA#HH7eMcpI;hlZg~wO?{)9p2YJRBcBjGZo6NiO>i_H81V?OkwoWNn|C>L z0+nU%NBer2wzj{mBe7-P*&?yEF5lqyOd71657)U&sGuWET7y}%f*~(FAcaQb6*;F} z%vT?2t5FS~DB2208+sOiG+Kke&AR7nxgPOz01njWvU2dbWn|Ut*ZHPswMs^OhRhM# zGunK<3^C8P9BrO&VkxSFErLXid6++}9XT4AG(>`j-qgRNHR8VnQ!Zx{6n9e&+=lTc7sbMk_g@`7eP=QNuy<&dZ*+<0C~`hat~`YcH+yp z{tW~Z2?_&;w!6hok*_Y;1FiO9D|g9ip5DtUqNkTOp(Y-`tBBvM!1@MLDst?37rv46 zuGqsiz9Qy!f?gHrtzy)k%@s4_GILrhsw^{NuvTta(Z$GL>4Z0ntL%8b!+NORINeQA zom4rk7E2m+%7?aIjcHOA3cQG_GQ?K7D62?l+2=-GHt3@_D%0()a`yEx2rFyTy&`2% z3>J&15qT^YVVzUIQe|6XS(e~db#R&E)iD)D85@(X{7|uBzbsc(TdJgyvE&haJndJ0 z<7|ooweBl9z*T8h$}dTOmGOO z_1~l8-oE~OFKtUDcbVg{$nHGP^K3(Y%T9V4+byPA{9UQ~`15TEKn0pBsVy8^V&7z(Ne`9N1jdCE4NJL;)Vc@c+j6 zUpVRg#Q)CGac^J$d7!p(|F?*yA&-1%K90s=e|BJQy@5}CSZ}0#2(RNP2-xMfv^4;Ub z4T+;7a%||iF=+fr^Y0Jc8vWnC^q;L*3W2djx3>=i+x*{d_ACD1>m2s>`~SVPMz`+E zq`{ZNoi_hgZP|oB;L{CcK)*#s7&lEgK1` z@NQ*n#s9tIwg3Ow`~Us4$Iz$7pb{F41V8(5HAVr0jPZnt5u`M}q$AWMcnlxMOoPr+ zhr2B+CPqJlLQV~vqC0U{-cykTk3rzb|A)b|RN;_aW8%{+e|b*ez2GyD z!u7N&L5d1E6G#H`=HlyxkqU#ykY7jnbb0{^Q#v3cX4>x4s~?cTe^lFj7B9x5w*4r+ z>Zxd#jO_V8OWoZ!9UKsSl?Df7Kre#>VkT*D@ZaDue4>iUOvBlmw>luHlD}bW0>Toc mZBM7Dc zVQyr3R8em|NM&qo0PI`qZ`(K$?`QoLb7~fCb8GpLM+*V@&`XQG;CiXi^m-*aeB=xc^KbmHfTfqF_$ntPzIOGg}oMAL7-HOIU>h`($3}$4)p4@Ni zbUK~W<74}`)9KXzcDgT5if8-2d(`V5J%P?W>(SU!rHOpf`C(c0!Tm`ZN$4k(iV7aW zwC9l|DZX}t?rG5RVpNf&iMH=v!{3ojz%CdV3JL#62PipeR30Z2453_5o}MKb0!b1^ zBVzh{(~@^DIPxBQ@cZ_p;{Q{^QdECH0NCdLr^hS)e|)m{{|9MgIHa8FSqSrmH%u8b zUi^NFQqmYh@O_U?$OuCZAW0eP3r3?^2p7Xkq5BdQa_s>~OoXCZ$XN({MER}j)lV6_ z!bqZ4A$|Ah-x7xmzXv5XHNVyK7>q%51 zl@U5MRYMPja!Mm-A|Z7MuR2B%8~n~$sx-=reopl|iY#XjAd*P5iI_4q!*$`4r;K@8 zFqFA*K7aWm<7CDEHBJ&nG^+OH`;Tw00)4CRN5&TaKkD?3YyN-yvfJJJ|3kEH0KcFH zJ;v%r4gdP^4hA!jDd&`r%+CrT5|atxu?oHUyajX!!6&z6WPTlFE)0u>rI3V=5Dtv< zLU^`9ZT;KO0+iH&b!PW=taUP<8$k#4zM2^tHJ<}=BQI>X=W{2@@`c4>K8K*>?#R6Q zBU5PjM}liYxdQ)E#Ml>7`-Qrh;|9i|9Y4G^eh!!K-@N_WfB)g**}?Pcbbyg&FhUKj zmT_g3kO;vj@c|hUg~4Uf~6)8l7!wu2V*!Fhg1Tm~S!n5z5Xf=(Xb+g;_Ju9d0>g|V57v~K# zX!-3TcqfU_YUN=c{Q2CU&oe)KPI-*Ckk2-&m@;=l=)ACQ7Fb_%Uga+u)<8Ly&}dtm zT07je{#b0FJ2+(j{hP0sumAbBX0b%XyDoVKK1q_`x;WGXRJ2XUvo+Q%E&20mN6M8p z7CWm8G=^I1UlY_MCYo5@6>IvRsG>SIYPa_?=medh8@}qi>X@aSDukIxWi^ib9zbpi zW1`kj$QvSKILK`UrhE9RQ;m#|y(jJ4!?i~I_jQacL8$_r+{aOH8~!`#)z5z?r`^sT z|2;&T&)bJErIQd8Y8X<6Caj)Kj9H>FhH%)nvAKQdy}eBckL})QLX+7U&9e)HUPjk~ z7>ATMMafo4KvJe5a0y2n5cAkxJ+{F&b2Q=+oF3UnI=M=RLwXB-t7K+EmU(t6p4%f8 zkV1yp?PljV>SmwLzZ}>RglD@?Y(>w)7>>*GHd*Mp^!B?IOeE!c z2>ws1^^@{z+??F+S!(N^mzH25&bC$)0 zZy+&Y2H*I9^}+w@-^HuMWFojzQ*Bh!kvKkMWRpOR+xC}m9URRD7B~dhNTRZ=&AW_w z0F`C#M*F%*Tbtijkyw#;Hb`ux%QyHv6&h>j!&NR5!pR8Z)?gMcVaO{7NTJbqdCqAU z^VLV%YE;7|inapMhMqYf4Obv=v+mhiu1EX~fCF*4tPFf^8Cf;^b-6BDEt3TneVI;Vc6%C^F?EWs`7U^Be?U3G@8E_Tu>_*FSiF_Sp zep6-%@Me7~#dgh53LidL@>l%sWEp}Z7OwgHK*k;ip(k*(CrHS4Q^)WIo3nlJnc<&tfhZT1-c-x&XM zCw-Xs-#Iz$?dv~})Ryl5=5aPlXg>D2-(8_=xH3>y{woKcR)+jHTrtRyERiGB{rWMX z>D04ygj&uo+~tga{Qn1pH~D{--~Hh*u+9IER_ebmyQjUq|35_AIRU>ZP-Nu0$BPY# zqdan~>A5y&{7LigkKG#m-@J66tw?Z=kwLe24+Go$-)#0v{@?2y_xAh$gS1As?u)p= zm;IeK|5k0;_&?y&4Tmc~FDs9Qxc0MuH&SU7nR8(d9O_BpJQsR1LS0RmhcHOhY#?rn zkhV@5x{%SW+1rj zX;gv)B{0e{@Pap2U$3-~=skt(I?AW>D~PF7UNEA%Z9kp*UNHENY}?QL#dy> [flags] --api-version=app.example.com/v1alpha1 \ --kind=AppService - # Helm project - $ operator-sdk new app-operator --type=helm \ - --api-version=app.example.com/v1alpha1 \ - --kind=AppService - - $ operator-sdk new app-operator --type=helm \ - --api-version=app.example.com/v1alpha1 \ - --kind=AppService \ - --helm-chart=myrepo/app - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=myrepo/app - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=myrepo/app \ - --helm-chart-version=1.2.3 - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=app \ - --helm-chart-repo=https://charts.mycompany.com/ - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=app \ - --helm-chart-repo=https://charts.mycompany.com/ \ - --helm-chart-version=1.2.3 - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=/path/to/local/chart-directories/app/ - - $ operator-sdk new app-operator --type=helm \ - --helm-chart=/path/to/local/chart-archives/app-1.2.3.tgz - ``` ### Options ``` - --api-version string Kubernetes apiVersion and has a format of $GROUP_NAME/$VERSION (e.g app.example.com/v1alpha1) - --crd-version string CRD version to generate (default "v1") - --generate-playbook Generate a playbook skeleton. (Only used for --type ansible) - --helm-chart string Initialize helm operator with existing helm chart (, /, or local path). Valid only for --type helm - --helm-chart-repo string Chart repository URL for the requested helm chart, Valid only for --type helm - --helm-chart-version string Specific version of the helm chart (default is latest version). Valid only for --type helm - -h, --help help for new - --kind string Kubernetes resource Kind name. (e.g AppService) - --skip-generation Skip generation of deepcopy and OpenAPI code and OpenAPI CRD specs - --type string Type of operator to initialize (choices: "ansible" or "helm") + --api-version string Kubernetes apiVersion and has a format of $GROUP_NAME/$VERSION (e.g app.example.com/v1alpha1) + --crd-version string CRD version to generate (default "v1") + --generate-playbook Generate a playbook skeleton. (Only used for --type ansible) + -h, --help help for new + --kind string Kubernetes resource Kind name. (e.g AppService) + --skip-generation Skip generation of deepcopy and OpenAPI code and OpenAPI CRD specs ``` ### Options inherited from parent commands