From 04eacd323b8ed2e2e2d4346e04af772f5fdd7d65 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 29 May 2020 14:36:53 -0700 Subject: [PATCH] generate: add `packagemanifests` subcommand for legacy project layouts --- .../generate-packagemanifests-legacy.yaml | 3 + cmd/operator-sdk/generate/cmd.go | 1 + .../generate/packagemanifests/cmd.go | 42 +++++ .../packagemanifests_legacy.go | 143 ++++++++++++++++++ hack/tests/subcommand-generate-csv.sh | 49 ++++-- .../clusterserviceversion.go | 18 +++ .../clusterserviceversion_test.go | 16 ++ .../en/docs/cli/operator-sdk_generate.md | 1 + .../operator-sdk_generate_packagemanifests.md | 75 +++++++++ 9 files changed, 334 insertions(+), 14 deletions(-) create mode 100644 changelog/fragments/generate-packagemanifests-legacy.yaml create mode 100644 cmd/operator-sdk/generate/packagemanifests/packagemanifests_legacy.go create mode 100644 website/content/en/docs/cli/operator-sdk_generate_packagemanifests.md diff --git a/changelog/fragments/generate-packagemanifests-legacy.yaml b/changelog/fragments/generate-packagemanifests-legacy.yaml new file mode 100644 index 0000000000..230c713dcd --- /dev/null +++ b/changelog/fragments/generate-packagemanifests-legacy.yaml @@ -0,0 +1,3 @@ +entries: + - description: add `generate packagemanifests` subcommand for legacy project layouts + kind: addition diff --git a/cmd/operator-sdk/generate/cmd.go b/cmd/operator-sdk/generate/cmd.go index 2dc10f6891..91b199aad4 100644 --- a/cmd/operator-sdk/generate/cmd.go +++ b/cmd/operator-sdk/generate/cmd.go @@ -48,6 +48,7 @@ func NewCmdLegacy() *cobra.Command { newGenerateCRDsCmd(), newGenerateCSVCmd(), bundle.NewCmdLegacy(), + packagemanifests.NewCmdLegacy(), ) return cmd } diff --git a/cmd/operator-sdk/generate/packagemanifests/cmd.go b/cmd/operator-sdk/generate/packagemanifests/cmd.go index 863f13865d..1bf8fc4ab0 100644 --- a/cmd/operator-sdk/generate/packagemanifests/cmd.go +++ b/cmd/operator-sdk/generate/packagemanifests/cmd.go @@ -136,6 +136,48 @@ func NewCmd() *cobra.Command { return cmd } +// NewCmdLegacy returns the 'packagemanifests' command configured for the legacy project layout. +func NewCmdLegacy() *cobra.Command { + c := &packagemanifestsCmd{} + + cmd := &cobra.Command{ + Use: "packagemanifests", + Short: "Generates a package manifests format", + Long: longHelp, + 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.setCommonDefaultsLegacy() + + if err := c.validateManifestsLegacy(); err != nil { + return fmt.Errorf("invalid command options: %v", err) + } + if err := c.runManifestsLegacy(); err != nil { + log.Fatalf("Error generating package manifests: %v", err) + } + + return nil + }, + } + + 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 new file mode 100644 index 0000000000..7e2d4d0b56 --- /dev/null +++ b/cmd/operator-sdk/generate/packagemanifests/packagemanifests_legacy.go @@ -0,0 +1,143 @@ +// 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 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 +` + +// setCommonDefaultsLegacy sets defaults useful to all modes of this subcommand for legacy project layouts. +func (c *packagemanifestsCmd) setCommonDefaultsLegacy() { + 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 + } +} + +// validateManifestsLegacy validates c for package manifests generation for legacy project layouts. +func (c packagemanifestsCmd) validateManifestsLegacy() 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 +} + +// runManifestsLegacy generates package manifests for legacy project layouts. +func (c packagemanifestsCmd) runManifestsLegacy() error { + + if !c.quiet { + log.Infoln("Generating package manifests version", c.version) + } + packageDir := filepath.Join(c.outputDir, 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) + } + if err := genutil.WriteObjectsToFilesLegacy(packageDir, objs...); err != nil { + return err + } + } + + if !c.quiet { + log.Infoln("Package manifests generated successfully in", c.outputDir) + } + + return nil +} diff --git a/hack/tests/subcommand-generate-csv.sh b/hack/tests/subcommand-generate-csv.sh index aacb57cbba..d066dfe09d 100755 --- a/hack/tests/subcommand-generate-csv.sh +++ b/hack/tests/subcommand-generate-csv.sh @@ -14,30 +14,25 @@ OPERATOR_VERSION="0.0.4" OPERATOR_BUNDLE_ROOT_DIR="deploy/olm-catalog/${OPERATOR_NAME}" DEFAULT_BUNDLE_DIR="${OPERATOR_BUNDLE_ROOT_DIR}/${OPERATOR_VERSION}" OUTPUT_DIR="foo" -OUTPUT_BUNDLE_DIR="${OUTPUT_DIR}/olm-catalog/${OPERATOR_NAME}/${OPERATOR_VERSION}" - -function csv_file_for_dir_legacy() { - echo "${1}/${OPERATOR_NAME}.v${OPERATOR_VERSION}.clusterserviceversion.yaml" -} +OUTPUT_BUNDLE_ROOT_DIR="${OUTPUT_DIR}/olm-catalog/${OPERATOR_NAME}" +OUTPUT_BUNDLE_DIR="${OUTPUT_BUNDLE_ROOT_DIR}/${OPERATOR_VERSION}" function check_csv_file_legacy() { - check_file "$1" "$(csv_file_for_dir_legacy "$2")" $3 -} - -function csv_file_for_dir() { - echo "${1}/${OPERATOR_NAME}.clusterserviceversion.yaml" + check_file "$1" "${2}/${OPERATOR_NAME}.v${OPERATOR_VERSION}.clusterserviceversion.yaml" $3 } function check_csv_file() { - check_file "$1" "$(csv_file_for_dir "$2")" $3 + check_file "$1" "${2}/${OPERATOR_NAME}.clusterserviceversion.yaml" $3 } -function crd_files_for_dir() { - echo "${1}/cache.example.com_memcacheds_crd.yaml ${1}/cache.example.com_memcachedrs_crd.yaml" +function check_package_file() { + check_file "$1" "${2}/${OPERATOR_NAME}.package.yaml" $3 } function check_crd_files() { - for file in $(crd_files_for_dir "$2"); do check_file "$1" "$file" $3; done + local memcacheds_crd_file="${2}/cache.example.com_memcacheds_crd.yaml" + local memcachedrs_crd_file="${2}/cache.example.com_memcachedrs_crd.yaml" + for file in $memcacheds_crd_file $memcachedrs_crd_file; do check_file "$1" "$file" $3; done } function generate_csv() { @@ -48,6 +43,10 @@ function generate_bundle() { echo_run operator-sdk generate bundle --operator-name $OPERATOR_NAME --interactive=false $@ } +function generate_packagemanifests() { + echo_run operator-sdk generate packagemanifests --operator-name $OPERATOR_NAME --interactive=false $@ +} + pushd "$TEST_DIR" > /dev/null trap_add "git clean -dfxq $TEST_DIR" EXIT trap_add "popd > /dev/null" EXIT @@ -136,3 +135,25 @@ check_file "$TEST_NAME" "bundle.Dockerfile" 1 cleanup_case header_text "All 'operator-sdk generate bundle' subcommand tests passed." + +header_text "Running 'operator-sdk generate packagemanifests' subcommand tests in $TEST_DIR." + +TEST_NAME="generate with version $OPERATOR_VERSION" +header_text "$TEST_NAME" +generate_packagemanifests --version $OPERATOR_VERSION +check_dir "$TEST_NAME" "$DEFAULT_BUNDLE_DIR" 1 +check_package_file "$TEST_NAME" "$OPERATOR_BUNDLE_ROOT_DIR" 1 +check_csv_file "$TEST_NAME" "$DEFAULT_BUNDLE_DIR" 1 +check_crd_files "$TEST_NAME" "$DEFAULT_BUNDLE_DIR" 1 +cleanup_case + +TEST_NAME="generate with version $OPERATOR_VERSION and output-dir" +header_text "$TEST_NAME" +generate_packagemanifests --version $OPERATOR_VERSION --output-dir "$OUTPUT_DIR" +check_dir "$TEST_NAME" "${OUTPUT_DIR}/${OPERATOR_VERSION}" 1 +check_package_file "$TEST_NAME" "$OUTPUT_DIR" 1 +check_csv_file "$TEST_NAME" "${OUTPUT_DIR}/${OPERATOR_VERSION}" 1 +check_crd_files "$TEST_NAME" "${OUTPUT_DIR}/${OPERATOR_VERSION}" 1 +cleanup_case + +header_text "All 'operator-sdk generate packagemanifests' subcommand tests passed." diff --git a/internal/generate/clusterserviceversion/clusterserviceversion.go b/internal/generate/clusterserviceversion/clusterserviceversion.go index 45292e6af6..bac9149b79 100644 --- a/internal/generate/clusterserviceversion/clusterserviceversion.go +++ b/internal/generate/clusterserviceversion/clusterserviceversion.go @@ -178,6 +178,14 @@ func WithBundleBase(inputDir, apisDir string, ilvl projutil.InteractiveLevel) Le } } +// WithPackageBase sets a Generator's base CSV to a legacy-style package base. +func WithPackageBase(inputDir, apisDir string, ilvl projutil.InteractiveLevel) LegacyOption { + return func(g *Generator) error { + g.getBase = g.makePackageBaseGetterLegacy(inputDir, apisDir, ilvl) + return nil + } +} + // GenerateLegacy configures the generator with opts then runs it. Used for // generating files for legacy project layouts. func (g *Generator) GenerateLegacy(opts ...LegacyOption) (err error) { @@ -275,6 +283,16 @@ func (g Generator) makeBundleBaseGetterLegacy(inputDir, apisDir string, ilvl pro return g.makeBaseGetterLegacy(basePath, apisDir, requiresInteraction(basePath, ilvl)) } +// makePackageBaseGetterLegacy returns a function that gets a package base +// for legacy project layouts. +func (g Generator) makePackageBaseGetterLegacy(inputDir, apisDir string, ilvl projutil.InteractiveLevel) getBaseFunc { + basePath := filepath.Join(inputDir, g.Version, makeCSVFileName(g.OperatorName)) + if genutil.IsNotExist(basePath) { + basePath = "" + } + return g.makeBaseGetterLegacy(basePath, apisDir, requiresInteraction(basePath, ilvl)) +} + // makeBaseGetterLegacy returns a function that gets a base from inputDir. // apisDir is used by getBaseFunc to populate base fields. This method should // be used when creating LegacyOptions. diff --git a/internal/generate/clusterserviceversion/clusterserviceversion_test.go b/internal/generate/clusterserviceversion/clusterserviceversion_test.go index a8101e007d..879ffb4667 100644 --- a/internal/generate/clusterserviceversion/clusterserviceversion_test.go +++ b/internal/generate/clusterserviceversion/clusterserviceversion_test.go @@ -186,6 +186,22 @@ var _ = Describe("Generating a ClusterServiceVersion", func() { Expect(outputFile).To(BeAnExistingFile()) Expect(string(readFileHelper(outputFile))).To(MatchYAML(newCSVStr)) }) + It("should write a ClusterServiceVersion manifest as a legacy package file", func() { + g = Generator{ + OperatorName: operatorName, + OperatorType: operatorType, + Version: version, + Collector: col, + } + opts := []LegacyOption{ + WithPackageBase(csvBasesDir, goAPIsDir, projutil.InteractiveHardOff), + LegacyOption(WithPackageWriter(tmp)), + } + Expect(g.GenerateLegacy(opts...)).Should(Succeed()) + outputFile := filepath.Join(tmp, g.Version, makeCSVFileName(operatorName)) + Expect(outputFile).To(BeAnExistingFile()) + Expect(string(readFileHelper(outputFile))).To(MatchYAML(newCSVStr)) + }) }) Context("with incorrect Options", func() { diff --git a/website/content/en/docs/cli/operator-sdk_generate.md b/website/content/en/docs/cli/operator-sdk_generate.md index ca70ef6ae0..90a69b16d3 100644 --- a/website/content/en/docs/cli/operator-sdk_generate.md +++ b/website/content/en/docs/cli/operator-sdk_generate.md @@ -23,4 +23,5 @@ code or manifests. * [operator-sdk generate crds](../operator-sdk_generate_crds) - Generates CRDs for API's * [operator-sdk generate csv](../operator-sdk_generate_csv) - Generates a ClusterServiceVersion YAML file for the operator * [operator-sdk generate k8s](../operator-sdk_generate_k8s) - Generates Kubernetes code for custom resource +* [operator-sdk generate packagemanifests](../operator-sdk_generate_packagemanifests) - Generates a package manifests format diff --git a/website/content/en/docs/cli/operator-sdk_generate_packagemanifests.md b/website/content/en/docs/cli/operator-sdk_generate_packagemanifests.md new file mode 100644 index 0000000000..da13808383 --- /dev/null +++ b/website/content/en/docs/cli/operator-sdk_generate_packagemanifests.md @@ -0,0 +1,75 @@ +--- +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 +