diff --git a/CHANGELOG.md b/CHANGELOG.md index 79663b6d39f..8e07eda57a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ ## Unreleased ### Added + - Support for vars in top level ansible watches. ([#2147](https://github.com/operator-framework/operator-sdk/pull/2147)) - Support for `"ansible.operator-sdk/verbosity"` annotation on Custom Resources watched by Ansible based operators to override verbosity on an individual resource. ([#2102](https://github.com/operator-framework/operator-sdk/pull/2102)) - Support for relative helm chart paths in the Helm operator's watches.yaml file. ([#2287](https://github.com/operator-framework/operator-sdk/pull/2287)) +- Added the [`olm-catalog gen-csv --include`](doc/cli/operator-sdk_olm-catalog_gen-csv.md#options) option to include files as input to the CSV generator in lieu of a config. ([#2249](https://github.com/operator-framework/operator-sdk/pull/2249)) ### Changed + - Upgrade minimal Ansible version in the init projects from `2.4` to `2.6`. ([#2107](https://github.com/operator-framework/operator-sdk/pull/2107)) - Upgrade Kubernetes version from `kubernetes-1.15.4` to `kubernetes-1.16.2`. ([#2145](https://github.com/operator-framework/operator-sdk/pull/2145)) - Upgrade Helm version from `v2.15.0` to `v2.16.1`. ([#2145](https://github.com/operator-framework/operator-sdk/pull/2145)) @@ -20,6 +23,8 @@ ### Removed +- Removed CSV configuration file support in favor of including all files in the default dir `deploy` and using [`gen-csv --include`](doc/cli/operator-sdk_olm-catalog_gen-csv.md#options) to include files as input to generator. ([#2249](https://github.com/operator-framework/operator-sdk/pull/2249)) + ### Bug Fixes - Fix issue faced in the Ansible based operators when `jmespath` queries are used because it was not installed. ([#2252](https://github.com/operator-framework/operator-sdk/pull/2252)) - Fix scorecard behavior such that a CSV file is read correctly when `olm-deployed` is set to `true`. ([#2274](https://github.com/operator-framework/operator-sdk/pull/2274)) diff --git a/cmd/operator-sdk/olmcatalog/gen-csv.go b/cmd/operator-sdk/olmcatalog/gen-csv.go index e4a6dceef34..4fcd0350d14 100644 --- a/cmd/operator-sdk/olmcatalog/gen-csv.go +++ b/cmd/operator-sdk/olmcatalog/gen-csv.go @@ -17,8 +17,10 @@ package olmcatalog import ( "fmt" "io/ioutil" + "os" "path/filepath" + genutil "github.com/operator-framework/operator-sdk/internal/generate/util" "github.com/operator-framework/operator-sdk/internal/scaffold" "github.com/operator-framework/operator-sdk/internal/scaffold/input" catalog "github.com/operator-framework/operator-sdk/internal/scaffold/olm-catalog" @@ -35,7 +37,7 @@ var ( csvVersion string csvChannel string fromVersion string - csvConfigPath string + includePaths []string operatorName string updateCRDs bool defaultChannel bool @@ -50,9 +52,7 @@ 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. - -Configure CSV generation by writing a config file 'deploy/olm-catalog/csv-config.yaml`, +version to --from-version. Otherwise the SDK will scaffold a new CSV manifest.`, RunE: genCSVFunc, } @@ -61,7 +61,7 @@ Configure CSV generation by writing a config file 'deploy/olm-catalog/csv-config log.Fatalf("Failed to mark `csv-version` flag for `olm-catalog gen-csv` subcommand as required: %v", err) } genCSVCmd.Flags().StringVar(&fromVersion, "from-version", "", "Semantic version of an existing CSV to use as a base") - genCSVCmd.Flags().StringVar(&csvConfigPath, "csv-config", "", "Path to CSV config file. Defaults to deploy/olm-catalog/csv-config.yaml") + genCSVCmd.Flags().StringSliceVar(&includePaths, "include", []string{scaffold.DeployDir}, "Paths to include in CSV generation, ex. \"deploy/prod,deploy/test\". If this flag is set and you want to enable default behavior, you must include \"deploy/\" in the argument list") genCSVCmd.Flags().BoolVar(&updateCRDs, "update-crds", false, "Update CRD manifests in deploy/{operator-name}/{csv-version} the using latest API's") genCSVCmd.Flags().StringVar(&operatorName, "operator-name", "", "Operator name to use while generating CSV") genCSVCmd.Flags().StringVar(&csvChannel, "csv-channel", "", "Channel the CSV should be registered under in the package manifest") @@ -91,23 +91,26 @@ func genCSVFunc(cmd *cobra.Command, args []string) error { log.Infof("Generating CSV manifest version %s", csvVersion) if operatorName == "" { - operatorName = filepath.Base(absProjectPath) + operatorName = filepath.Base(projutil.MustGetwd()) + } + gcfg := genutil.Config{ + OperatorName: operatorName, + IncludeFuncs: genutil.MakeIncludeFuncs(includePaths...), } s := &scaffold.Scaffold{} csv := &catalog.CSV{ - CSVVersion: csvVersion, - FromVersion: fromVersion, - ConfigFilePath: csvConfigPath, - OperatorName: operatorName, + Config: gcfg, + CSVVersion: csvVersion, + FromVersion: fromVersion, } err := s.Execute(cfg, csv, &catalog.PackageManifest{ + Config: gcfg, CSVVersion: csvVersion, Channel: csvChannel, ChannelIsDefault: defaultChannel, - OperatorName: operatorName, }, ) if err != nil { @@ -115,16 +118,9 @@ func genCSVFunc(cmd *cobra.Command, args []string) error { } // Write CRD's to the new or updated CSV package dir. + bundleDir := filepath.Join(catalog.OLMCatalogDir, operatorName, csvVersion) if updateCRDs { - input, err := csv.GetInput() - if err != nil { - return err - } - cfg, err := catalog.GetCSVConfig(csvConfigPath) - if err != nil { - return err - } - err = writeCRDsToDir(cfg.CRDCRPaths, filepath.Dir(input.Path)) + err = writeCRDsToDir(scaffold.CRDsDir, bundleDir, gcfg.IncludeFuncs) if err != nil { return err } @@ -166,25 +162,25 @@ func verifyCSVVersion(version string) error { return nil } -func writeCRDsToDir(crdPaths []string, toDir string) error { - for _, p := range crdPaths { - b, err := ioutil.ReadFile(p) - if err != nil { +func writeCRDsToDir(fromDir, toDir string, includeFuncs genutil.IncludeFuncs) error { + return filepath.Walk(fromDir, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { return err } - typeMeta, err := k8sutil.GetTypeMetaFromBytes(b) + if !includeFuncs.IsInclude(path) { + return nil + } + b, err := ioutil.ReadFile(path) if err != nil { return err } - if typeMeta.Kind != "CustomResourceDefinition" { - continue - } - - path := filepath.Join(toDir, filepath.Base(p)) - err = ioutil.WriteFile(path, b, fileutil.DefaultFileMode) + typeMeta, err := k8sutil.GetTypeMetaFromBytes(b) if err != nil { return err } - } - return nil + if typeMeta.Kind == "CustomResourceDefinition" { + return ioutil.WriteFile(path, b, fileutil.DefaultFileMode) + } + return nil + }) } diff --git a/doc/cli/operator-sdk_olm-catalog_gen-csv.md b/doc/cli/operator-sdk_olm-catalog_gen-csv.md index c5d59389ef7..16b64114244 100644 --- a/doc/cli/operator-sdk_olm-catalog_gen-csv.md +++ b/doc/cli/operator-sdk_olm-catalog_gen-csv.md @@ -11,8 +11,6 @@ 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. -Configure CSV generation by writing a config file 'deploy/olm-catalog/csv-config.yaml - ``` operator-sdk olm-catalog gen-csv [flags] ``` @@ -21,11 +19,11 @@ operator-sdk olm-catalog gen-csv [flags] ``` --csv-channel string Channel the CSV should be registered under in the package manifest - --csv-config string Path to CSV config file. Defaults to deploy/olm-catalog/csv-config.yaml --csv-version string Semantic version of the CSV --default-channel Use the channel passed to --csv-channel as the package manifests' default channel. Only valid when --csv-channel is set --from-version string Semantic version of an existing CSV to use as a base -h, --help help for gen-csv + --include strings Paths to include in CSV generation, ex. "deploy/prod,deploy/test". If this flag is set and you want to enable default behavior, you must include "deploy/" in the argument list (default [deploy]) --operator-name string Operator name to use while generating CSV --update-crds Update CRD manifests in deploy/{operator-name}/{csv-version} the using latest API's ``` diff --git a/doc/user/olm-catalog/generating-a-csv.md b/doc/user/olm-catalog/generating-a-csv.md index 087620c56cd..e25aeb2aa22 100644 --- a/doc/user/olm-catalog/generating-a-csv.md +++ b/doc/user/olm-catalog/generating-a-csv.md @@ -11,32 +11,12 @@ This document describes how to manage the following lifecycle for your Operator Operator SDK projects have an expected [project layout][doc-project-layout]. In particular, a few manifests are expected to be present in the `deploy` directory: * Roles: `role.yaml` +* ClusterRoles: `cluster_role.yaml` * Deployments: `operator.yaml` * Custom Resources (CR's): `crds/___cr.yaml` -* Custom Resource Definitions (CRD's): `crds/__crd.yaml`. +* CustomResourceDefinitions (CRD's): `crds/__crd.yaml`. -`gen-csv` reads these files and adds their data to a CSV in an alternate form. - -The following example config containing default values should be copied and written to `deploy/olm-catalog/csv-config.yaml`: - -```yaml -crd-cr-paths: -- deploy/crds -operator-path: deploy/operator.yaml -role-paths: -- deploy/role.yaml -``` - -Explanation of all config fields: - -- `crd-cr-paths`: list of strings - a list of CRD and CR manifest file/directory paths. Defaults to `[deploy/crds]`. -- `operator-path`: string - the operator `Deployment` manifest file path. Defaults to `deploy/operator.yaml`. -- `role-paths`: list of strings - Role and ClusterRole manifest file paths. Defaults to `[deploy/role.yaml]`. -- `operator-name`: string - the name used to create the CSV and manifest file names. Defaults to the project's name. - -**Note**: The [design doc][doc-csv-design] has outdated field information which should not be referenced. - -Fields in this config file can be modified to point towards alternate manifest locations, and passed to `gen-csv --csv-config=` to configure CSV generation. For example, if I have one set of production CR/CRD manifests under `deploy/crds/production`, and a set of test manifests under `deploy/crds/test`, and I only want to include production manifests in my CSV, I can set `crd-cr-paths: [deploy/crds/production]`. `gen-csv` will then ignore `deploy/crds/test` when getting CR/CRD data. +`gen-csv` extracts manifests from files in `deploy/` by default that match the kinds above and adds them to the CSV. If your manifest files are not in `deploy/`, you can use the `--include=[list of paths]` option to instruct the command to extract manifests from files at those paths, ex. `--include="deploy/prod,deploy/test"`. Setting `--include` overrides default behavior; if you still want default behavior, you must append `deploy/` to the list of paths passed to `--include`. ## Versioning @@ -44,7 +24,7 @@ CSV's are versioned in path, file name, and in their `metadata.name` field. For `gen-csv` allows you to upgrade your CSV using the `--from-version` flag. If you have an existing CSV with version `0.0.1` and want to write a new version `0.0.2`, you can run `operator-sdk olm-catalog gen-csv --csv-version 0.0.2 --from-version 0.0.1`. This will write a new CSV manifest to `deploy/olm-catalog//0.0.2/.v0.0.2.clusterserviceversion.yaml` containing user-defined data from `0.0.1` and any modifications you've made to `roles.yaml`, `operator.yaml`, CR's, or CRD's. -The SDK can manage CRD's in your Operator bundle as well. You can pass the `--update-crds` flag to `gen-csv` to add or update your CRD's in your bundle by copying manifests pointed to by `crd-cr-paths` in your config. CRD's in a bundle are not updated by default. +The SDK can manage CRD's in your Operator bundle as well. You can pass the `--update-crds` flag to `gen-csv` to add or update your CRD's in your bundle by copying manifests in `deploy/crds` to your bundle. CRD's in a bundle are not updated by default. ## First Generation diff --git a/internal/generate/util/config.go b/internal/generate/util/config.go new file mode 100644 index 00000000000..04577cbecb9 --- /dev/null +++ b/internal/generate/util/config.go @@ -0,0 +1,74 @@ +// Copyright 2019 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package genutil + +import ( + "path/filepath" + "strings" + + "github.com/operator-framework/operator-sdk/internal/util/projutil" +) + +// Config configures a generator with common operator project information. +type Config struct { + // OperatorName is the operator's name, ex. app-operator + OperatorName string + // InputDir is a dir containing relevant input files. If not set, a default + // is used on a per-generator basis. + InputDir string + // OutputDir is a dir in which to generate output files. If not set, a + // default is used on a per-generator basis. + OutputDir string + // IncludeFuncs contains a set of filters for paths that a generator + // may encounter while gathering data for generation. If any func returns + // true, that path will be included by the generator. + IncludeFuncs IncludeFuncs +} + +// IncludeFuncs is a slice of filter funcs. A string passing any func in +// IncludeFuncs satisfies the filter. +type IncludeFuncs []func(string) bool + +// MakeIncludeFuncs creates a set of closures around each path in paths +// to populate Config.IncludeFuncs. If the argument to the closure has +// a prefix of path, it returns true. +func MakeIncludeFuncs(paths ...string) (includes IncludeFuncs) { + pathSet := map[string]struct{}{} + for _, path := range paths { + pathSet[filepath.Clean(path)] = struct{}{} + } + wd := projutil.MustGetwd() + string(filepath.Separator) + for path := range pathSet { + // Copy the string for the closure. + pb := strings.Builder{} + pb.WriteString(path) + includes = append(includes, func(p string) bool { + // Handle absolute paths referencing the project directory. + p = strings.TrimPrefix(p, wd) + return strings.HasPrefix(filepath.Clean(p), pb.String()) + }) + } + return includes +} + +// IsInclude checks if path passes any filter in funcs. +func (funcs IncludeFuncs) IsInclude(path string) bool { + for _, f := range funcs { + if f(path) { + return true + } + } + return false +} diff --git a/internal/scaffold/olm-catalog/config.go b/internal/scaffold/olm-catalog/config.go deleted file mode 100644 index caa4a6412f6..00000000000 --- a/internal/scaffold/olm-catalog/config.go +++ /dev/null @@ -1,146 +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 catalog - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/operator-framework/operator-sdk/internal/scaffold" - - "github.com/ghodss/yaml" - log "github.com/sirupsen/logrus" -) - -// CSVConfig is a configuration file for CSV composition. Its fields contain -// file path information. -// TODO(estroz): define field for path to write CSV bundle. -// TODO(estroz): make CSVConfig a viper.Config -type CSVConfig struct { - // The operator manifest file path. Defaults to deploy/operator.yaml. - OperatorPath string `json:"operator-path,omitempty"` - // Role and ClusterRole manifest file paths. Defaults to [deploy/role.yaml]. - RolePaths []string `json:"role-paths,omitempty"` - // A list of CRD and CR manifest file paths. Defaults to [deploy/crds]. - CRDCRPaths []string `json:"crd-cr-paths,omitempty"` - // OperatorName is the name used to create the CSV and manifest file names. - // Defaults to the project's name. - OperatorName string `json:"operator-name,omitempty"` -} - -// TODO: discuss case of no config file at default path: write new file or not. -func GetCSVConfig(cfgFile string) (*CSVConfig, error) { - cfg := &CSVConfig{} - if _, err := os.Stat(cfgFile); err == nil { - cfgData, err := ioutil.ReadFile(cfgFile) - if err != nil { - return nil, err - } - if err = yaml.Unmarshal(cfgData, cfg); err != nil { - return nil, err - } - } else if !os.IsNotExist(err) { - return nil, err - } - - if err := cfg.setFields(); err != nil { - return nil, err - } - return cfg, nil -} - -const yamlExt = ".yaml" - -func (c *CSVConfig) setFields() error { - if c.OperatorPath == "" { - info, err := (&scaffold.Operator{}).GetInput() - if err != nil { - return err - } - c.OperatorPath = info.Path - } - - if len(c.RolePaths) == 0 { - info, err := (&scaffold.Role{}).GetInput() - if err != nil { - return err - } - c.RolePaths = []string{info.Path} - } - - if len(c.CRDCRPaths) == 0 { - paths, err := getManifestPathsFromDir(scaffold.CRDsDir) - if err != nil && !os.IsNotExist(err) { - return err - } - if os.IsNotExist(err) { - log.Infof("Default CRDs dir %s does not exist. Omitting field spec.customresourcedefinitions.owned from CSV.", scaffold.CRDsDir) - } else if len(paths) == 0 { - log.Infof("Default CRDs dir %s is empty. Omitting field spec.customresourcedefinitions.owned from CSV.", scaffold.CRDsDir) - } else { - c.CRDCRPaths = paths - } - } else { - // Allow user to specify a list of dirs to search. Avoid duplicate files. - paths, seen := make([]string, 0), make(map[string]struct{}) - for _, path := range c.CRDCRPaths { - info, err := os.Stat(path) - if err != nil { - return err - } - if info.IsDir() { - tmpPaths, err := getManifestPathsFromDir(path) - if err != nil { - return err - } - for _, p := range tmpPaths { - if _, ok := seen[p]; !ok { - paths = append(paths, p) - seen[p] = struct{}{} - } - } - } else if filepath.Ext(path) == yamlExt { - if _, ok := seen[path]; !ok { - paths = append(paths, path) - seen[path] = struct{}{} - } - } - } - c.CRDCRPaths = paths - } - - return nil -} - -func getManifestPathsFromDir(dir string) (paths []string, err error) { - err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info == nil { - return fmt.Errorf("file info for %s was nil", path) - } - if !info.IsDir() && filepath.Ext(path) == yamlExt { - paths = append(paths, path) - } - return nil - }) - if err != nil { - return nil, err - } - return paths, nil -} diff --git a/internal/scaffold/olm-catalog/config_test.go b/internal/scaffold/olm-catalog/config_test.go deleted file mode 100644 index 4699006acaa..00000000000 --- a/internal/scaffold/olm-catalog/config_test.go +++ /dev/null @@ -1,55 +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 catalog - -import ( - "path/filepath" - "reflect" - "sort" - "testing" - - "github.com/operator-framework/operator-sdk/internal/scaffold" -) - -func TestConfig(t *testing.T) { - crdsDir := filepath.Join(testDataDir, scaffold.CRDsDir) - - cfg := &CSVConfig{ - CRDCRPaths: []string{crdsDir}, - } - if err := cfg.setFields(); err != nil { - t.Errorf("Set fields crd-cr paths dir only: (%v)", err) - } - if len(cfg.CRDCRPaths) != 3 { - t.Errorf("Wanted 3 crd/cr files, got: %v", cfg.CRDCRPaths) - } - - cfg = &CSVConfig{ - CRDCRPaths: []string{crdsDir, filepath.Join(crdsDir, "app.example.com_appservices_crd.yaml")}, - } - if err := cfg.setFields(); err != nil { - t.Errorf("Set fields crd-cr paths dir file mix: (%v)", err) - } - want := []string{ - filepath.Join(crdsDir, "app.example.com_v1alpha1_appservice_cr.yaml"), - filepath.Join(crdsDir, "app.example.com_appservices_crd.yaml"), - filepath.Join(crdsDir, "app.example.com_appservices2_crd.yaml"), - } - sort.Strings(want) - sort.Strings(cfg.CRDCRPaths) - if !reflect.DeepEqual(want, cfg.CRDCRPaths) { - t.Errorf("Files in crd-cr-paths do not match expected:\nwanted: %+q\ngot: %+q", want, cfg.CRDCRPaths) - } -} diff --git a/internal/scaffold/olm-catalog/csv.go b/internal/scaffold/olm-catalog/csv.go index e44e881abb5..267b0592948 100644 --- a/internal/scaffold/olm-catalog/csv.go +++ b/internal/scaffold/olm-catalog/csv.go @@ -22,6 +22,7 @@ import ( "strings" "sync" + genutil "github.com/operator-framework/operator-sdk/internal/generate/util" "github.com/operator-framework/operator-sdk/internal/scaffold" "github.com/operator-framework/operator-sdk/internal/scaffold/input" "github.com/operator-framework/operator-sdk/internal/util/k8sutil" @@ -46,18 +47,14 @@ var ErrNoCSVVersion = errors.New("no CSV version supplied") type CSV struct { input.Input + genutil.Config - // ConfigFilePath is the location of a configuration file path for this - // projects' CSV file. - ConfigFilePath string // CSVVersion is the CSV current version. CSVVersion string // FromVersion is the CSV version from which to build a new CSV. A CSV // manifest with this version should exist at: // deploy/olm-catalog/{from_version}/operator-name.v{from_version}.{CSVYamlFileExt} FromVersion string - // OperatorName is the operator's name, ex. app-operator - OperatorName string once sync.Once fs afero.Fs // For testing, ex. afero.NewMemMapFs() @@ -91,9 +88,6 @@ func (s *CSV) GetInput() (input.Input, error) { getCSVFileName(operatorName, s.CSVVersion), ) } - if s.ConfigFilePath == "" { - s.ConfigFilePath = filepath.Join(s.pathPrefix, OLMCatalogDir, CSVConfigYamlFile) - } return s.Input, nil } @@ -113,15 +107,10 @@ func (s *CSV) CustomRender() ([]byte, error) { csv = &olmapiv1alpha1.ClusterServiceVersion{} } - cfg, err := GetCSVConfig(s.ConfigFilePath) - if err != nil { - return nil, err - } - if err = s.updateCSVVersions(csv); err != nil { return nil, err } - if err = s.updateCSVFromManifestFiles(cfg, csv); err != nil { + if err = s.updateCSVFromManifestFiles(csv); err != nil { return nil, err } s.setCSVDefaultFields(csv) @@ -289,26 +278,27 @@ func (s *CSV) updateCSVVersions(csv *olmapiv1alpha1.ClusterServiceVersion) error // Old csv version to replace, and updated csv version. oldVer, newVer := csv.Spec.Version.String(), s.CSVVersion - if oldVer == newVer { - return nil - } - - // Replace all references to the old operator name. - lowerOperatorName := strings.ToLower(s.OperatorName) - oldCSVName := getCSVName(lowerOperatorName, oldVer) - oldRe, err := regexp.Compile(fmt.Sprintf("\\b%s\\b", regexp.QuoteMeta(oldCSVName))) - if err != nil { - return errors.Wrapf(err, "error compiling CSV name regexp %s", oldRe.String()) - } - b, err := yaml.Marshal(csv) - if err != nil { - return err - } - newCSVName := getCSVName(lowerOperatorName, newVer) - b = oldRe.ReplaceAll(b, []byte(newCSVName)) - *csv = olmapiv1alpha1.ClusterServiceVersion{} - if err = yaml.Unmarshal(b, csv); err != nil { - return errors.Wrapf(err, "error unmarshalling CSV %s after replacing old CSV name", csv.GetName()) + // Empty and same version replacement is unnecessary. + emptyVer := olmversion.OperatorVersion{} + if oldVer != emptyVer.String() && oldVer != newVer { + // Replace all references to the old operator name. + lowerOperatorName := strings.ToLower(s.OperatorName) + oldCSVName := getCSVName(lowerOperatorName, oldVer) + oldRe, err := regexp.Compile(fmt.Sprintf("\\b%s\\b", regexp.QuoteMeta(oldCSVName))) + if err != nil { + return errors.Wrapf(err, "error compiling CSV name regexp %s", oldRe.String()) + } + b, err := yaml.Marshal(csv) + if err != nil { + return err + } + newCSVName := getCSVName(lowerOperatorName, newVer) + b = oldRe.ReplaceAll(b, []byte(newCSVName)) + *csv = olmapiv1alpha1.ClusterServiceVersion{} + if err = yaml.Unmarshal(b, csv); err != nil { + return errors.Wrapf(err, "error unmarshalling CSV %s after replacing old CSV name", csv.GetName()) + } + csv.Spec.Replaces = oldCSVName } ver, err := semver.Parse(s.CSVVersion) @@ -316,33 +306,41 @@ func (s *CSV) updateCSVVersions(csv *olmapiv1alpha1.ClusterServiceVersion) error return err } csv.Spec.Version = olmversion.OperatorVersion{Version: ver} - csv.Spec.Replaces = oldCSVName return nil } // updateCSVFromManifestFiles gathers relevant data from generated and // user-defined manifests and updates csv. -func (s *CSV) updateCSVFromManifestFiles(cfg *CSVConfig, csv *olmapiv1alpha1.ClusterServiceVersion) error { +func (s *CSV) updateCSVFromManifestFiles(csv *olmapiv1alpha1.ClusterServiceVersion) error { + // Exclude deploy/olm-catalog to avoid parsing unnecessary files. + olmExclude := genutil.MakeIncludeFuncs(filepath.Join(s.pathPrefix, OLMCatalogDir)) + includeFuncs := append(s.IncludeFuncs, olmExclude...) store := NewUpdaterStore() otherSpecs := make(map[string][][]byte) - paths := append(cfg.CRDCRPaths, cfg.OperatorPath) - paths = append(paths, cfg.RolePaths...) - for _, f := range paths { - yamlData, err := afero.ReadFile(s.getFS(), f) - if err != nil { + root := filepath.Join(s.pathPrefix, scaffold.DeployDir) + err := afero.Walk(s.getFS(), root, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { return err } + if !includeFuncs.IsInclude(path) { + return nil + } + yamlData, err := afero.ReadFile(s.getFS(), path) + if err != nil { + return err + } scanner := yamlutil.NewYAMLScanner(yamlData) for scanner.Scan() { yamlSpec := scanner.Bytes() typeMeta, err := k8sutil.GetTypeMetaFromBytes(yamlSpec) if err != nil { - return errors.Wrapf(err, "error getting type metadata from manifest %s", f) + log.Printf("Skipping non-object manifest %s", path) + continue } found, err := store.AddToUpdater(yamlSpec, typeMeta.Kind) if err != nil { - return errors.Wrapf(err, "error adding manifest %s to CSV updaters", f) + return errors.Wrapf(err, "error adding manifest %s to CSV updaters", path) } if !found { id := gvkID(typeMeta.GroupVersionKind()) @@ -352,9 +350,10 @@ func (s *CSV) updateCSVFromManifestFiles(cfg *CSVConfig, csv *olmapiv1alpha1.Clu otherSpecs[id] = append(otherSpecs[id], yamlSpec) } } - if err = scanner.Err(); err != nil { - return err - } + return scanner.Err() + }) + if err != nil { + return err } for id := range store.crds.crIDs { diff --git a/internal/scaffold/olm-catalog/csv_test.go b/internal/scaffold/olm-catalog/csv_test.go index 995fd4098da..5bf5fc6f747 100644 --- a/internal/scaffold/olm-catalog/csv_test.go +++ b/internal/scaffold/olm-catalog/csv_test.go @@ -16,24 +16,29 @@ package catalog import ( "bytes" + "encoding/json" "io" "io/ioutil" "os" "path/filepath" "testing" + genutil "github.com/operator-framework/operator-sdk/internal/generate/util" "github.com/operator-framework/operator-sdk/internal/scaffold" "github.com/operator-framework/operator-sdk/internal/scaffold/input" testutil "github.com/operator-framework/operator-sdk/internal/scaffold/internal/testutil" "github.com/operator-framework/operator-sdk/internal/util/diffutil" + internalk8sutil "github.com/operator-framework/operator-sdk/internal/util/k8sutil" "github.com/operator-framework/operator-sdk/pkg/k8sutil" "github.com/blang/semver" "github.com/ghodss/yaml" olmapiv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" olminstall "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" + olmversion "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/version" "github.com/spf13/afero" appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -55,7 +60,13 @@ func TestCSVNew(t *testing.T) { }, } - sc := &CSV{CSVVersion: csvVer, pathPrefix: testDataDir, OperatorName: operatorName} + sc := &CSV{ + Config: genutil.Config{ + OperatorName: operatorName, + }, + CSVVersion: csvVer, + pathPrefix: testDataDir, + } err := s.Execute(&input.Config{ProjectName: projectName}, sc) if err != nil { t.Fatalf("Failed to execute the scaffold: (%v)", err) @@ -73,6 +84,78 @@ func TestCSVNew(t *testing.T) { } } +func TestCSVInclude(t *testing.T) { + buf := &bytes.Buffer{} + s := &scaffold.Scaffold{ + GetWriter: func(_ string, _ os.FileMode) (io.Writer, error) { + return buf, nil + }, + } + csvVer := "1.0.0" + projectName := "app-operator-dir" + operatorName := "app-operator" + + sc := &CSV{ + Config: genutil.Config{ + OperatorName: operatorName, + // Include no directories, which should produce an empty CSV. + }, + CSVVersion: csvVer, + pathPrefix: testDataDir, + } + err := s.Execute(&input.Config{ProjectName: projectName}, sc) + if err != nil { + t.Fatalf("Failed to execute the scaffold: (%v)", err) + } + + // Create an empty CSV. + ver, err := semver.Parse(csvVer) + if err != nil { + t.Fatal(err) + } + csv := &olmapiv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + APIVersion: olmapiv1alpha1.ClusterServiceVersionAPIVersion, + Kind: olmapiv1alpha1.ClusterServiceVersionKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: getCSVName(operatorName, csvVer), + Namespace: "placeholder", + Annotations: map[string]string{ + "capabilities": "Basic Install", + }, + }, + Spec: olmapiv1alpha1.ClusterServiceVersionSpec{ + DisplayName: internalk8sutil.GetDisplayName(operatorName), + Description: "Placeholder description", + Provider: olmapiv1alpha1.AppLink{}, + Maintainers: []olmapiv1alpha1.Maintainer{}, + Links: []olmapiv1alpha1.AppLink{}, + Maturity: "alpha", + Version: olmversion.OperatorVersion{Version: ver}, + InstallModes: []olmapiv1alpha1.InstallMode{ + {Type: olmapiv1alpha1.InstallModeTypeOwnNamespace, Supported: true}, + {Type: olmapiv1alpha1.InstallModeTypeSingleNamespace, Supported: true}, + {Type: olmapiv1alpha1.InstallModeTypeMultiNamespace, Supported: false}, + {Type: olmapiv1alpha1.InstallModeTypeAllNamespaces, Supported: true}, + }, + InstallStrategy: olmapiv1alpha1.NamedInstallStrategy{ + StrategyName: olminstall.InstallStrategyNameDeployment, + StrategySpecRaw: json.RawMessage(`{"deployments": null}`), + }, + }, + } + csvExpBytes, err := internalk8sutil.GetObjectBytes(csv, yaml.Marshal) + if err != nil { + t.Fatal(err) + } + csvExp := string(csvExpBytes) + if csvExp != buf.String() { + diffs := diffutil.Diff(csvExp, buf.String()) + t.Errorf("Expected vs actual differs.\n%v", diffs) + } +} + func TestCSVFromOld(t *testing.T) { s := &scaffold.Scaffold{Fs: afero.NewMemMapFs()} @@ -83,10 +166,12 @@ func TestCSVFromOld(t *testing.T) { } sc := &CSV{ - CSVVersion: newCSVVer, - FromVersion: oldCSVVer, - pathPrefix: testDataDir, - OperatorName: operatorName, + Config: genutil.Config{ + OperatorName: operatorName, + }, + CSVVersion: newCSVVer, + FromVersion: oldCSVVer, + pathPrefix: testDataDir, } err := s.Execute(&input.Config{ProjectName: projectName}, sc) if err != nil { @@ -115,10 +200,14 @@ func TestCSVFromOld(t *testing.T) { func TestUpdateVersion(t *testing.T) { sc := &CSV{ - Input: input.Input{ProjectName: projectName}, - CSVVersion: newCSVVer, - pathPrefix: testDataDir, - OperatorName: operatorName, + Input: input.Input{ + ProjectName: projectName, + }, + Config: genutil.Config{ + OperatorName: operatorName, + }, + CSVVersion: newCSVVer, + pathPrefix: testDataDir, } csvExpBytes, err := ioutil.ReadFile(sc.getCSVPath(oldCSVVer)) if err != nil { diff --git a/internal/scaffold/olm-catalog/package_manifest.go b/internal/scaffold/olm-catalog/package_manifest.go index faa458ff901..57a0e7e7493 100644 --- a/internal/scaffold/olm-catalog/package_manifest.go +++ b/internal/scaffold/olm-catalog/package_manifest.go @@ -21,6 +21,7 @@ import ( "sort" "strings" + genutil "github.com/operator-framework/operator-sdk/internal/generate/util" "github.com/operator-framework/operator-sdk/internal/scaffold" "github.com/operator-framework/operator-sdk/internal/scaffold/input" registryutil "github.com/operator-framework/operator-sdk/internal/util/operator-registry" @@ -36,6 +37,7 @@ const PackageManifestFileExt = ".package.yaml" type PackageManifest struct { input.Input + genutil.Config // CSVVersion is the version of the CSV being updated. CSVVersion string @@ -45,8 +47,6 @@ type PackageManifest struct { // If ChannelIsDefault is true, Channel will be the package manifests' // default channel. ChannelIsDefault bool - // OperatorName is the operator's name, ex. app-operator - OperatorName string } var _ input.File = &PackageManifest{} diff --git a/internal/scaffold/olm-catalog/package_manifest_test.go b/internal/scaffold/olm-catalog/package_manifest_test.go index 94bc7ac6827..a920cf7956a 100644 --- a/internal/scaffold/olm-catalog/package_manifest_test.go +++ b/internal/scaffold/olm-catalog/package_manifest_test.go @@ -21,6 +21,7 @@ import ( "path/filepath" "testing" + genutil "github.com/operator-framework/operator-sdk/internal/generate/util" "github.com/operator-framework/operator-sdk/internal/scaffold" "github.com/operator-framework/operator-sdk/internal/scaffold/input" "github.com/operator-framework/operator-sdk/internal/util/diffutil" @@ -45,10 +46,12 @@ func TestPackageManifest(t *testing.T) { } pm := &PackageManifest{ + Config: genutil.Config{ + OperatorName: projectName, + }, CSVVersion: csvVer, Channel: "stable", ChannelIsDefault: true, - OperatorName: projectName, } err = s.Execute(cfg, pm) if err != nil { diff --git a/internal/scaffold/olm-catalog/testdata/deploy/olm-catalog/app-operator/0.1.0/app-operator.v0.1.0.clusterserviceversion.yaml b/internal/scaffold/olm-catalog/testdata/deploy/olm-catalog/app-operator/0.1.0/app-operator.v0.1.0.clusterserviceversion.yaml index b2c3c1a84a7..0315f522b58 100644 --- a/internal/scaffold/olm-catalog/testdata/deploy/olm-catalog/app-operator/0.1.0/app-operator.v0.1.0.clusterserviceversion.yaml +++ b/internal/scaffold/olm-catalog/testdata/deploy/olm-catalog/app-operator/0.1.0/app-operator.v0.1.0.clusterserviceversion.yaml @@ -9,13 +9,6 @@ metadata: spec: apiservicedefinitions: {} customresourcedefinitions: - owned: - - kind: AppService - name: appservice.example.com - version: v1alpha1 - - kind: AppService2 - name: appservice2.example.com - version: v1alpha2 required: - description: Represents a cluster of etcd nodes. displayName: etcd Cluster diff --git a/internal/scaffold/olm-catalog/testdata/deploy/olm-catalog/csv-config.yaml b/internal/scaffold/olm-catalog/testdata/deploy/olm-catalog/csv-config.yaml deleted file mode 100644 index c8281c290dc..00000000000 --- a/internal/scaffold/olm-catalog/testdata/deploy/olm-catalog/csv-config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -operator-path: "testdata/deploy/operator.yaml" -crd-cr-paths: - - "testdata/deploy/crds" - - "testdata/deploy/crds/app.example.com_appservices2_crd.yaml" -role-paths: - - "testdata/deploy/role.yaml"