diff --git a/changelog/fragments/rm-legacy-bundle-cmds.yaml b/changelog/fragments/rm-legacy-bundle-cmds.yaml new file mode 100644 index 0000000000..f18bba17c9 --- /dev/null +++ b/changelog/fragments/rm-legacy-bundle-cmds.yaml @@ -0,0 +1,11 @@ +# entries is a list of entries to include in +# release notes and/or the migration guide +entries: + - description: > + Remove legacy "bundle create/validate" commands + kind: "removal" + # Is this a breaking change? + breaking: true + migration: + header: Remove legacy "bundle create/validate" commands + body: TBD diff --git a/changelog/fragments/rm-legacy-gen-bundle.yaml b/changelog/fragments/rm-legacy-gen-bundle.yaml new file mode 100644 index 0000000000..cfac537bea --- /dev/null +++ b/changelog/fragments/rm-legacy-gen-bundle.yaml @@ -0,0 +1,11 @@ +# entries is a list of entries to include in +# release notes and/or the migration guide +entries: + - description: > + Remove legacy generate bundle command + kind: "removal" + # Is this a breaking change? + breaking: true + migration: + header: Remove legacy generate bundle command + body: TBD diff --git a/changelog/fragments/rm-legacy-gen-csv.yaml b/changelog/fragments/rm-legacy-gen-csv.yaml new file mode 100644 index 0000000000..df7724a00f --- /dev/null +++ b/changelog/fragments/rm-legacy-gen-csv.yaml @@ -0,0 +1,11 @@ +# entries is a list of entries to include in +# release notes and/or the migration guide +entries: + - description: > + Remove legacy generate csv command + kind: "removal" + # Is this a breaking change? + breaking: true + migration: + header: Remove legacy generate csv command + body: TBD diff --git a/changelog/fragments/rm-legacy-gen-packagemanifests.yaml b/changelog/fragments/rm-legacy-gen-packagemanifests.yaml new file mode 100644 index 0000000000..9b1f9b7984 --- /dev/null +++ b/changelog/fragments/rm-legacy-gen-packagemanifests.yaml @@ -0,0 +1,11 @@ +# entries is a list of entries to include in +# release notes and/or the migration guide +entries: + - description: > + Remove legacy generate packagemanifests command + kind: "removal" + # Is this a breaking change? + breaking: true + migration: + header: Remove legacy generate packagemanifests command + body: TBD diff --git a/cmd/operator-sdk/bundle/cmd.go b/cmd/operator-sdk/bundle/cmd.go index 39e51e120a..ae637ee9a7 100644 --- a/cmd/operator-sdk/bundle/cmd.go +++ b/cmd/operator-sdk/bundle/cmd.go @@ -20,16 +20,11 @@ import ( //nolint:structcheck type bundleCmd struct { - directory string - packageName string - imageTag string - imageBuilder string - defaultChannel string - channels string - generateOnly bool + directory string + imageBuilder string } -func newCmd() *cobra.Command { +func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: "bundle", Short: "Manage operator bundle metadata", @@ -39,30 +34,12 @@ native software, like the Operator Lifecycle Manager. More information about operator bundles and metadata: https://github.com/operator-framework/operator-registry/blob/master/docs/design/operator-bundle.md -`, - } - return cmd -} -func NewCmdLegacy() *cobra.Command { - cmd := newCmd() - cmd.Long += ` -More information about the integration with OLM via SDK: -https://sdk.operatorframework.io/docs/olm-integration/legacy -` - cmd.AddCommand( - newCreateCmd(), - newValidateCmdLegacy(), - ) - return cmd -} - -func NewCmd() *cobra.Command { - cmd := newCmd() - cmd.Long += ` More information about the integration with OLM via SDK: https://sdk.operatorframework.io/docs/olm-integration -` +`, + } + cmd.AddCommand( newValidateCmd(), ) diff --git a/cmd/operator-sdk/bundle/cmd_test.go b/cmd/operator-sdk/bundle/cmd_test.go index 6d1164a537..dae98dbacc 100644 --- a/cmd/operator-sdk/bundle/cmd_test.go +++ b/cmd/operator-sdk/bundle/cmd_test.go @@ -30,16 +30,4 @@ var _ = Describe("Running a bundle command", func() { Expect(subcommands[0].Use).To(Equal("validate")) }) }) - - Describe("NewCmdLegacy", func() { - It("builds and returns a cobra command with the correct subcommands", func() { - cmd := NewCmdLegacy() - Expect(cmd).NotTo(BeNil()) - - subcommands := cmd.Commands() - Expect(len(subcommands)).To(Equal(2)) - Expect(subcommands[0].Use).To(Equal("create")) - Expect(subcommands[1].Use).To(Equal("validate")) - }) - }) }) diff --git a/cmd/operator-sdk/bundle/create.go b/cmd/operator-sdk/bundle/create.go deleted file mode 100644 index 9ca5488c0a..0000000000 --- a/cmd/operator-sdk/bundle/create.go +++ /dev/null @@ -1,387 +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 bundle - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - - catalog "github.com/operator-framework/operator-sdk/internal/generate/olm-catalog" - "github.com/operator-framework/operator-sdk/internal/util/projutil" - - "github.com/operator-framework/operator-sdk/internal/registry" - - "github.com/blang/semver" - "github.com/operator-framework/operator-registry/pkg/lib/bundle" - "github.com/operator-framework/operator-sdk/internal/scorecard" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -type bundleCreateCmd struct { - bundleCmd - - outputDir string - overwrite bool -} - -// newCreateCmd returns a command that will build operator bundle image or -// generate metadata for them. -func newCreateCmd() *cobra.Command { - c := &bundleCreateCmd{} - - cmd := &cobra.Command{ - Use: "create", - Short: "Create an operator bundle image", - Long: `The 'operator-sdk bundle create' command will build an operator -bundle image containing operator metadata and manifests, tagged with the -provided image tag. - -To write all files required to build a bundle image without building the -image, set '--generate-only=true'. A bundle.Dockerfile and bundle metadata -will be written if '--generate-only=true': - -` + "```" + ` - $ operator-sdk bundle create --generate-only --directory ./deploy/olm-catalog/test-operator/manifests - $ ls . - ... - bundle.Dockerfile - ... - $ tree ./deploy/olm-catalog/test-operator/ - ./deploy/olm-catalog/test-operator/ - ├── manifests - │   ├── example.com_tests_crd.yaml - │   └── test-operator.clusterserviceversion.yaml - └── metadata - └── annotations.yaml -` + "```" + ` - -'--generate-only' is useful if you want to build an operator's bundle image -manually or modify metadata before building an image. - -More information about operator bundles and metadata: -https://github.com/operator-framework/operator-registry#manifest-format. - -NOTE: bundle images are not runnable. -`, - Example: `The following invocation will build a test-operator 0.1.0 bundle image using Docker. -This image will contain manifests for package channels 'stable' and 'beta': - - $ operator-sdk bundle create quay.io/example/test-operator:v0.1.0 \ - --directory ./deploy/olm-catalog/test-operator/manifests \ - --package test-operator \ - --channels stable,beta \ - --default-channel stable - -Assuming your operator has the same name as your repo directory and the only -channel is 'stable', the above command can be abbreviated to: - - $ operator-sdk bundle create quay.io/example/test-operator:v0.1.0 - -The following invocation will generate test-operator bundle metadata and a -bundle.Dockerfile for your latest operator version without building the image: - - $ operator-sdk bundle create \ - --generate-only \ - --package test-operator \ - --channels beta \ - --default-channel beta -`, - RunE: func(cmd *cobra.Command, args []string) (err error) { - if err = c.setDefaults(); err != nil { - log.Fatalf("Failed to default args: %v", err) - } - - if err = c.validate(args); err != nil { - return fmt.Errorf("invalid command args: %v", err) - } - - if c.generateOnly { - err = c.runGenerate() - } else { - c.imageTag = args[0] - err = c.runBuild() - } - - if err != nil { - log.Fatal(err) - } - - return nil - }, - Deprecated: "use 'generate bundle' and 'docker build -f bundle.Dockerfile' instead", - } - - c.addToFlagSet(cmd.Flags()) - return cmd -} - -func (c *bundleCreateCmd) addToFlagSet(fs *pflag.FlagSet) { - fs.StringVarP(&c.directory, "directory", "d", "", - "The directory where bundle manifests are located, ex. /deploy/olm-catalog/test-operator/manifests") - fs.StringVarP(&c.outputDir, "output-dir", "o", "", - "Optional output directory for operator manifests") - fs.StringVarP(&c.imageTag, "tag", "t", "", - "The path of a registry to pull from, image name and its tag that present the bundle image "+ - "(e.g. quay.io/test/test-operator:v0.1.0)") - fs.StringVarP(&c.packageName, "package", "p", "", - "The name of the package that bundle image belongs to. Set if package name differs from project name") - fs.StringVarP(&c.channels, "channels", "c", "stable", - "The comma-separated list of channels that bundle image belongs to") - fs.BoolVarP(&c.generateOnly, "generate-only", "g", false, - "Generate metadata/, manifests/ and a Dockerfile on disk without building the bundle image") - fs.BoolVar(&c.overwrite, "overwrite", false, - "Overwrite bundle.Dockerfile, manifests and metadata dirs if they exist. "+ - "If --output-dir is also set, the original files will not be overwritten") - fs.StringVarP(&c.imageBuilder, "image-builder", "b", "docker", - "Tool to build container images. One of: [docker, podman, buildah]") - fs.StringVarP(&c.defaultChannel, "default-channel", "e", "", - "The default channel for the bundle image") -} - -func (c *bundleCreateCmd) setDefaults() (err error) { - if c.packageName == "" { - c.packageName = filepath.Base(projutil.MustGetwd()) - } - defaultManifestsDir := filepath.Join(catalog.OLMCatalogDir, c.packageName, bundle.ManifestsDir) - if c.directory == "" { - if isNotExist(defaultManifestsDir) { - return fmt.Errorf("default manifests directory %s does not exist; "+ - "set --directory to a valid bundle manifests directory", defaultManifestsDir) - } - c.directory = defaultManifestsDir - } - - // Clean and make paths relative for less verbose error messages. Don't return - // an error if we cannot. - if dir, err := relWd(c.directory); err == nil { - c.directory = dir - } - - // A default channel can be inferred if there is only one channel. Don't infer - // default otherwise; the user must set this value. - if c.defaultChannel == "" && strings.Count(c.channels, ",") == 0 { - c.defaultChannel = c.channels - } - - return nil -} - -func relWd(dir string) (out string, err error) { - if out, err = filepath.Abs(dir); err != nil { - return "", err - } - wd, err := os.Getwd() - if err != nil { - return "", err - } - return filepath.Rel(wd, out) -} - -func (c bundleCreateCmd) validate(args []string) error { - if c.directory == "" { - return fmt.Errorf("--directory must be set") - } - if c.packageName == "" { - return fmt.Errorf("--package must be set") - } - - // Ensure a default channel is present. - if c.defaultChannel == "" { - return fmt.Errorf("--default-channel must be set") - } - - // Bundle commands only work with bundle directory formats, not package - // manifests formats. - if isPackageManifestsDir(c.directory, c.packageName) { - return fmt.Errorf("bundle commands can only be used on bundle directory formats") - } - - if c.generateOnly { - if len(args) != 0 { - return errors.New("the command does not accept any arguments if --generate-only=true") - } - } else { - if len(args) != 1 { - return errors.New("a bundle image tag is a required argument if --generate-only=true") - } - } - return nil -} - -// runGenerate generates a bundle.Dockerfile, and manifests/ and metadata/ dirs with scorecard config -// copied to the bundle image. -func (c bundleCreateCmd) runGenerate() error { - if c.generateOnly { - c.overwrite = true - } - - err := bundle.GenerateFunc(c.directory, c.outputDir, c.packageName, c.channels, c.defaultChannel, c.overwrite) - if err != nil { - return fmt.Errorf("error generating bundle image files: %v", err) - } - - // rootDir is the location where metadata directory is present. - rootDir := c.outputDir - if rootDir == "" { - rootDir = filepath.Dir(c.directory) - } - - if err = RewriteBundleImageContents(rootDir); err != nil { - return err - } - return nil -} - -// runBuild runs the equivalent of runGenerate then builds a bundle image. If -// a manifest/, metadata/ or bundle.DockerFile do not exist, they are removed. -func (c bundleCreateCmd) runBuild() error { - rootDir := filepath.Dir(c.directory) - metadataDir := filepath.Join(rootDir, bundle.MetadataDir) - - // Clean up transient files once the image is built, as they are no longer - // needed. - if isNotExist(metadataDir) { - defer remove(metadataDir) - } - if isNotExist(bundle.DockerFile) { - defer remove(bundle.DockerFile) - } - - // Build with overwrite-able option. - err := c.buildFunc() - if err != nil { - return fmt.Errorf("error building bundle image: %v", err) - } - return nil -} - -// buildFunc is used to build a container image from a list of manifests and generates dockerfile and annotations.yaml. -func (c bundleCreateCmd) buildFunc() error { - _, err := os.Stat(c.directory) - if os.IsNotExist(err) { - return err - } - - err = c.runGenerate() - if err != nil { - return err - } - - // Build bundle image - log.Info("Building bundle image") - buildCmd, err := bundle.BuildBundleImage(c.imageTag, c.imageBuilder) - if err != nil { - return err - } - - if err := bundle.ExecuteCommand(buildCmd); err != nil { - return err - } - - return nil -} - -// remove removes path from disk. Used in defer statements. -func remove(path string) { - if err := os.RemoveAll(path); err != nil { - log.Fatal(err) - } -} - -// isExist returns true if path exists. -func isExist(path string) bool { - _, err := os.Stat(path) - return err == nil || os.IsExist(err) -} - -// isNotExist returns true if path does not exist. -func isNotExist(path string) bool { - _, err := os.Stat(path) - return err != nil && os.IsNotExist(err) -} - -// isPackageManifestsDir checks if dir is a package manifests format directory -// by checking for the existence of a package manifest and a semver-named directory. -func isPackageManifestsDir(dir, operatorName string) bool { - packageManifestPath := filepath.Join(filepath.Dir(dir), operatorName+".package.yaml") - _, err := semver.ParseTolerant(filepath.Clean(filepath.Base(dir))) - return isExist(packageManifestPath) && err == nil -} - -// copyScorecardConfigToBundle checks if bundle.Dockerfile and scorecard config exists in -// the operator project. If it does, it injects the scorecard configuration into bundle -// image. -// TODO: Add labels to annotations.yaml and bundle.dockerfile. -func copyScorecardConfig() error { - if isExist(bundle.DockerFile) && isExist(scorecard.ConfigDirName) { - scorecardFileContent := fmt.Sprintf("COPY %s %s\n", scorecard.ConfigDirName, scorecard.ConfigDirPath) - err := projutil.RewriteFileContents(bundle.DockerFile, "COPY", scorecardFileContent) - if err != nil { - return fmt.Errorf("error rewriting dockerfile, %v", err) - } - } - return nil -} - -func addLabelsToDockerfile(filename string, metricAnnotation map[string]string) error { - var sdkMetricContent strings.Builder - for key, value := range metricAnnotation { - sdkMetricContent.WriteString(fmt.Sprintf("LABEL %s=%s\n", key, value)) - } - - err := projutil.RewriteFileContents(filename, "LABEL", sdkMetricContent.String()) - if err != nil { - return fmt.Errorf("error rewriting dockerfile with metric labels, %v", err) - } - return nil -} - -func RewriteBundleImageContents(rootDir string) error { - metricLabels := projutil.MakeBundleMetricsLabels() - - // write metric labels to bundle.Dockerfile - if err := addLabelsToDockerfile(bundle.DockerFile, metricLabels); err != nil { - return fmt.Errorf("error writing metric labels to bundle.dockerfile: %v", err) - } - - annotationsFilePath := getAnnotationsFilePath(rootDir) - if err := addLabelsToAnnotations(annotationsFilePath, metricLabels); err != nil { - return fmt.Errorf("error writing metric labels to annotations.yaml: %v", err) - } - - // Add a COPY for the scorecard config to bundle.Dockerfile. - if err := copyScorecardConfig(); err != nil { - return fmt.Errorf("error copying scorecardConfig to bundle image, %v", err) - } - return nil -} - -// getAnnotationsFilePath return the locations of annotations.yaml. -func getAnnotationsFilePath(rootDir string) string { - return filepath.Join(rootDir, bundle.MetadataDir, bundle.AnnotationsFile) -} - -func addLabelsToAnnotations(filename string, metricLables map[string]string) error { - err := registry.RewriteAnnotationsYaml(filename, metricLables) - if err != nil { - return err - } - return nil -} diff --git a/cmd/operator-sdk/bundle/create_test.go b/cmd/operator-sdk/bundle/create_test.go deleted file mode 100644 index 7320d72d35..0000000000 --- a/cmd/operator-sdk/bundle/create_test.go +++ /dev/null @@ -1,145 +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 bundle - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("Checking operator-sdk bundle create command", func() { - Describe("newCreateCmd", func() { - It("builds and returns a cobra command", func() { - cmd := newCreateCmd() - Expect(cmd).NotTo(BeNil()) - - flag := cmd.Flags().Lookup("directory") - Expect(flag).NotTo(BeNil()) - Expect(flag.Shorthand).To(Equal("d")) - - flag = cmd.Flags().Lookup("output-dir") - Expect(flag).NotTo(BeNil()) - - flag = cmd.Flags().Lookup("tag") - Expect(flag).NotTo(BeNil()) - Expect(flag.Shorthand).To(Equal("t")) - - flag = cmd.Flags().Lookup("package") - Expect(flag).NotTo(BeNil()) - - flag = cmd.Flags().Lookup("channels") - Expect(flag).NotTo(BeNil()) - Expect(flag.Shorthand).To(Equal("c")) - Expect(flag.DefValue).To(Equal("stable")) - - flag = cmd.Flags().Lookup("generate-only") - Expect(flag).NotTo(BeNil()) - Expect(flag.DefValue).To(Equal("false")) - - flag = cmd.Flags().Lookup("overwrite") - Expect(flag).NotTo(BeNil()) - Expect(flag.DefValue).To(Equal("false")) - - flag = cmd.Flags().Lookup("image-builder") - Expect(flag).NotTo(BeNil()) - Expect(flag.Shorthand).To(Equal("b")) - Expect(flag.DefValue).To(Equal("docker")) - - flag = cmd.Flags().Lookup("default-channel") - Expect(flag).NotTo(BeNil()) - Expect(flag.Shorthand).To(Equal("e")) - }) - }) - - Describe("validate", func() { - var cmd bundleCreateCmd - BeforeEach(func() { - cmd = bundleCreateCmd{} - }) - - It("fails if directory is not set", func() { - err := cmd.validate([]string{}) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("--directory must be set")) - }) - - It("fails if package is not set", func() { - cmd.directory = "apple" - - err := cmd.validate([]string{}) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("--package must be set")) - }) - - It("fails if default channel is not set", func() { - cmd.directory = "banana" - cmd.packageName = "cherry" - - err := cmd.validate([]string{}) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("--default-channel must be set")) - }) - - It("fails if GenerateOnly is false but a bundle image tag is not provided", func() { - cmd.directory = "durian" - cmd.packageName = "elderberry" - cmd.defaultChannel = "fig" - - err := cmd.validate([]string{}) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("a bundle image tag is a required argument if --generate-only=true")) - }) - - It("fails if GenerateOnly is false and more than one arg is provided", func() { - cmd.directory = "grapefruit" - cmd.packageName = "honeydew" - cmd.defaultChannel = "imbe" - - err := cmd.validate([]string{"aaa", "bbb"}) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("a bundle image tag is a required argument if --generate-only=true")) - }) - - It("succeeds if GenerateOnly is false and a bundle image tag is provided", func() { - cmd.directory = "jackfruit" - cmd.packageName = "kiwi" - cmd.defaultChannel = "lime" - - err := cmd.validate([]string{"aaa"}) - Expect(err).NotTo(HaveOccurred()) - }) - - It("fails if GenerateOnly is true and any args are provided", func() { - cmd.directory = "mango" - cmd.packageName = " nectarine" - cmd.defaultChannel = "orange" - cmd.generateOnly = true - - err := cmd.validate([]string{"aaa"}) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("the command does not accept any arguments if --generate-only=true")) - }) - - It("succeeds if GenerateOnly is true and no args are provided", func() { - cmd.directory = "pineapple" - cmd.packageName = "quince" - cmd.defaultChannel = "raspberry" - cmd.generateOnly = true - - err := cmd.validate([]string{}) - Expect(err).NotTo(HaveOccurred()) - }) - }) -}) diff --git a/cmd/operator-sdk/bundle/validate.go b/cmd/operator-sdk/bundle/validate.go index f9c699ca88..fdf2fe62f2 100644 --- a/cmd/operator-sdk/bundle/validate.go +++ b/cmd/operator-sdk/bundle/validate.go @@ -58,28 +58,6 @@ then validate them for 'test-operator' version v0.1.0: # Validate the directory containing manifests and metadata. $ operator-sdk bundle validate ./bundle -To build and validate an image built with the above manifests and metadata: - - # Create a registry namespace or use an existing one. - $ export NAMESPACE= - - # Build and push the image using the docker CLI. - $ docker build -f bundle.Dockerfile -t quay.io/$NAMESPACE/test-operator:v0.1.0 . - $ docker push quay.io/$NAMESPACE/test-operator:v0.1.0 - - # Ensure the image with modified metadata and Dockerfile is valid. - $ operator-sdk bundle validate quay.io/$NAMESPACE/test-operator:v0.1.0 -` - - examplesLegacy = `The following command flow will generate test-operator bundle manifests and metadata, -then validate them for 'test-operator' version v0.1.0: - - # Generate manifests and metadata locally. - $ operator-sdk generate bundle --version 0.1.0 - - # Validate the directory containing manifests and metadata. - $ operator-sdk bundle validate ./deploy/olm-catalog/test-operator - To build and validate an image built with the above manifests and metadata: # Create a registry namespace or use an existing one. @@ -108,14 +86,6 @@ func newValidateCmd() *cobra.Command { return cmd } -// newValidateCmdLegacy returns a command that will validate an operator bundle for the legacy CLI. -func newValidateCmdLegacy() *cobra.Command { - cmd := makeValidateCmd() - cmd.Long = longHelp - cmd.Example = examplesLegacy - return cmd -} - // makeValidateCmd makes a command that will validate an operator bundle. Help text must be customized. func makeValidateCmd() *cobra.Command { c := bundleValidateCmd{} @@ -309,3 +279,21 @@ func checkResults(results []apierrors.ManifestResult, res *internal.Result) { } } } + +// relWd returns the path of dir relative to the current working directory +func relWd(dir string) (out string, err error) { + if out, err = filepath.Abs(dir); err != nil { + return "", err + } + wd, err := os.Getwd() + if err != nil { + return "", err + } + return filepath.Rel(wd, out) +} + +// isExist returns true if path exists. +func isExist(path string) bool { + _, err := os.Stat(path) + return err == nil || os.IsExist(err) +} diff --git a/cmd/operator-sdk/cli/legacy.go b/cmd/operator-sdk/cli/legacy.go index f21031569a..8e4b7f8ec9 100644 --- a/cmd/operator-sdk/cli/legacy.go +++ b/cmd/operator-sdk/cli/legacy.go @@ -21,9 +21,7 @@ import ( "github.com/operator-framework/operator-sdk/cmd/operator-sdk/add" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/build" - "github.com/operator-framework/operator-sdk/cmd/operator-sdk/bundle" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/completion" - "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/new" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/olm" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/scorecard" @@ -65,10 +63,8 @@ func GetCLIRoot() *cobra.Command { root.AddCommand( add.NewCmd(), build.NewCmd(), - bundle.NewCmdLegacy(), scorecard.NewCmd(), completion.NewCmd(), - generate.NewCmdLegacy(), new.NewCmd(), olm.NewCmd(), version.NewCmd(), diff --git a/cmd/operator-sdk/generate/bundle/bundle.go b/cmd/operator-sdk/generate/bundle/bundle.go index 4c3da416dc..3f75ac6dff 100644 --- a/cmd/operator-sdk/generate/bundle/bundle.go +++ b/cmd/operator-sdk/generate/bundle/bundle.go @@ -24,10 +24,11 @@ import ( "github.com/operator-framework/operator-registry/pkg/lib/bundle" "sigs.k8s.io/kubebuilder/pkg/model/config" - genbundle "github.com/operator-framework/operator-sdk/cmd/operator-sdk/bundle" genutil "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/internal" gencsv "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion" "github.com/operator-framework/operator-sdk/internal/generate/collector" + "github.com/operator-framework/operator-sdk/internal/registry" + "github.com/operator-framework/operator-sdk/internal/scorecard" "github.com/operator-framework/operator-sdk/internal/util/projutil" ) @@ -269,13 +270,48 @@ func (c bundleCmd) generateMetadata(manifestsDir, outputDir string) error { rootDir = filepath.Dir(manifestsDir) } - if err = genbundle.RewriteBundleImageContents(rootDir); err != nil { + if err = rewriteBundleImageContents(rootDir); err != nil { return err } } return nil } +func rewriteBundleImageContents(rootDir string) error { + metricLabels := projutil.MakeBundleMetricsLabels() + + // write metric labels to bundle.Dockerfile + if err := addLabelsToDockerfile(bundle.DockerFile, metricLabels); err != nil { + return fmt.Errorf("error writing metric labels to bundle.dockerfile: %v", err) + } + + annotationsFilePath := getAnnotationsFilePath(rootDir) + if err := addLabelsToAnnotations(annotationsFilePath, metricLabels); err != nil { + return fmt.Errorf("error writing metric labels to annotations.yaml: %v", err) + } + + // Add a COPY for the scorecard config to bundle.Dockerfile. + if err := copyScorecardConfig(); err != nil { + return fmt.Errorf("error copying scorecardConfig to bundle image, %v", err) + } + return nil +} + +// copyScorecardConfigToBundle checks if bundle.Dockerfile and scorecard config exists in +// the operator project. If it does, it injects the scorecard configuration into bundle +// image. +// TODO: Add labels to annotations.yaml and bundle.dockerfile. +func copyScorecardConfig() error { + if isExist(bundle.DockerFile) && isExist(scorecard.ConfigDirName) { + scorecardFileContent := fmt.Sprintf("COPY %s %s\n", scorecard.ConfigDirName, scorecard.ConfigDirPath) + err := projutil.RewriteFileContents(bundle.DockerFile, "COPY", scorecardFileContent) + if err != nil { + return fmt.Errorf("error rewriting dockerfile, %v", err) + } + } + return nil +} + // checkMetatdataExists returns true if bundle.Dockerfile and metadataDir exist, if not // it returns false. func checkMetatdataExists(outputDir, manifestsDir string) bool { @@ -291,3 +327,35 @@ func checkMetatdataExists(outputDir, manifestsDir string) bool { } return true } + +func addLabelsToDockerfile(filename string, metricAnnotation map[string]string) error { + var sdkMetricContent strings.Builder + for key, value := range metricAnnotation { + sdkMetricContent.WriteString(fmt.Sprintf("LABEL %s=%s\n", key, value)) + } + + err := projutil.RewriteFileContents(filename, "LABEL", sdkMetricContent.String()) + if err != nil { + return fmt.Errorf("error rewriting dockerfile with metric labels, %v", err) + } + return nil +} + +// getAnnotationsFilePath return the locations of annotations.yaml. +func getAnnotationsFilePath(rootDir string) string { + return filepath.Join(rootDir, bundle.MetadataDir, bundle.AnnotationsFile) +} + +func addLabelsToAnnotations(filename string, metricLables map[string]string) error { + err := registry.RewriteAnnotationsYaml(filename, metricLables) + if err != nil { + return err + } + return nil +} + +// isExist returns true if path exists. +func isExist(path string) bool { + _, err := os.Stat(path) + return err == nil || os.IsExist(err) +} diff --git a/cmd/operator-sdk/generate/cmd.go b/cmd/operator-sdk/generate/cmd.go index b0750b16b9..837d043dfa 100644 --- a/cmd/operator-sdk/generate/cmd.go +++ b/cmd/operator-sdk/generate/cmd.go @@ -22,18 +22,15 @@ import ( "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/packagemanifests" ) -func newCmd() *cobra.Command { - return &cobra.Command{ +// NewCmd returns the 'generate' command configured for the new project layout. +func NewCmd() *cobra.Command { + cmd := &cobra.Command{ Use: "generate ", Short: "Invokes a specific generator", Long: `The 'operator-sdk generate' command invokes a specific generator to generate code or manifests.`, } -} -// NewCmd returns the 'generate' command configured for the new project layout. -func NewCmd() *cobra.Command { - cmd := newCmd() cmd.AddCommand( kustomize.NewCmd(), bundle.NewCmd(), @@ -41,14 +38,3 @@ func NewCmd() *cobra.Command { ) return cmd } - -// NewCmdLegacy returns the 'generate' command configured for the legacy project layout. -func NewCmdLegacy() *cobra.Command { - cmd := newCmd() - cmd.AddCommand( - newGenerateCSVCmd(), - bundle.NewCmdLegacy(), - packagemanifests.NewCmdLegacy(), - ) - return cmd -} diff --git a/cmd/operator-sdk/generate/csv.go b/cmd/operator-sdk/generate/csv.go deleted file mode 100644 index e40a5ce597..0000000000 --- a/cmd/operator-sdk/generate/csv.go +++ /dev/null @@ -1,327 +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 generate - -import ( - "fmt" - "path/filepath" - - gencatalog "github.com/operator-framework/operator-sdk/internal/generate/olm-catalog" - "github.com/operator-framework/operator-sdk/internal/util/projutil" - - "github.com/blang/semver" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -type csvCmd struct { - csvVersion string - csvChannel string - fromVersion string - operatorName string - outputDir string - deployDir string - apisDir string - crdDir string - interactivelevel projutil.InteractiveLevel - updateCRDs bool - defaultChannel bool - makeManifests bool - interactive bool -} - -func newGenerateCSVCmd() *cobra.Command { - c := &csvCmd{} - cmd := &cobra.Command{ - Use: "csv", - Short: "Generates a ClusterServiceVersion YAML file for the operator", - Long: `The 'generate csv' command generates a ClusterServiceVersion (CSV) YAML manifest -for the operator. This file is used to publish the operator to the OLM Catalog. - -A CSV semantic version is supplied via the --csv-version flag. If your operator -has already generated a CSV manifest you want to use as a base, supply its -version to --from-version. Otherwise the SDK will scaffold a new CSV manifest. - -The --make-manifests flag directs the generator to create a bundle manifests directory -intended to hold your latest operator manifests. This flag is true by default. - -More information on bundles: -https://github.com/operator-framework/operator-registry/blob/master/docs/design/operator-bundle.md#operator-bundle-overview - -Flags that change project default paths: - --deploy-dir: - The CSV's install strategy and permissions will be generated from the operator manifests - (Deployment and Role/ClusterRole) present in this directory. - - --apis-dir: - The CSV annotation comments will be parsed from the Go types under this path to - fill out metadata for owned APIs in spec.customresourcedefinitions.owned. - - --crd-dir: - The CSV's spec.customresourcedefinitions.owned field is generated from the CRD manifests - in this path. These CRD manifests are also copied over to the bundle directory if - --update-crds=true (the default). Additionally the CR manifests will be used to populate - the CSV example CRs. -`, - Example: ` ##### Generate a CSV in bundle format from default input paths ##### - $ tree pkg/apis/ deploy/ - pkg/apis/ - ├── ... - └── cache - ├── group.go - ├── v1alpha1 - ├── ... - └── memcached_types.go - deploy/ - ├── crds - │   ├── cache.example.com_memcacheds_crd.yaml - │   └── cache.example.com_v1alpha1_memcached_cr.yaml - ├── operator.yaml - ├── role.yaml - ├── role_binding.yaml - └── service_account.yaml - - $ operator-sdk generate csv --csv-version=0.0.1 - INFO[0000] Generating CSV manifest version 0.0.1 - ... - - $ tree deploy/ - deploy/ - ... - └── olm-catalog - └── memcached-operator - └── manifests - ├── cache.example.com_memcacheds_crd.yaml - └── memcached-operator.clusterserviceversion.yaml - ... - - ##### Generate a CSV in package manifests format from default input paths ##### - - $ operator-sdk generate csv --csv-version=0.0.1 --make-manifests=false --update-crds - INFO[0000] Generating CSV manifest version 0.0.1 - ... - $ tree deploy/ - deploy/ - ... - └── olm-catalog - └── memcached-operator - ├── 0.0.1 - │   ├── cache.example.com_memcacheds_crd.yaml - │   └── memcached-operator.v0.0.1.clusterserviceversion.yaml - └── memcached-operator.package.yaml - ... - - ##### Generate CSV from custom input paths ##### - $ operator-sdk generate csv --csv-version=0.0.1 --update-crds \ - --deploy-dir=config --apis-dir=api --output-dir=production - INFO[0000] Generating CSV manifest version 0.0.1 - ... - - $ tree config/ api/ production/ - config/ - ├── crds - │ ├── cache.example.com_memcacheds_crd.yaml - │ └── cache.example.com_v1alpha1_memcached_cr.yaml - ├── operator.yaml - ├── role.yaml - ├── role_binding.yaml - └── service_account.yaml - api/ - ├── ... - └── cache - | ├── group.go - | └── v1alpha1 - | ├── ... - | └── memcached_types.go - production/ - └── olm-catalog - └── memcached-operator - ├── 0.0.1 - │ ├── cache.example.com_memcacheds_crd.yaml - │ └── memcached-operator.v0.0.1.clusterserviceversion.yaml - └── memcached-operator.package.yaml -`, - - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) != 0 { - return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath()) - } - if err := c.validate(); err != nil { - return fmt.Errorf("error validating command flags: %v", err) - } - - if err := projutil.CheckProjectRoot(); err != nil { - log.Warn("Could not detect project root. Ensure that this command " + - "runs from the project root directory.") - } - - // Legacy behavior. - if !c.makeManifests && !cmd.Flags().Changed("update-crds") { - c.updateCRDs = false - } - - // Check if the user has any specific preference to enable / disable interactive prompts. - // Default behaviour is to disable the prompts. - if cmd.Flags().Changed("interactive") { - if c.interactive { - c.interactivelevel = projutil.InteractiveOnAll - } else { - c.interactivelevel = projutil.InteractiveHardOff - } - } - - if err := c.run(); err != nil { - log.Fatal(err) - } - return nil - }, - Deprecated: "use 'generate bundle' or 'generate packagemanifests' instead", - } - - cmd.Flags().StringVar(&c.csvVersion, "csv-version", "", - "Semantic version of the CSV. This flag must be set if a package manifest exists") - cmd.Flags().StringVar(&c.fromVersion, "from-version", "", - "Semantic version of an existing CSV to use as a base") - - // TODO: Allow multiple paths - // Deployment and RBAC manifests might be in different dirs e.g kubebuilder - cmd.Flags().StringVar(&c.deployDir, "deploy-dir", "deploy", - `Project relative path to root directory for operator manifests (Deployment and RBAC)`) - cmd.Flags().StringVar(&c.apisDir, "apis-dir", filepath.Join("pkg", "apis"), - `Project relative path to root directory for API type defintions`) - // TODO: Allow multiple paths - // CRD and CR manifests might be in different dirs e.g kubebuilder - cmd.Flags().StringVar(&c.crdDir, "crd-dir", "", - `Project relative path to root directory for CRD and CR manifests`) - - cmd.Flags().StringVar(&c.outputDir, "output-dir", "", - "Base directory to output generated CSV. If --make-manifests=false the resulting "+ - "CSV bundle directory will be /olm-catalog//. "+ - "If --make-manifests=true, the bundle directory will be /manifests") - cmd.Flags().StringVar(&c.operatorName, "operator-name", "", - "Operator name to use while generating CSV") - - cmd.Flags().StringVar(&c.csvChannel, "csv-channel", "", - "Channel the CSV should be registered under in the package manifest") - cmd.Flags().BoolVar(&c.defaultChannel, "default-channel", false, - "Use the channel passed to --csv-channel as the package manifests' default channel. "+ - "Only valid when --csv-channel is set") - - cmd.Flags().BoolVar(&c.updateCRDs, "update-crds", true, - "Update CRD manifests in deploy// from the default "+ - "CRDs dir deploy/crds or --crd-dir if set. If --make-manifests=false, this option "+ - "is false by default") - cmd.Flags().BoolVar(&c.makeManifests, "make-manifests", true, - "When set, the generator will create or update a CSV manifest in a 'manifests' "+ - "directory. This directory is intended to be used for your latest bundle manifests. "+ - "The default location is deploy/olm-catalog//manifests. "+ - "If --output-dir is set, the directory will be /manifests") - cmd.Flags().BoolVar(&c.interactive, "interactive", false, - "When set, will enable the interactive command prompt feature to fill the UI "+ - "metadata fields in CSV") - - return cmd -} - -func (c csvCmd) run() error { - log.Infof("Generating CSV manifest version %s", c.csvVersion) - - // Default crdDir differently if deployDir is set, since the CRD manifest dir - // is expected to be in a projects manifests (deploy) dir. - if c.crdDir == "" { - if c.deployDir != "" { - c.crdDir = filepath.Join(c.deployDir, "crds") - } else { - c.crdDir = filepath.Join("deploy", "crds") - } - } - - if c.operatorName == "" { - c.operatorName = filepath.Base(projutil.MustGetwd()) - } - - csv := gencatalog.BundleGenerator{ - OperatorName: c.operatorName, - CSVVersion: c.csvVersion, - FromVersion: c.fromVersion, - UpdateCRDs: c.updateCRDs, - MakeManifests: c.makeManifests, - DeployDir: c.deployDir, - ApisDir: c.apisDir, - CRDsDir: c.crdDir, - OutputDir: c.outputDir, - InteractivePreference: c.interactivelevel, - } - - if err := csv.Generate(); err != nil { - return fmt.Errorf("error generating CSV: %v", err) - } - - // A package manifest file is not a part of the bundle format. - if !c.makeManifests { - pkg := gencatalog.PkgGenerator{ - OperatorName: c.operatorName, - CSVVersion: c.csvVersion, - OutputDir: c.outputDir, - Channel: c.csvChannel, - ChannelIsDefault: c.defaultChannel, - } - if err := pkg.Generate(); err != nil { - return fmt.Errorf("error generating package manifest: %v", err) - } - } - - log.Info("CSV manifest generated successfully") - - return nil -} - -func (c csvCmd) validate() error { - // If a manifests directory exists, allow no versions to be set. In this case - // either a new CSV will be created or existing CSV updated in the manifests dir. - if c.csvVersion != "" { - if err := validateVersion(c.csvVersion); err != nil { - return err - } - } - if c.fromVersion != "" { - if err := validateVersion(c.fromVersion); err != nil { - return err - } - if c.csvVersion == c.fromVersion { - return fmt.Errorf("--from-version (%s) cannot equal --csv-version; set only csv-version instead", - c.fromVersion) - } - } - - if c.defaultChannel && c.csvChannel == "" { - return fmt.Errorf("default-channel can only be used if csv-channel is set") - } - - return nil -} - -func validateVersion(version string) error { - v, err := semver.Parse(version) - if err != nil { - return fmt.Errorf("%s is not a valid semantic version: %v", version, err) - } - // Ensures numerical values composing csvVersion don't contain leading 0's, - // ex. 01.01.01 - if v.String() != version { - return fmt.Errorf("provided CSV version %s contains bad values (parses to %s)", version, v) - } - return nil -} diff --git a/cmd/operator-sdk/generate/packagemanifests/cmd.go b/cmd/operator-sdk/generate/packagemanifests/cmd.go index 83fb5d515a..4113f8f3e3 100644 --- a/cmd/operator-sdk/generate/packagemanifests/cmd.go +++ b/cmd/operator-sdk/generate/packagemanifests/cmd.go @@ -23,7 +23,6 @@ import ( "github.com/spf13/pflag" kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" - "github.com/operator-framework/operator-sdk/internal/util/projutil" ) //nolint:maligned @@ -46,15 +45,6 @@ type packagemanifestsCmd struct { isDefaultChannel bool } -//nolint:maligned -type packagemanifestsCmdLegacy struct { - packagemanifestsCmd - - apisDir string - interactiveLevel projutil.InteractiveLevel - interactive bool -} - // NewCmd returns the 'packagemanifests' command configured for the new project layout. func NewCmd() *cobra.Command { c := &packagemanifestsCmd{} @@ -95,52 +85,6 @@ func NewCmd() *cobra.Command { return cmd } -// NewCmdLegacy returns the 'packagemanifests' command configured for the legacy project layout. -func NewCmdLegacy() *cobra.Command { - c := &packagemanifestsCmdLegacy{} - - cmd := &cobra.Command{ - Use: "packagemanifests", - Short: "Generates a package manifests format", - Long: longHelpLegacy, - Example: examplesLegacy, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) != 0 { - return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath()) - } - - // Check if the user has any specific preference to enable/disable interactive prompts. - // Default behaviour is to disable the prompt unless a base package does not exist. - if cmd.Flags().Changed("interactive") { - if c.interactive { - c.interactiveLevel = projutil.InteractiveOnAll - } else { - c.interactiveLevel = projutil.InteractiveHardOff - } - } - - c.setDefaults() - - if err := c.validate(); err != nil { - return fmt.Errorf("invalid command options: %v", err) - } - if err := c.run(); err != nil { - log.Fatalf("Error generating package manifests: %v", err) - } - - return nil - }, - } - - cmd.Flags().StringVar(&c.apisDir, "apis-dir", "", "Root directory for API type defintions") - cmd.Flags().BoolVar(&c.interactive, "interactive", false, "When set or no package base exists, an interactive "+ - "command prompt will be presented to accept package ClusterServiceVersion metadata") - - c.addCommonFlagsTo(cmd.Flags()) - - return cmd -} - func (c *packagemanifestsCmd) addCommonFlagsTo(fs *pflag.FlagSet) { fs.StringVar(&c.operatorName, "operator-name", "", "Name of the packaged operator") fs.StringVarP(&c.version, "version", "v", "", "Semantic version of the packaged operator") diff --git a/cmd/operator-sdk/generate/packagemanifests/packagemanifests_legacy.go b/cmd/operator-sdk/generate/packagemanifests/packagemanifests_legacy.go deleted file mode 100644 index 000afb3112..0000000000 --- a/cmd/operator-sdk/generate/packagemanifests/packagemanifests_legacy.go +++ /dev/null @@ -1,162 +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 packagemanifests - -import ( - "fmt" - "path/filepath" - - log "github.com/sirupsen/logrus" - - genutil "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/internal" - gencsv "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion" - "github.com/operator-framework/operator-sdk/internal/generate/collector" - "github.com/operator-framework/operator-sdk/internal/util/projutil" -) - -const ( - longHelpLegacy = ` -Note: while the package manifests format is not yet deprecated, the operator-framework is migrated -towards using bundles by default. Run 'operator-sdk generate bundle -h' for more information. - -Running 'generate packagemanifests' is the first step to publishing your operator to a catalog -and/or deploying it with OLM. This command generates a set of manifests in a versioned directory -and a package manifest file for your operator. It will interactively ask for UI metadata, -an important component of publishing your operator, by default unless a package for your -operator exists or you set '--interactive=false'. - -Set '--version' to supply a semantic version for your new package. This is a required flag when running -'generate packagemanifests --manifests'. - -More information on the package manifests format: -https://github.com/operator-framework/operator-registry/#manifest-format -` - - examplesLegacy = ` - # Create the package manifest file and a new package: - $ operator-sdk generate packagemanifests --version 0.0.1 - INFO[0000] Generating package manifests version 0.0.1 - - Display name for the operator (required): - > memcached-operator - ... - - # After running the above commands, you should see: - $ tree deploy/olm-catalog - deploy/olm-catalog - └── memcached-operator - ├── 0.0.1 - │ ├── cache.example.com_memcacheds_crd.yaml - │ └── memcached-operator.clusterserviceversion.yaml - └── memacached-operator.package.yaml -` -) - -// setDefaults sets defaults useful to all modes of this subcommand for legacy project layouts. -func (c *packagemanifestsCmdLegacy) setDefaults() { - if c.operatorName == "" { - c.operatorName = filepath.Base(projutil.MustGetwd()) - } - - if c.apisDir == "" { - c.apisDir = filepath.Join("pkg", "apis") - } - if c.deployDir == "" { - c.deployDir = "deploy" - } - if c.crdsDir == "" { - c.crdsDir = filepath.Join(c.deployDir, "crds") - } - - defaultBundleDir := filepath.Join(c.deployDir, "olm-catalog", c.operatorName) - if c.inputDir == "" { - c.inputDir = defaultBundleDir - } - if c.outputDir == "" { - c.outputDir = defaultBundleDir - } -} - -// validate validates c for package manifests generation for legacy project layouts. -func (c packagemanifestsCmdLegacy) validate() error { - - if err := genutil.ValidateVersion(c.version); err != nil { - return err - } - if c.fromVersion != "" { - if err := genutil.ValidateVersion(c.fromVersion); err != nil { - return err - } - } - - if c.isDefaultChannel && c.channelName == "" { - return fmt.Errorf("--default-channel can only be set if --channel is set") - } - - return nil -} - -// run generates package manifests for legacy project layouts. -func (c packagemanifestsCmdLegacy) run() error { - - if !c.quiet { - log.Infoln("Generating package manifests version", c.version) - } - - if err := c.generatePackageManifest(); err != nil { - return err - } - - col := &collector.Manifests{} - if err := col.UpdateFromDirs(c.deployDir, c.crdsDir); err != nil { - return err - } - - csvGen := gencsv.Generator{ - OperatorName: c.operatorName, - OperatorType: projutil.GetOperatorType(), - Version: c.version, - FromVersion: c.fromVersion, - Collector: col, - } - - opts := []gencsv.LegacyOption{ - gencsv.WithPackageBase(c.inputDir, c.apisDir, c.interactiveLevel), - gencsv.LegacyOption(gencsv.WithPackageWriter(c.outputDir)), - } - if err := csvGen.GenerateLegacy(opts...); err != nil { - return fmt.Errorf("error generating ClusterServiceVersion: %v", err) - } - - if c.updateCRDs { - var objs []interface{} - for _, crd := range col.V1CustomResourceDefinitions { - objs = append(objs, crd) - } - for _, crd := range col.V1beta1CustomResourceDefinitions { - objs = append(objs, crd) - } - dir := filepath.Join(c.outputDir, c.version) - if err := genutil.WriteObjectsToFilesLegacy(dir, objs...); err != nil { - return err - } - } - - if !c.quiet { - log.Infoln("Package manifests generated successfully in", c.outputDir) - } - - return nil -} diff --git a/internal/generate/olm-catalog/csv.go b/internal/generate/olm-catalog/csv.go deleted file mode 100644 index 1525a2213d..0000000000 --- a/internal/generate/olm-catalog/csv.go +++ /dev/null @@ -1,414 +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 olmcatalog - -import ( - "bytes" - "errors" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion" - "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion/bases" - "github.com/operator-framework/operator-sdk/internal/generate/collector" - "github.com/operator-framework/operator-sdk/internal/scaffold" - "github.com/operator-framework/operator-sdk/internal/util/fileutil" - "github.com/operator-framework/operator-sdk/internal/util/k8sutil" - "github.com/operator-framework/operator-sdk/internal/util/projutil" - - "github.com/blang/semver" - olmapiv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/operator-framework/operator-registry/pkg/lib/bundle" - log "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/yaml" -) - -const ( - OLMCatalogChildDir = "olm-catalog" - // OLMCatalogDir is the default location for OLM catalog directory. - OLMCatalogDir = scaffold.DeployDir + string(filepath.Separator) + OLMCatalogChildDir - csvYamlFileExt = ".clusterserviceversion.yaml" -) - -type BundleGenerator struct { - // OperatorName is the operator's name, ex. app-operator - OperatorName string - OutputDir string - FromVersion string - // csvVersion is the CSV current version. - CSVVersion string - // These directories specify where to retrieve manifests from. - DeployDir, ApisDir, CRDsDir string - // InteractivePreference refers to the user preference to enable/disable - // interactive prompts. - InteractivePreference projutil.InteractiveLevel - // updateCRDs directs the generator to also add CustomResourceDefinition - // manifests to the bundle. - UpdateCRDs bool - // makeManifests directs the generator to use 'manifests' as the bundle - // dir name. - MakeManifests bool - // noUpdate is for testing the generator's update capabilities. - noUpdate bool - // fromBundleDir is set if the generator needs to update from - // an existing CSV bundle directory - fromBundleDir string - // toBundleDir is the bundle directory filepath where the CSV will be generated - // This is set according to the generator's OutputDir - toBundleDir string -} - -// getBundleDirs gets directory names of the new bundle and, if it exists, -// a bundle to update the new bundle from. -// getBundleDirs is aware of 'manifests' directories and will update the -// new bundle from an existing 'manifests' directory if it exists. -func getBundleDirs(operatorName, csvVersion, outputDir, deployDir string) (toBundleDir, fromBundleDir string) { - - defaultOperatorDir := filepath.Join(deployDir, OLMCatalogChildDir, operatorName) - - // If outputDir was set, first check this dir for existing bundles. Otherwise - // check the default location. - if outputDir == "" { - toBundleDir = filepath.Join(defaultOperatorDir, bundle.ManifestsDir) - } else { - toBundleDir = filepath.Join(outputDir, bundle.ManifestsDir) - outputOperatorDir := filepath.Join(outputDir, OLMCatalogChildDir, operatorName) - switch { - case isBundleDirExist(outputOperatorDir, bundle.ManifestsDir): - fromBundleDir = filepath.Join(outputOperatorDir, bundle.ManifestsDir) - case isExist(toBundleDir): - fromBundleDir = toBundleDir - case isBundleDirExist(outputOperatorDir, csvVersion): - // Updating an existing CSV version - fromBundleDir = filepath.Join(outputOperatorDir, csvVersion) - } - if fromBundleDir != "" { - return toBundleDir, fromBundleDir - } - } - - switch { - case isBundleDirExist(defaultOperatorDir, bundle.ManifestsDir): - fromBundleDir = filepath.Join(defaultOperatorDir, bundle.ManifestsDir) - case isExist(toBundleDir): - fromBundleDir = toBundleDir - case isBundleDirExist(defaultOperatorDir, csvVersion): - // Updating an existing CSV version - fromBundleDir = filepath.Join(defaultOperatorDir, csvVersion) - } - - return toBundleDir, fromBundleDir -} - -// getBundleDirsLegacy gets directory names of the new bundle and, if it -// exists, a bundle to update the new bundle from. -// getBundleDirsLegacy assumes a 'manifests' directory does not exist and -// will not update the new bundle from an existing 'manifests' directory. -func getBundleDirsLegacy(operatorName, csvVersion, fromVersion, outputDir, - deployDir string) (toBundleDir, fromBundleDir string) { - - defaultOperatorDir := filepath.Join(deployDir, OLMCatalogChildDir, operatorName) - - // If outputDir was set, first check this dir for existing bundles. Otherwise - // check the default location. - if outputDir == "" { - toBundleDir = filepath.Join(defaultOperatorDir, csvVersion) - } else { - outputOperatorDir := filepath.Join(outputDir, OLMCatalogChildDir, operatorName) - toBundleDir = filepath.Join(outputOperatorDir, csvVersion) - switch { - case isBundleDirExist(outputOperatorDir, fromVersion): - // Upgrading a new CSV from previous CSV version - fromBundleDir = filepath.Join(outputOperatorDir, fromVersion) - case isBundleDirExist(outputOperatorDir, csvVersion): - // Updating an existing CSV version - fromBundleDir = filepath.Join(outputOperatorDir, csvVersion) - } - if fromBundleDir != "" { - return toBundleDir, fromBundleDir - } - } - - switch { - case isBundleDirExist(defaultOperatorDir, fromVersion): - // Upgrading a new CSV from previous CSV version - fromBundleDir = filepath.Join(defaultOperatorDir, fromVersion) - case isBundleDirExist(defaultOperatorDir, csvVersion): - // Updating an existing CSV version - fromBundleDir = filepath.Join(defaultOperatorDir, csvVersion) - } - - return toBundleDir, fromBundleDir -} - -func (g *BundleGenerator) setDefaults() { - if g.DeployDir == "" { - g.DeployDir = scaffold.DeployDir - } - if g.ApisDir == "" { - g.ApisDir = scaffold.ApisDir - } - if g.CRDsDir == "" { - g.CRDsDir = filepath.Join(g.DeployDir, "crds") - } - - if g.MakeManifests { - g.toBundleDir, g.fromBundleDir = getBundleDirs(g.OperatorName, g.CSVVersion, - g.OutputDir, g.DeployDir) - } else { - g.toBundleDir, g.fromBundleDir = getBundleDirsLegacy(g.OperatorName, g.CSVVersion, - g.FromVersion, g.OutputDir, g.DeployDir) - } -} - -// Generate allows a CSV to be written by marshalling -// olmapiv1alpha1.ClusterServiceVersion instead of writing to a template. -func (g BundleGenerator) Generate() error { - g.setDefaults() - - fileMap, err := g.generateCSV() - if err != nil { - return err - } - if len(fileMap) == 0 { - return errors.New("error generating CSV manifest: no generated file found") - } - - // Write CRD's to the new or updated CSV package dir. - if g.UpdateCRDs { - if err := addCustomResourceDefinitionsToFileSet(g.CRDsDir, fileMap); err != nil { - return fmt.Errorf("error collecting CustomResourceDefinitions from %s: %v", g.CRDsDir, err) - } - } - - if err := os.MkdirAll(g.toBundleDir, 0755); err != nil { - return fmt.Errorf("error mkdir %s: %v", g.toBundleDir, err) - } - - for fileName, b := range fileMap { - path := filepath.Join(g.toBundleDir, fileName) - log.Debugf("CSV generator writing %s", path) - if err := ioutil.WriteFile(path, b, fileutil.DefaultFileMode); err != nil { - return fmt.Errorf("error writing bundle file: %v", err) - } - } - return nil -} - -func getCSVName(name, version string) string { - return name + ".v" + version -} - -func getCSVFileName(name string) string { - return strings.ToLower(name) + csvYamlFileExt -} - -func getCSVFileNameLegacy(name, version string) string { - return getCSVName(strings.ToLower(name), version) + csvYamlFileExt -} - -func (g BundleGenerator) generateCSV() (fileMap map[string][]byte, err error) { - - csv, err := g.getBase() - if err != nil { - return nil, err - } - - if err = g.updateCSVVersions(csv); err != nil { - return nil, err - } - - if err = g.updateCSVFromManifests(csv); err != nil { - return nil, err - } - - path := "" - if g.MakeManifests { - path = getCSVFileName(g.OperatorName) - } else { - path = getCSVFileNameLegacy(g.OperatorName, g.CSVVersion) - } - // TODO(estroz): replace with CSV validator from API library. - if fields := getEmptyRequiredCSVFields(csv); len(fields) != 0 { - if g.fromBundleDir != "" { - // An existing csv should have several required fields populated. - log.Warnf("Required csv fields not filled in file %s:%s\n", path, joinFields(fields)) - } else { - // A new csv won't have several required fields populated. - // Report required fields to user informationally. - log.Infof("Fill in the following required fields in file %s:%s\n", path, joinFields(fields)) - } - } - - b, err := k8sutil.GetObjectBytes(csv, yaml.Marshal) - if err != nil { - return nil, err - } - fileMap = map[string][]byte{ - path: b, - } - return fileMap, nil -} - -// getBase either reads an existing CSV from fromBundleDir or creates a new one. -func (g BundleGenerator) getBase() (*olmapiv1alpha1.ClusterServiceVersion, error) { - v1crds, v1beta1crds, err := k8sutil.GetCustomResourceDefinitions(g.CRDsDir) - if err != nil { - return nil, err - } - var gvks []schema.GroupVersionKind - v1crdGVKs := k8sutil.GVKsForV1CustomResourceDefinitions(v1crds...) - gvks = append(gvks, v1crdGVKs...) - v1beta1crdGVKs := k8sutil.GVKsForV1beta1CustomResourceDefinitions(v1beta1crds...) - gvks = append(gvks, v1beta1crdGVKs...) - - b := bases.ClusterServiceVersion{ - OperatorName: g.OperatorName, - OperatorType: projutil.GetOperatorType(), - APIsDir: g.ApisDir, - GVKs: gvks, - } - - if g.fromBundleDir != "" && !g.noUpdate { - if g.MakeManifests { - b.BasePath = filepath.Join(g.fromBundleDir, getCSVFileName(g.OperatorName)) - } else { - if g.FromVersion == "" { - b.BasePath = filepath.Join(g.fromBundleDir, getCSVFileNameLegacy(g.OperatorName, g.CSVVersion)) - } else { - b.BasePath = filepath.Join(g.fromBundleDir, getCSVFileNameLegacy(g.OperatorName, g.FromVersion)) - } - } - } - - // Check if user explicitly wants an interactive prompt or has no preference. - if (g.InteractivePreference == projutil.InteractiveSoftOff && isNotExist(b.BasePath)) || - g.InteractivePreference == projutil.InteractiveOnAll { - b.Interactive = true - } - - return b.GetBase() -} - -// TODO: replace with validation library. -func getEmptyRequiredCSVFields(csv *olmapiv1alpha1.ClusterServiceVersion) (fields []string) { - // Metadata - if csv.TypeMeta.APIVersion != olmapiv1alpha1.ClusterServiceVersionAPIVersion { - fields = append(fields, "apiVersion") - } - if csv.TypeMeta.Kind != olmapiv1alpha1.ClusterServiceVersionKind { - fields = append(fields, "kind") - } - if csv.ObjectMeta.Name == "" { - fields = append(fields, "metadata.name") - } - // Spec fields - if csv.Spec.Version.String() == "" { - fields = append(fields, "spec.version") - } - if csv.Spec.DisplayName == "" { - fields = append(fields, "spec.displayName") - } - if csv.Spec.Description == "" { - fields = append(fields, "spec.description") - } - if len(csv.Spec.Keywords) == 0 || len(csv.Spec.Keywords[0]) == 0 { - fields = append(fields, "spec.keywords") - } - if len(csv.Spec.Maintainers) == 0 { - fields = append(fields, "spec.maintainers") - } - if csv.Spec.Provider == (olmapiv1alpha1.AppLink{}) { - fields = append(fields, "spec.provider") - } - if csv.Spec.Maturity == "" { - fields = append(fields, "spec.maturity") - } - - return fields -} - -// updateCSVVersions updates csv's version and data involving the version, -// ex. ObjectMeta.Name, and place the old version in the `replaces` object, -// if there is an old version to replace. -func (g BundleGenerator) updateCSVVersions(csv *olmapiv1alpha1.ClusterServiceVersion) (err error) { - - oldVer, newVer := csv.Spec.Version.String(), g.CSVVersion - newCSVName := getCSVName(g.OperatorName, newVer) - oldCSVName := getCSVName(g.OperatorName, oldVer) - - // If the new version is empty, either because a CSV is only being updated or - // a base was generated, no update is needed. - if newVer == "0.0.0" || newVer == "" || newVer == oldVer { - return nil - } - if oldVer != "0.0.0" { - csv.Spec.Replaces = oldCSVName - } - - csv.SetName(newCSVName) - csv.Spec.Version.Version, err = semver.Parse(newVer) - return err -} - -// updateCSVFromManifests gathers relevant data from generated and -// user-defined manifests and updates csv. -func (g BundleGenerator) updateCSVFromManifests(csv *olmapiv1alpha1.ClusterServiceVersion) (err error) { - // Collect all manifests in paths. - col := &collector.Manifests{} - if err := col.UpdateFromDirs(g.DeployDir, g.CRDsDir); err != nil { - return err - } - - // Apply manifests to the CSV object. - if err = clusterserviceversion.ApplyTo(col, csv); err != nil { - return fmt.Errorf("error building CSV: %v", err) - } - - // Ensure WATCH_NAMESPACE is set. - if err = checkWatchNamespaces(csv); err != nil { - return fmt.Errorf("error checking for WATCH_NAMESPACE: %v", err) - } - - return nil -} - -// OLM places the set of target namespaces for the operator in -// "metadata.annotations['olm.targetNamespaces']". This value should be -// referenced in either: -// - The DeploymentSpec's pod spec WATCH_NAMESPACE env variable. -// - Some other DeploymentSpec pod spec field. -func checkWatchNamespaces(csv *olmapiv1alpha1.ClusterServiceVersion) error { - envVarValue := clusterserviceversion.TargetNamespacesRef - for _, dep := range csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs { - // Make sure "olm.targetNamespaces" is referenced somewhere in dep, - // and emit a warning of not. - b, err := dep.Spec.Template.Marshal() - if err != nil { - return err - } - if !bytes.Contains(b, []byte(envVarValue)) { - log.Warnf("No WATCH_NAMESPACE environment variable nor reference to %q "+ - "detected in operator Deployment %s. For compatibility between OLM and a "+ - "namespaced operator, your operator must watch namespaces defined in %q", - envVarValue, dep.Name, envVarValue) - } - } - return nil -} diff --git a/internal/generate/olm-catalog/csv_go_test.go b/internal/generate/olm-catalog/csv_go_test.go deleted file mode 100644 index a2962be75e..0000000000 --- a/internal/generate/olm-catalog/csv_go_test.go +++ /dev/null @@ -1,474 +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 olmcatalog - -import ( - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "testing" - - "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion/bases" - "github.com/operator-framework/operator-sdk/internal/util/fileutil" - "github.com/operator-framework/operator-sdk/internal/util/k8sutil" - "github.com/operator-framework/operator-sdk/internal/util/projutil" - - "github.com/blang/semver" - "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/stretchr/testify/assert" - "sigs.k8s.io/yaml" -) - -const ( - testProjectName = "memcached-operator" - - // Dir names/CSV versions - csvVersion = "0.0.3" - fromVersion = "0.0.2" - notExistVersion = "1.0.0" - noUpdateDir = "noupdate" -) - -var ( - testGoDataDir = filepath.Join("..", "testdata", "go") - testNonStandardLayoutDataDir = filepath.Join("..", "testdata", "non-standard-layout") -) - -func chDirWithCleanup(t *testing.T, dataDir string) func() { - wd, err := os.Getwd() - if err != nil { - t.Fatal(err) - } - if err := os.Chdir(dataDir); err != nil { - t.Fatal(err) - } - chDirCleanupFunc := func() { - if err := os.Chdir(wd); err != nil { - t.Fatal(err) - } - } - return chDirCleanupFunc -} - -func mkTempDirWithCleanup(t *testing.T, prefix string) (dir string, f func()) { - var err error - if dir, err = ioutil.TempDir("", prefix); err != nil { - t.Fatalf("Failed to create tmp dir: %v", err) - } - f = func() { - if err := os.RemoveAll(dir); err != nil { - // Not a test failure since files in /tmp will eventually get deleted - t.Logf("Failed to remove tmp dir %s: %v", dir, err) - } - } - return -} - -func readFile(t *testing.T, path string) []byte { - b, err := ioutil.ReadFile(path) - if err != nil { - t.Fatalf("Failed to read testdata file: %v", err) - } - return b -} - -// TODO: Change to table driven subtests to test out different Inputs/Output for the generator -func TestGoCSVNewWithInputsToOutput(t *testing.T) { - // Change directory to project root so the test cases can form the correct pkg imports - cleanupFunc := chDirWithCleanup(t, testNonStandardLayoutDataDir) - defer cleanupFunc() - - // Temporary output dir for generating catalog bundle - outputDir, rmDirFunc := mkTempDirWithCleanup(t, t.Name()+"-output-catalog") - defer rmDirFunc() - - csvVersion := "0.0.1" - g := BundleGenerator{ - OperatorName: testProjectName, - DeployDir: "config", - ApisDir: "api", - CRDsDir: filepath.Join("config", "crds"), - OutputDir: outputDir, - CSVVersion: csvVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: false, - InteractivePreference: projutil.InteractiveHardOff, - } - - g.noUpdate = true - - if err := g.Generate(); err != nil { - t.Fatalf("Failed to execute CSV generator: %v", err) - } - - csvFileName := getCSVFileNameLegacy(testProjectName, csvVersion) - - // Read expected CSV - expBundleDir := filepath.Join("expected-catalog", OLMCatalogChildDir, testProjectName, csvVersion) - csvExp := string(readFile(t, filepath.Join(expBundleDir, csvFileName))) - - // Read generated CSV from outputDir path - outputBundleDir := filepath.Join(outputDir, OLMCatalogChildDir, testProjectName, csvVersion) - csvOutput := string(readFile(t, filepath.Join(outputBundleDir, csvFileName))) - - assert.Equal(t, csvExp, csvOutput) -} - -func TestGoCSVUpgradeWithInputsToOutput(t *testing.T) { - // Change directory to project root so the test cases can form the correct pkg imports - cleanupFunc := chDirWithCleanup(t, testNonStandardLayoutDataDir) - defer cleanupFunc() - - // Temporary output dir for generating catalog bundle - outputDir, rmDirFunc := mkTempDirWithCleanup(t, t.Name()+"-output-catalog") - defer rmDirFunc() - - fromVersion := "0.0.3" - csvVersion := "0.0.4" - - // Copy over expected fromVersion CSV bundle directory to the output dir - // so the test can upgrade from it - outputFromCSVDir := filepath.Join(outputDir, OLMCatalogChildDir, testProjectName) - if err := os.MkdirAll(outputFromCSVDir, fileutil.DefaultDirFileMode); err != nil { - t.Fatalf("Failed to create CSV bundle dir (%s) for fromVersion (%s): %v", outputFromCSVDir, fromVersion, err) - } - expCatalogDir := filepath.Join("expected-catalog", OLMCatalogChildDir) - expFromCSVDir := filepath.Join(expCatalogDir, testProjectName, fromVersion) - cmd := exec.Command("cp", "-r", expFromCSVDir, outputFromCSVDir) - t.Logf("Copying expected fromVersion CSV manifest dir %#v", cmd.Args) - if err := projutil.ExecCmd(cmd); err != nil { - t.Fatalf("Failed to copy expected CSV bundle dir (%s) to output dir (%s): %v", expFromCSVDir, outputFromCSVDir, err) - } - - g := BundleGenerator{ - OperatorName: testProjectName, - DeployDir: "config", - ApisDir: "api", - CRDsDir: filepath.Join("config", "crds"), - OutputDir: outputDir, - CSVVersion: csvVersion, - FromVersion: fromVersion, - UpdateCRDs: false, - MakeManifests: false, - InteractivePreference: projutil.InteractiveHardOff, - } - - if err := g.Generate(); err != nil { - t.Fatalf("Failed to execute CSV generator: %v", err) - } - csvFileName := getCSVFileNameLegacy(testProjectName, csvVersion) - - // Read expected CSV - expCsvFile := filepath.Join(expCatalogDir, testProjectName, csvVersion, csvFileName) - csvExp := string(readFile(t, expCsvFile)) - - // Read generated CSV from outputDir path - csvOutputFile := filepath.Join(outputFromCSVDir, csvVersion, csvFileName) - csvOutput := string(readFile(t, csvOutputFile)) - - assert.Equal(t, csvExp, csvOutput) -} - -func TestGoCSVNew(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - g := BundleGenerator{ - InteractivePreference: projutil.InteractiveHardOff, - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: csvVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: false, - } - g.noUpdate = true - g.setDefaults() - fileMap, err := g.generateCSV() - if err != nil { - t.Fatalf("Failed to execute CSV generator: %v", err) - } - - csvExpFile := getCSVFileNameLegacy(testProjectName, csvVersion) - csvExpBytes := readFile(t, filepath.Join(OLMCatalogDir, testProjectName, noUpdateDir, csvExpFile)) - if b, ok := fileMap[csvExpFile]; !ok { - t.Errorf("Failed to generate CSV for version %s", csvVersion) - } else { - assert.Equal(t, string(csvExpBytes), string(b)) - } -} - -func TestGoCSVUpdate(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - g := BundleGenerator{ - InteractivePreference: projutil.InteractiveHardOff, - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: csvVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: false, - } - g.setDefaults() - fileMap, err := g.generateCSV() - if err != nil { - t.Fatalf("Failed to execute CSV generator: %v", err) - } - - csvExpFile := getCSVFileNameLegacy(testProjectName, csvVersion) - csvExpBytes := readFile(t, filepath.Join(OLMCatalogDir, testProjectName, csvVersion, csvExpFile)) - if b, ok := fileMap[csvExpFile]; !ok { - t.Errorf("Failed to generate CSV for version %s", csvVersion) - } else { - assert.Equal(t, string(csvExpBytes), string(b)) - } -} - -func TestGoCSVUpgrade(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - g := BundleGenerator{ - InteractivePreference: projutil.InteractiveHardOff, - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: csvVersion, - FromVersion: fromVersion, - UpdateCRDs: false, - MakeManifests: false, - } - g.setDefaults() - fileMap, err := g.generateCSV() - if err != nil { - t.Fatalf("Failed to execute CSV generator: %v", err) - } - - csvExpFile := getCSVFileNameLegacy(testProjectName, csvVersion) - csvExpBytes := readFile(t, filepath.Join(OLMCatalogDir, testProjectName, csvVersion, csvExpFile)) - if b, ok := fileMap[csvExpFile]; !ok { - t.Errorf("Failed to generate CSV for version %s", csvVersion) - } else { - assert.Equal(t, string(csvExpBytes), string(b)) - } -} - -func TestGoCSVNewManifests(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - g := BundleGenerator{ - InteractivePreference: projutil.InteractiveHardOff, - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: csvVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: true, - } - g.noUpdate = true - g.setDefaults() - fileMap, err := g.generateCSV() - if err != nil { - t.Fatalf("Failed to execute CSV generator: %v", err) - } - - csvExpFile := getCSVFileNameLegacy(testProjectName, csvVersion) - csvExpBytes := readFile(t, filepath.Join(OLMCatalogDir, testProjectName, noUpdateDir, csvExpFile)) - if b, ok := fileMap[getCSVFileName(testProjectName)]; !ok { - t.Errorf("Failed to generate CSV for version %s", csvVersion) - } else { - assert.Equal(t, string(csvExpBytes), string(b)) - } -} - -func TestGoCSVUpdateManifests(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - g := BundleGenerator{ - InteractivePreference: projutil.InteractiveHardOff, - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: csvVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: true, - } - g.setDefaults() - fileMap, err := g.generateCSV() - if err != nil { - t.Fatalf("Failed to execute CSV generator: %v", err) - } - - csvExpFile := getCSVFileNameLegacy(testProjectName, csvVersion) - csvExpBytes := readFile(t, filepath.Join(OLMCatalogDir, testProjectName, csvVersion, csvExpFile)) - if b, ok := fileMap[getCSVFileName(testProjectName)]; !ok { - t.Errorf("Failed to generate CSV for version %s", csvVersion) - } else { - assert.Equal(t, string(csvExpBytes), string(b)) - } -} - -func TestGoCSVNewWithInvalidDeployDir(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - g := BundleGenerator{ - InteractivePreference: projutil.InteractiveHardOff, - OperatorName: testProjectName, - DeployDir: "notExist", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: "notExist", - OutputDir: "deploy", - CSVVersion: notExistVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: false, - } - - g.setDefaults() - _, err := g.generateCSV() - if err == nil { - t.Fatalf("Failed to get error for running CSV generator"+ - "on non-existent manifests directory: %s", g.DeployDir) - } -} - -func TestGoCSVNewWithEmptyDeployDir(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - g := BundleGenerator{ - InteractivePreference: projutil.InteractiveHardOff, - OperatorName: testProjectName, - DeployDir: "emptydir", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: "emptydir", - OutputDir: "emptydir", - CSVVersion: notExistVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: false, - } - - g.setDefaults() - fileMap, err := g.generateCSV() - if err != nil { - t.Fatalf("Failed to execute CSV generator: %v", err) - } - - // Create an empty CSV. - b := bases.ClusterServiceVersion{ - OperatorName: testProjectName, - OperatorType: projutil.OperatorTypeGo, - } - csv, err := b.GetBase() - if err != nil { - t.Fatal(err) - } - if err := g.updateCSVVersions(csv); err != nil { - t.Fatal(err) - } - csv.Spec.InstallStrategy.StrategyName = v1alpha1.InstallStrategyNameDeployment - csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs = []v1alpha1.StrategyDeploymentSpec{} - - csvExpBytes, err := k8sutil.GetObjectBytes(csv, yaml.Marshal) - if err != nil { - t.Fatal(err) - } - csvExpFile := getCSVFileNameLegacy(testProjectName, notExistVersion) - if b, ok := fileMap[csvExpFile]; !ok { - t.Errorf("Failed to generate CSV for version %s", notExistVersion) - } else { - assert.Equal(t, string(csvExpBytes), string(b)) - } -} - -func TestUpdateCSVVersion(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - csv, err := getCSVFromDir(filepath.Join(OLMCatalogDir, testProjectName, fromVersion)) - if err != nil { - t.Fatal("Failed to get new CSV") - } - - g := BundleGenerator{ - InteractivePreference: projutil.InteractiveHardOff, - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: csvVersion, - FromVersion: fromVersion, - UpdateCRDs: false, - MakeManifests: false, - } - g.setDefaults() - if err := g.updateCSVVersions(csv); err != nil { - t.Fatalf("Failed to update csv with version %s: (%v)", csvVersion, err) - } - - wantedSemver, err := semver.Parse(csvVersion) - if err != nil { - t.Errorf("Failed to parse %s: %v", csvVersion, err) - } - if !csv.Spec.Version.Equals(wantedSemver) { - t.Errorf("Wanted csv version %v, got %v", wantedSemver, csv.Spec.Version) - } - wantedName := getCSVName(testProjectName, csvVersion) - if csv.ObjectMeta.Name != wantedName { - t.Errorf("Wanted csv name %s, got %s", wantedName, csv.ObjectMeta.Name) - } - - csvDepSpecs := csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs - if len(csvDepSpecs) != 1 { - t.Fatal("No deployment specs in CSV") - } - csvPodImage := csvDepSpecs[0].Spec.Template.Spec.Containers[0].Image - if len(csvDepSpecs[0].Spec.Template.Spec.Containers) != 1 { - t.Fatal("No containers in CSV deployment spec") - } - // updateCSVVersions should not update podspec image. - wantedImage := "quay.io/example/memcached-operator:v0.0.2" - if csvPodImage != wantedImage { - t.Errorf("Podspec image changed from %s to %s", wantedImage, csvPodImage) - } - - wantedReplaces := getCSVName(testProjectName, fromVersion) - if csv.Spec.Replaces != wantedReplaces { - t.Errorf("Wanted csv replaces %s, got %s", wantedReplaces, csv.Spec.Replaces) - } -} diff --git a/internal/generate/olm-catalog/csv_util.go b/internal/generate/olm-catalog/csv_util.go deleted file mode 100644 index d43e657af2..0000000000 --- a/internal/generate/olm-catalog/csv_util.go +++ /dev/null @@ -1,143 +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 olmcatalog - -import ( - "bytes" - "errors" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/operator-framework/operator-sdk/internal/util/k8sutil" - - olmapiv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - log "github.com/sirupsen/logrus" - "sigs.k8s.io/yaml" -) - -// isBundleDirExist returns true if "parentDir/version" exists on disk. -func isBundleDirExist(parentDir, version string) bool { - // Ensure full path is constructed. - return version != "" && isExist(filepath.Join(parentDir, version)) -} - -func isNotExist(path string) bool { - _, err := os.Stat(path) - return err != nil && errors.Is(err, os.ErrNotExist) -} - -func isExist(path string) bool { - _, err := os.Stat(path) - return err == nil || errors.Is(err, os.ErrExist) -} - -// addCustomResourceDefinitionsToFileSet adds all CustomResourceDefinition -// manifests in dir to fileMap with file name keys. -func addCustomResourceDefinitionsToFileSet(dir string, fileMap map[string][]byte) error { - infos, err := ioutil.ReadDir(dir) - if err != nil { - return err - } - - for _, info := range infos { - if info.IsDir() { - continue - } - - fromPath := filepath.Join(dir, info.Name()) - b, err := ioutil.ReadFile(fromPath) - if err != nil { - return fmt.Errorf("error reading manifest %s: %v", fromPath, err) - } - - scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) - manifests := []byte{} - for scanner.Scan() { - manifest := scanner.Bytes() - typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) - if err != nil { - log.Debugf("Skipping non-Object manifest %s: %v", fromPath, err) - continue - } - if typeMeta.Kind == "CustomResourceDefinition" { - manifests = k8sutil.CombineManifests(manifests, b) - } - } - if err = scanner.Err(); err != nil { - return err - } - - if len(manifests) != 0 { - fileMap[info.Name()] = manifests - } - } - - return nil -} - -// getCSVFromDir returns the ClusterServiceVersion manifest in dir. If no -// manifest is found, getCSVFromDir returns an error. -func getCSVFromDir(dir string) (*olmapiv1alpha1.ClusterServiceVersion, error) { - infos, err := ioutil.ReadDir(dir) - if err != nil { - return nil, err - } - - for _, info := range infos { - // Only read manifest from files, not directories - if info.IsDir() { - continue - } - - path := filepath.Join(dir, info.Name()) - b, err := ioutil.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("error reading manifest %s: %v", path, err) - } - - scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) - for scanner.Scan() { - manifest := scanner.Bytes() - typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) - if err != nil { - log.Debugf("Skipping non-Object manifest %s: %v", path, err) - continue - } - if typeMeta.Kind == olmapiv1alpha1.ClusterServiceVersionKind { - csv := &olmapiv1alpha1.ClusterServiceVersion{} - if err := yaml.Unmarshal(manifest, csv); err != nil { - return nil, fmt.Errorf("error unmarshalling ClusterServiceVersion from manifest %s: %v", path, err) - } - return csv, nil - } - } - if err = scanner.Err(); err != nil { - return nil, fmt.Errorf("error scanning manifest %s: %v", path, err) - } - } - - return nil, fmt.Errorf("no CSV manifest in %s", dir) -} - -func joinFields(fields []string) string { - sb := &strings.Builder{} - for _, f := range fields { - sb.WriteString("\n\t" + f) - } - return sb.String() -} diff --git a/internal/generate/olm-catalog/package_manifest.go b/internal/generate/olm-catalog/package_manifest.go deleted file mode 100644 index 55cdc61b65..0000000000 --- a/internal/generate/olm-catalog/package_manifest.go +++ /dev/null @@ -1,211 +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 olmcatalog - -import ( - "errors" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "sort" - "strings" - - apimanifests "github.com/operator-framework/api/pkg/manifests" - "github.com/operator-framework/api/pkg/validation" - log "github.com/sirupsen/logrus" - "sigs.k8s.io/yaml" - - "github.com/operator-framework/operator-sdk/internal/scaffold" - "github.com/operator-framework/operator-sdk/internal/util/fileutil" -) - -const ( - packageManifestFileExt = ".package.yaml" -) - -type PkgGenerator struct { - OperatorName string - OutputDir string - // csvVersion is the version of the CSV being updated. - CSVVersion string - // channel is csvVersion's package manifest channel. If a new package - // manifest is generated, this channel will be the manifest default. - Channel string - // If channelIsDefault is true, channel will be the package manifests' - // default channel. - ChannelIsDefault bool - // PackageManifest file name - fileName string -} - -// getPkgFileName will return the name of the PackageManifestFile -func getPkgFileName(operatorName string) string { - return strings.ToLower(operatorName) + packageManifestFileExt -} - -func (g *PkgGenerator) setDefaults() { - // The olm-catalog directory location depends on where the output directory is set. - if g.OutputDir == "" { - g.OutputDir = scaffold.DeployDir - } - g.fileName = getPkgFileName(g.OperatorName) -} - -func (g PkgGenerator) Generate() error { - g.setDefaults() - fileMap, err := g.generate() - if err != nil { - return err - } - if len(fileMap) == 0 { - return errors.New("error generating package manifest: no generated file found") - } - pkgManifestOutputDir := filepath.Join(g.OutputDir, OLMCatalogChildDir, g.OperatorName) - if err = os.MkdirAll(pkgManifestOutputDir, 0755); err != nil { - return fmt.Errorf("error mkdir %s: %v", pkgManifestOutputDir, err) - } - for fileName, b := range fileMap { - path := filepath.Join(pkgManifestOutputDir, fileName) - log.Debugf("Package manifest generator writing %s", path) - if err = ioutil.WriteFile(path, b, fileutil.DefaultFileMode); err != nil { - return err - } - } - return nil -} - -// generate either reads an existing package manifest or creates a new -// manifest and modifies it based on values set in s. -func (g PkgGenerator) generate() (map[string][]byte, error) { - pkg, err := g.buildPackageManifest() - if err != nil { - return nil, err - } - - g.setChannels(&pkg) - sortChannelsByName(&pkg) - - if err := validatePackageManifest(&pkg); err != nil { - return nil, err - } - - b, err := yaml.Marshal(pkg) - if err != nil { - return nil, err - } - - fileMap := map[string][]byte{ - g.fileName: b, - } - return fileMap, nil -} - -// buildPackageManifest will create a apimanifests.PackageManifest from scratch, or reads -// an existing one if found at the expected path. -func (g PkgGenerator) buildPackageManifest() (apimanifests.PackageManifest, error) { - pkgManifestOutputDir := filepath.Join(g.OutputDir, OLMCatalogChildDir, g.OperatorName) - path := filepath.Join(pkgManifestOutputDir, g.fileName) - pkg := apimanifests.PackageManifest{} - if isExist(path) { - b, err := ioutil.ReadFile(path) - if err != nil { - return pkg, fmt.Errorf("failed to read package manifest %s: %v", path, err) - } - if err = yaml.Unmarshal(b, &pkg); err != nil { - return pkg, fmt.Errorf("failed to unmarshal package manifest %s: %v", path, err) - } - } else { - pkg = newPackageManifest(g.OperatorName, g.Channel, g.CSVVersion) - } - return pkg, nil -} - -// sortChannelsByName sorts pkg.Channels by each element's name. -// NOTE: sorting makes the channel order always consistent when appending new channels -func sortChannelsByName(pkg *apimanifests.PackageManifest) { - sort.Slice(pkg.Channels, func(i int, j int) bool { - return pkg.Channels[i].Name < pkg.Channels[j].Name - }) -} - -// validatePackageManifest will validate pkg using the api validation library. -// More info: https://github.com/operator-framework/api -func validatePackageManifest(pkg *apimanifests.PackageManifest) error { - if pkg == nil { - return errors.New("generated package manifest is empty") - } - results := validation.PackageManifestValidator.Validate(pkg) - for _, r := range results { - if r.HasError() { - var errorMsgs strings.Builder - for _, e := range r.Errors { - errorMsgs.WriteString(fmt.Sprintf("%s\n", e.Error())) - } - return fmt.Errorf("error validating package manifest: %s", errorMsgs.String()) - } - for _, w := range r.Warnings { - log.Warnf("Package manifest validation warning: type [%s] %s", w.Type, w.Detail) - } - } - return nil -} - -// newPackageManifest will return the apimanifests.PackageManifest populated. -func newPackageManifest(operatorName, channelName, version string) apimanifests.PackageManifest { - // Take the current CSV version to be the "alpha" channel, as an operator - // should only be designated anything more stable than "alpha" by a human. - channel := "alpha" - if channelName != "" { - channel = channelName - } - lowerOperatorName := strings.ToLower(operatorName) - pkg := apimanifests.PackageManifest{ - PackageName: lowerOperatorName, - Channels: []apimanifests.PackageChannel{ - {Name: channel, CurrentCSVName: getCSVName(lowerOperatorName, version)}, - }, - DefaultChannelName: channel, - } - return pkg -} - -// setChannels checks for duplicate channels in pkg and sets the default -// channel if possible. -func (g PkgGenerator) setChannels(pkg *apimanifests.PackageManifest) { - if g.Channel != "" { - channelIdx := -1 - for i, channel := range pkg.Channels { - if channel.Name == g.Channel { - channelIdx = i - break - } - } - lowerOperatorName := strings.ToLower(g.OperatorName) - if channelIdx == -1 { - pkg.Channels = append(pkg.Channels, apimanifests.PackageChannel{ - Name: g.Channel, - CurrentCSVName: getCSVName(lowerOperatorName, g.CSVVersion), - }) - } else { - pkg.Channels[channelIdx].CurrentCSVName = getCSVName(lowerOperatorName, g.CSVVersion) - } - // Use g.Channel as the default channel if caller has specified it as the - // default. - if g.ChannelIsDefault { - pkg.DefaultChannelName = g.Channel - } - } -} diff --git a/internal/generate/olm-catalog/package_manifest_test.go b/internal/generate/olm-catalog/package_manifest_test.go deleted file mode 100644 index 8bc07c44e5..0000000000 --- a/internal/generate/olm-catalog/package_manifest_test.go +++ /dev/null @@ -1,239 +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 olmcatalog - -import ( - "io/ioutil" - "os" - "path/filepath" - "reflect" - "testing" - - apimanifests "github.com/operator-framework/api/pkg/manifests" - - "github.com/stretchr/testify/assert" -) - -func TestGeneratePackageManifestToOutput(t *testing.T) { - chdirCleanup := chDirWithCleanup(t, testNonStandardLayoutDataDir) - defer chdirCleanup() - - // Temporary output dir for generating package manifest. - outputDir, mktempCleanup := mkTempDirWithCleanup(t, "-output-catalog") - defer mktempCleanup() - - g := PkgGenerator{ - OperatorName: testProjectName, - OutputDir: outputDir, - CSVVersion: csvVersion, - Channel: "stable", - ChannelIsDefault: true, - } - if err := g.Generate(); err != nil { - t.Fatalf("Failed to execute package manifest generator: %v", err) - } - - pkgManFileName := getPkgFileName(testProjectName) - - // Read expected Package Manifest - expCatalogDir := filepath.Join("expected-catalog", OLMCatalogChildDir) - pkgManExpBytes, err := ioutil.ReadFile(filepath.Join(expCatalogDir, testProjectName, pkgManFileName)) - if err != nil { - t.Fatalf("Failed to read expected package manifest file: %v", err) - } - pkgManExp := string(pkgManExpBytes) - - // Read generated Package Manifest from OutputDir/olm-catalog - outputCatalogDir := filepath.Join(g.OutputDir, OLMCatalogChildDir) - pkgManOutputBytes, err := ioutil.ReadFile(filepath.Join(outputCatalogDir, testProjectName, pkgManFileName)) - if err != nil { - t.Fatalf("Failed to read output package manifest file: %v", err) - } - pkgManOutput := string(pkgManOutputBytes) - - assert.Equal(t, pkgManExp, pkgManOutput) - -} - -func TestGeneratePackageManifest(t *testing.T) { - chdirCleanup := chDirWithCleanup(t, testNonStandardLayoutDataDir) - defer chdirCleanup() - - // Temporary output dir for generating package manifest. - outputDir, mktempCleanup := mkTempDirWithCleanup(t, "-output-catalog") - defer mktempCleanup() - - manifestsRootDir := filepath.Join(outputDir, OLMCatalogChildDir, testProjectName) - if err := os.MkdirAll(manifestsRootDir, os.ModePerm); err != nil { - t.Fatal(err) - } - outputPath := filepath.Join(manifestsRootDir, getPkgFileName(testProjectName)) - err := ioutil.WriteFile(outputPath, []byte(packageManifestInput), os.ModePerm) - if err != nil { - t.Fatal(err) - } - - g := PkgGenerator{ - OperatorName: testProjectName, - CSVVersion: csvVersion, - OutputDir: outputDir, - Channel: "stable", - ChannelIsDefault: true, - } - g.setDefaults() - fileMap, err := g.generate() - if err != nil { - t.Fatalf("Failed to execute package manifest generator: %v", err) - } - - if b, ok := fileMap[g.fileName]; !ok { - t.Error("Failed to generate package manifest") - } else { - assert.Equal(t, packageManifestExp, string(b)) - } -} - -const packageManifestInput = `channels: -- currentCSV: memcached-operator.v0.0.2 - name: alpha -defaultChannel: alpha -packageName: memcached-operator -` - -const packageManifestExp = `channels: -- currentCSV: memcached-operator.v0.0.2 - name: alpha -- currentCSV: memcached-operator.v0.0.3 - name: stable -defaultChannel: stable -packageName: memcached-operator -` - -func TestValidatePackageManifest(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - g := PkgGenerator{ - OperatorName: testProjectName, - CSVVersion: csvVersion, - Channel: "stable", - ChannelIsDefault: true, - } - - // pkg is a basic, valid package manifest. - pkg, err := g.buildPackageManifest() - if err != nil { - t.Fatalf("Failed to execute package manifest generator: %v", err) - } - - g.setChannels(&pkg) - sortChannelsByName(&pkg) - - // invalid mock data, pkg with empty channel - invalidPkgWithEmptyChannels := pkg - invalidPkgWithEmptyChannels.Channels = []apimanifests.PackageChannel{} - - type args struct { - pkg *apimanifests.PackageManifest - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Should work successfully with a valid pkg", - wantErr: false, - args: args{ - pkg: &pkg, - }, - }, - { - name: "Should return error when the pkg is not informed", - wantErr: true, - }, - { - name: "Should return error when the pkg is invalid", - wantErr: true, - args: args{ - pkg: &invalidPkgWithEmptyChannels, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := validatePackageManifest(tt.args.pkg); (err != nil) != tt.wantErr { - t.Errorf("Failed to check package manifest validate: error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestNewPackageManifest(t *testing.T) { - type args struct { - operatorName string - channelName string - version string - } - tests := []struct { - name string - args args - want apimanifests.PackageManifest - }{ - { - name: "Should return a valid apimanifests.PackageManifest", - want: apimanifests.PackageManifest{ - PackageName: "memcached-operator", - Channels: []apimanifests.PackageChannel{ - apimanifests.PackageChannel{ - Name: "stable", - CurrentCSVName: "memcached-operator.v0.0.3", - }, - }, - DefaultChannelName: "stable", - }, - args: args{ - operatorName: testProjectName, - channelName: "stable", - version: csvVersion, - }, - }, - { - name: "Should return a valid apimanifests.PackageManifest with channel == alpha when it is not informed", - want: apimanifests.PackageManifest{ - PackageName: "memcached-operator", - Channels: []apimanifests.PackageChannel{ - apimanifests.PackageChannel{ - Name: "alpha", - CurrentCSVName: "memcached-operator.v0.0.3", - }, - }, - DefaultChannelName: "alpha", - }, - args: args{ - operatorName: testProjectName, - version: csvVersion, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := newPackageManifest(tt.args.operatorName, tt.args.channelName, tt.args.version) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewPackageManifest() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/website/content/en/docs/cli/operator-sdk.md b/website/content/en/docs/cli/operator-sdk.md index 58b6921157..56a5b345f2 100644 --- a/website/content/en/docs/cli/operator-sdk.md +++ b/website/content/en/docs/cli/operator-sdk.md @@ -19,9 +19,7 @@ An SDK for building operators with ease * [operator-sdk add](../operator-sdk_add) - Adds a controller or resource to the project * [operator-sdk build](../operator-sdk_build) - Compiles code and builds artifacts -* [operator-sdk bundle](../operator-sdk_bundle) - Manage operator bundle metadata * [operator-sdk completion](../operator-sdk_completion) - Generators for shell completions -* [operator-sdk generate](../operator-sdk_generate) - Invokes a specific generator * [operator-sdk new](../operator-sdk_new) - Creates a new operator application * [operator-sdk olm](../operator-sdk_olm) - Manage the Operator Lifecycle Manager installation in your cluster * [operator-sdk scorecard](../operator-sdk_scorecard) - Runs scorecard diff --git a/website/content/en/docs/cli/operator-sdk_bundle.md b/website/content/en/docs/cli/operator-sdk_bundle.md deleted file mode 100644 index fad154164f..0000000000 --- a/website/content/en/docs/cli/operator-sdk_bundle.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: "operator-sdk bundle" ---- -## operator-sdk bundle - -Manage operator bundle metadata - -### Synopsis - -Manage bundle builds, bundle metadata generation, and bundle validation. -An operator bundle is a portable operator packaging format understood by Kubernetes -native software, like the Operator Lifecycle Manager. - -More information about operator bundles and metadata: -https://github.com/operator-framework/operator-registry/blob/master/docs/design/operator-bundle.md - -More information about the integration with OLM via SDK: -https://sdk.operatorframework.io/docs/olm-integration/legacy - - -### Options - -``` - -h, --help help for bundle -``` - -### SEE ALSO - -* [operator-sdk](../operator-sdk) - An SDK for building operators with ease -* [operator-sdk bundle validate](../operator-sdk_bundle_validate) - Validate an operator bundle - diff --git a/website/content/en/docs/cli/operator-sdk_bundle_validate.md b/website/content/en/docs/cli/operator-sdk_bundle_validate.md deleted file mode 100644 index 569d21f9c0..0000000000 --- a/website/content/en/docs/cli/operator-sdk_bundle_validate.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: "operator-sdk bundle validate" ---- -## operator-sdk bundle validate - -Validate an operator bundle - -### Synopsis - -The 'operator-sdk bundle validate' command can validate both content and format of an operator bundle -image or an operator bundle directory on-disk containing operator metadata and manifests. This command will exit -with an exit code of 1 if any validation errors arise, and 0 if only warnings arise or all validators pass. - -More information about operator bundles and metadata: -https://github.com/operator-framework/operator-registry/blob/master/docs/design/operator-bundle.md - -NOTE: if validating an image, the image must exist in a remote registry, not just locally. - - -``` -operator-sdk bundle validate [flags] -``` - -### Examples - -``` -The following command flow will generate test-operator bundle manifests and metadata, -then validate them for 'test-operator' version v0.1.0: - - # Generate manifests and metadata locally. - $ operator-sdk generate bundle --version 0.1.0 - - # Validate the directory containing manifests and metadata. - $ operator-sdk bundle validate ./deploy/olm-catalog/test-operator - -To build and validate an image built with the above manifests and metadata: - - # Create a registry namespace or use an existing one. - $ export NAMESPACE= - - # Build and push the image using the docker CLI. - $ docker build -f bundle.Dockerfile -t quay.io/$NAMESPACE/test-operator:v0.1.0 . - $ docker push quay.io/$NAMESPACE/test-operator:v0.1.0 - - # Ensure the image with modified metadata and Dockerfile is valid. - $ operator-sdk bundle validate quay.io/$NAMESPACE/test-operator:v0.1.0 - -``` - -### Options - -``` - -h, --help help for validate - -b, --image-builder string Tool to pull and unpack bundle images. Only used when validating a bundle image. One of: [docker, podman, none] (default "docker") -``` - -### SEE ALSO - -* [operator-sdk bundle](../operator-sdk_bundle) - Manage operator bundle metadata - diff --git a/website/content/en/docs/cli/operator-sdk_generate.md b/website/content/en/docs/cli/operator-sdk_generate.md deleted file mode 100644 index aa1849ce35..0000000000 --- a/website/content/en/docs/cli/operator-sdk_generate.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: "operator-sdk generate" ---- -## operator-sdk generate - -Invokes a specific generator - -### Synopsis - -The 'operator-sdk generate' command invokes a specific generator to generate -code or manifests. - -### Options - -``` - -h, --help help for generate -``` - -### SEE ALSO - -* [operator-sdk](../operator-sdk) - An SDK for building operators with ease -* [operator-sdk generate bundle](../operator-sdk_generate_bundle) - Generates bundle data for the operator -* [operator-sdk generate packagemanifests](../operator-sdk_generate_packagemanifests) - Generates a package manifests format - diff --git a/website/content/en/docs/cli/operator-sdk_generate_bundle.md b/website/content/en/docs/cli/operator-sdk_generate_bundle.md deleted file mode 100644 index eb9a86f626..0000000000 --- a/website/content/en/docs/cli/operator-sdk_generate_bundle.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: "operator-sdk generate bundle" ---- -## operator-sdk generate bundle - -Generates bundle data for the operator - -### Synopsis - - -Running 'generate bundle' is the first step to publishing your operator to a catalog -and/or deploying it with OLM. This command generates a set of bundle manifests, -metadata, and a bundle.Dockerfile for your operator, and will interactively ask -for UI metadata, an important component of publishing your operator, by default unless -a bundle for your operator exists or you set '--interactive=false'. - -Set '--version' to supply a semantic version for your bundle if you are creating one -for the first time or upgrading an existing one. - -If '--output-dir' is set and you wish to build bundle images from that directory, -either manually update your bundle.Dockerfile or set '--overwrite'. - -More information on bundles: -https://github.com/operator-framework/operator-registry/#manifest-format - - -``` -operator-sdk generate bundle [flags] -``` - -### Examples - -``` - - # Create bundle manifests, metadata, and a bundle.Dockerfile: - $ operator-sdk generate bundle --version 0.0.1 - INFO[0000] Generating bundle manifest version 0.0.1 - - Display name for the operator (required): - > memcached-operator - ... - - # After running the above commands, you should see: - $ tree deploy/olm-catalog - deploy/olm-catalog - └── memcached-operator - ├── manifests - │ ├── cache.example.com_memcacheds_crd.yaml - │ └── memcached-operator.clusterserviceversion.yaml - └── metadata - └── annotations.yaml - - # Then build and push your bundle image: - $ export USERNAME= - $ export BUNDLE_IMG=quay.io/$USERNAME/memcached-operator-bundle:v0.0.1 - $ docker build -f bundle.Dockerfile -t $BUNDLE_IMG . - Sending build context to Docker daemon 42.33MB - Step 1/9 : FROM scratch - ... - $ docker push $BUNDLE_IMG - -``` - -### Options - -``` - --apis-dir string Root directory for API type defintions - --channels string A comma-separated list of channels the bundle belongs to (default "alpha") - --crds-dir string Root directory for CustomResoureDefinition manifests - --default-channel string The default channel for the bundle - --deploy-dir string Root directory for operator manifests such as Deployments and RBAC, ex. 'deploy'. This directory is different from that passed to --input-dir - -h, --help help for bundle - --input-dir string Directory to read an existing bundle from. This directory is the parent of your bundle 'manifests' directory, and different from --deploy-dir - --interactive When set or no bundle base exists, an interactive command prompt will be presented to accept bundle ClusterServiceVersion metadata - --manifests Generate bundle manifests - --metadata Generate bundle metadata and Dockerfile - --operator-name string Name of the bundle's operator - --output-dir string Directory to write the bundle to - --overwrite Overwrite the bundle's metadata and Dockerfile if they exist (default true) - -q, --quiet Run in quiet mode - -v, --version string Semantic version of the operator in the generated bundle. Only set if creating a new bundle or upgrading your operator -``` - -### SEE ALSO - -* [operator-sdk generate](../operator-sdk_generate) - Invokes a specific generator - diff --git a/website/content/en/docs/cli/operator-sdk_generate_packagemanifests.md b/website/content/en/docs/cli/operator-sdk_generate_packagemanifests.md deleted file mode 100644 index 6ff7e82298..0000000000 --- a/website/content/en/docs/cli/operator-sdk_generate_packagemanifests.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: "operator-sdk generate packagemanifests" ---- -## operator-sdk generate packagemanifests - -Generates a package manifests format - -### Synopsis - - -Note: while the package manifests format is not yet deprecated, the operator-framework is migrated -towards using bundles by default. Run 'operator-sdk generate bundle -h' for more information. - -Running 'generate packagemanifests' is the first step to publishing your operator to a catalog -and/or deploying it with OLM. This command generates a set of manifests in a versioned directory -and a package manifest file for your operator. It will interactively ask for UI metadata, -an important component of publishing your operator, by default unless a package for your -operator exists or you set '--interactive=false'. - -Set '--version' to supply a semantic version for your new package. This is a required flag when running -'generate packagemanifests --manifests'. - -More information on the package manifests format: -https://github.com/operator-framework/operator-registry/#manifest-format - - -``` -operator-sdk generate packagemanifests [flags] -``` - -### Examples - -``` - - # Create the package manifest file and a new package: - $ operator-sdk generate packagemanifests --version 0.0.1 - INFO[0000] Generating package manifests version 0.0.1 - - Display name for the operator (required): - > memcached-operator - ... - - # After running the above commands, you should see: - $ tree deploy/olm-catalog - deploy/olm-catalog - └── memcached-operator - ├── 0.0.1 - │ ├── cache.example.com_memcacheds_crd.yaml - │ └── memcached-operator.clusterserviceversion.yaml - └── memacached-operator.package.yaml - -``` - -### Options - -``` - --apis-dir string Root directory for API type defintions - --channel string Channel name for the generated package - --crds-dir string Root directory for CustomResoureDefinition manifests - --default-channel Use the channel passed to --channel as the package manifest file's default channel - --deploy-dir string Root directory for operator manifests such as Deployments and RBAC, ex. 'deploy'. This directory is different from that passed to --input-dir - -h, --help help for packagemanifests - --input-dir string Directory to read existing package manifests from. This directory is the parent of individual versioned package directories, and different from --deploy-dir - --interactive When set or no package base exists, an interactive command prompt will be presented to accept package ClusterServiceVersion metadata - --operator-name string Name of the packaged operator - --output-dir string Directory in which to write package manifests - -q, --quiet Run in quiet mode - --update-crds Update CustomResoureDefinition manifests in this package (default true) - -v, --version string Semantic version of the packaged operator -``` - -### SEE ALSO - -* [operator-sdk generate](../operator-sdk_generate) - Invokes a specific generator -