diff --git a/cmd/operator-sdk/test/local.go b/cmd/operator-sdk/test/local.go index bb1a57454e..cc1594e334 100644 --- a/cmd/operator-sdk/test/local.go +++ b/cmd/operator-sdk/test/local.go @@ -15,6 +15,7 @@ package test import ( + "bytes" "errors" "fmt" "io/ioutil" @@ -307,7 +308,7 @@ func replaceImage(manifestPath, image string) error { } foundDeployment := false newManifest := []byte{} - scanner := internalk8sutil.NewYAMLScanner(yamlFile) + scanner := internalk8sutil.NewYAMLScanner(bytes.NewBuffer(yamlFile)) for scanner.Scan() { yamlSpec := scanner.Bytes() diff --git a/go.mod b/go.mod index ae5ce8b4c9..b2282c8732 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,8 @@ require ( github.com/mattn/go-isatty v0.0.12 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.1.2 + github.com/onsi/ginkgo v1.12.0 + github.com/onsi/gomega v1.9.0 github.com/operator-framework/api v0.3.5 github.com/operator-framework/operator-registry v1.12.2 github.com/pborman/uuid v1.2.0 diff --git a/internal/generate/clusterserviceversion/bases/clusterserviceversion.go b/internal/generate/clusterserviceversion/bases/clusterserviceversion.go new file mode 100644 index 0000000000..3c69700a69 --- /dev/null +++ b/internal/generate/clusterserviceversion/bases/clusterserviceversion.go @@ -0,0 +1,199 @@ +// 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 bases + +import ( + "bytes" + "fmt" + "io/ioutil" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/operator-sdk/internal/util/k8sutil" + "github.com/operator-framework/operator-sdk/internal/util/projutil" +) + +// ClusterServiceVersion configures the v1alpha1.ClusterServiceVersion +// that GetBase() returns. +type ClusterServiceVersion struct { + // BasePath is the path to the base being read. If empty, GetBase() returns + // a default base. + BasePath string + // OperatorName is the operator's name, ex. app-operator + OperatorName string + // OperatorType + OperatorType projutil.OperatorType + // APIsDir contains project API definition files. + APIsDir string + // GVKs are all GroupVersionKinds in the project. + GVKs []schema.GroupVersionKind + // Interactive turns on an interactive prompt. + Interactive bool + + // Fields for input to the base. + DisplayName string + Description string + Maturity string + Capabilities string + Keywords []string + Provider v1alpha1.AppLink + Links []v1alpha1.AppLink + Maintainers []v1alpha1.Maintainer + Icon []v1alpha1.Icon // TODO(estroz): read icon bytes from files. +} + +// GetBase returns a base v1alpha1.ClusterServiceVersion, populated +// either with default values or, if b.BasePath is set, bytes from disk. +func (b ClusterServiceVersion) GetBase() (base *v1alpha1.ClusterServiceVersion, err error) { + if b.BasePath != "" { + if base, err = readClusterServiceVersionBase(b.BasePath); err != nil { + return nil, fmt.Errorf("error reading existing ClusterServiceVersion base %s: %v", b.BasePath, err) + } + } else { + b.setDefaults() + base = b.makeNewBase() + } + + // Interactively fill in UI metadata. + if b.Interactive { + meta := &uiMetadata{} + meta.runInteractivePrompt() + meta.apply(base) + } + + if b.APIsDir != "" { + switch b.OperatorType { + case projutil.OperatorTypeGo: + if err := updateDescriptionsForGVKs(base, b.APIsDir, b.GVKs); err != nil { + return nil, fmt.Errorf("error generating ClusterServiceVersion base metadata: %w", err) + } + } + } + + return base, nil +} + +// setDefaults sets default values in b using b's existing values. +func (b *ClusterServiceVersion) setDefaults() { + if b.DisplayName == "" { + b.DisplayName = k8sutil.GetDisplayName(b.OperatorName) + } + if b.Description == "" { + b.Description = b.DisplayName + " description. TODO." + } + if b.Maturity == "" { + b.Maturity = "alpha" + } + if b.Capabilities == "" { + b.Capabilities = "Basic Install" + } + if len(b.Keywords) == 0 || b.Keywords[0] == "" { + b.Keywords = []string{b.OperatorName} + } + if len(b.Links) == 0 || b.Links[0] == (v1alpha1.AppLink{}) { + b.Links = []v1alpha1.AppLink{ + { + Name: b.DisplayName, + URL: fmt.Sprintf("https://%s.domain", b.OperatorName), + }, + } + } + if len(b.Icon) == 0 { + b.Icon = make([]v1alpha1.Icon, 1) + } + if b.Provider == (v1alpha1.AppLink{}) { + b.Provider = v1alpha1.AppLink{ + Name: "Provider Name", + URL: "https://your.domain", + } + } + if len(b.Maintainers) == 0 || b.Maintainers[0] == (v1alpha1.Maintainer{}) { + b.Maintainers = []v1alpha1.Maintainer{ + { + Name: "Maintainer Name", + Email: "your@email.com", + }, + } + } +} + +// makeNewBase returns a base v1alpha1.ClusterServiceVersion to modify. +func (b ClusterServiceVersion) makeNewBase() *v1alpha1.ClusterServiceVersion { + return &v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.ClusterServiceVersionAPIVersion, + Kind: v1alpha1.ClusterServiceVersionKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: b.OperatorName + ".vX.Y.Z", + Namespace: "placeholder", + Annotations: map[string]string{ + "capabilities": b.Capabilities, + "alm-examples": "[]", + }, + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + DisplayName: b.DisplayName, + Description: b.Description, + Provider: b.Provider, + Maintainers: b.Maintainers, + Links: b.Links, + Maturity: b.Maturity, + Keywords: b.Keywords, + Icon: b.Icon, + InstallModes: []v1alpha1.InstallMode{ + {Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true}, + {Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true}, + {Type: v1alpha1.InstallModeTypeMultiNamespace, Supported: false}, + {Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: true}, + }, + }, + } +} + +// readClusterServiceVersionBase returns the ClusterServiceVersion base at path. +// If no base is found, readClusterServiceVersionBase returns an error. +func readClusterServiceVersionBase(path string) (*v1alpha1.ClusterServiceVersion, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, 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 == v1alpha1.ClusterServiceVersionKind { + csv := &v1alpha1.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 ClusterServiceVersion manifest in %s", path) +} diff --git a/internal/generate/clusterserviceversion/bases/markers.go b/internal/generate/clusterserviceversion/bases/markers.go new file mode 100644 index 0000000000..cfe3a6fc56 --- /dev/null +++ b/internal/generate/clusterserviceversion/bases/markers.go @@ -0,0 +1,75 @@ +// 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 bases + +import ( + "errors" + "fmt" + "strings" + + "github.com/markbates/inflect" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/operator-framework/operator-sdk/internal/generate/olm-catalog/descriptor" +) + +// updateDescriptionsForGVKs updates csv with API metadata found in apisDir +// filtered by gvks. +func updateDescriptionsForGVKs(csv *v1alpha1.ClusterServiceVersion, apisDir string, + gvks []schema.GroupVersionKind) error { + + gvkMap := make(map[schema.GroupVersionKind]v1alpha1.CRDDescription) + for _, desc := range csv.Spec.CustomResourceDefinitions.Owned { + group := desc.Name + if split := strings.Split(desc.Name, "."); len(split) > 1 { + group = strings.Join(split[1:], ".") + } + // Parse CRD descriptors from source code comments and annotations. + gvk := schema.GroupVersionKind{ + Group: group, + Version: desc.Version, + Kind: desc.Kind, + } + gvkMap[gvk] = desc + } + + descriptions := []v1alpha1.CRDDescription{} + for _, gvk := range gvks { + newDescription, err := descriptor.GetCRDDescriptionForGVK(apisDir, gvk) + if err != nil { + if errors.Is(err, descriptor.ErrAPIDirNotExist) { + log.Debugf("Directory for API %s does not exist. Skipping CSV annotation parsing for API.", gvk) + } else if errors.Is(err, descriptor.ErrAPITypeNotFound) { + log.Debugf("No kind type found for API %s. Skipping CSV annotation parsing for API.", gvk) + } else { + // TODO: Should we ignore all CSV annotation parsing errors and simply log the error + // like we do for the above cases. + return fmt.Errorf("failed to set CRD descriptors for %s: %v", gvk, err) + } + // Keep the existing description and don't update on error + if desc, hasDesc := gvkMap[gvk]; hasDesc { + descriptions = append(descriptions, desc) + } + } else { + // Replace the existing description with the newly parsed one + newDescription.Name = inflect.Pluralize(strings.ToLower(gvk.Kind)) + "." + gvk.Group + descriptions = append(descriptions, newDescription) + } + } + csv.Spec.CustomResourceDefinitions.Owned = descriptions + return nil +} diff --git a/internal/generate/olm-catalog/csv_cmd_prompt.go b/internal/generate/clusterserviceversion/bases/metadata.go similarity index 77% rename from internal/generate/olm-catalog/csv_cmd_prompt.go rename to internal/generate/clusterserviceversion/bases/metadata.go index c5a49997cb..57917c3ee0 100644 --- a/internal/generate/olm-catalog/csv_cmd_prompt.go +++ b/internal/generate/clusterserviceversion/bases/metadata.go @@ -12,22 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -package olmcatalog +package bases import ( "strings" - olmapiv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-sdk/internal/util/projutil" ) -// InteractiveCSVCmd includes the list of CSV fields which would be asked +// uiMetadata includes the list of CSV fields which would be asked // to the user while generating CSV. -type interactiveCSVCmd struct { +type uiMetadata struct { // DisplayName is the name of the crd. DisplayName string - // Keyword is a list of keywords describing the operator. - Keywords []string // Description of the operator. Can include the features, limitations or // use-cases of the operator. Description string @@ -35,25 +34,26 @@ type interactiveCSVCmd struct { ProviderName string // URL related to the publishing entity behind the operator. ProviderURL string + // Keyword is a list of keywords describing the operator. + Keywords []string // Maintainers is the list of organizational entities maintaining the operator. Maintainers []string + // FEAT: read icon bytes from files. } -// generateInteractivePrompt generates the prompts for user to provide input to the CSV -// fields. -func (s *interactiveCSVCmd) generateInteractivePrompt() { +// runInteractivePrompt prompts the user to provide input to uiMetadata fields. +func (s *uiMetadata) runInteractivePrompt() { s.DisplayName = projutil.GetRequiredInput("Display name for the operator") - s.Keywords = projutil.GetStringArray("Comma-separated list of keywords for your operator") s.Description = projutil.GetRequiredInput("Description for the operator") s.ProviderName = projutil.GetRequiredInput("Provider's name for the operator") s.ProviderURL = projutil.GetOptionalInput("Any relevant URL for the provider name") + s.Keywords = projutil.GetStringArray("Comma-separated list of keywords for your operator") s.Maintainers = projutil.GetStringArray("Comma-separated list of maintainers and their emails" + " (e.g. 'name1:email1, name2:email2')") } -// addUImetadata populates the CSV with the data obtained from the interactive -// prompts which appear while generating CSV. -func (s *interactiveCSVCmd) addUImetadata(csv *olmapiv1alpha1.ClusterServiceVersion) { +// apply populates the CSV with the data in s. +func (s uiMetadata) apply(csv *v1alpha1.ClusterServiceVersion) { if s.DisplayName != "" { csv.Spec.DisplayName = s.DisplayName } @@ -67,11 +67,11 @@ func (s *interactiveCSVCmd) addUImetadata(csv *olmapiv1alpha1.ClusterServiceVers } if len(s.Maintainers) != 0 { - maintainers := make([]olmapiv1alpha1.Maintainer, 0) + maintainers := make([]v1alpha1.Maintainer, 0) for _, entity := range s.Maintainers { entityDetails := strings.Split(entity, ":") if len(entityDetails) == 2 { - m := olmapiv1alpha1.Maintainer{} + m := v1alpha1.Maintainer{} m.Name, m.Email = entityDetails[0], entityDetails[1] maintainers = append(maintainers, m) } @@ -80,12 +80,11 @@ func (s *interactiveCSVCmd) addUImetadata(csv *olmapiv1alpha1.ClusterServiceVers } if s.ProviderName != "" { - provider := olmapiv1alpha1.AppLink{} + provider := v1alpha1.AppLink{} provider.Name = s.ProviderName if s.ProviderURL != "" { provider.URL = s.ProviderURL } csv.Spec.Provider = provider } - } diff --git a/internal/generate/clusterserviceversion/bases/metadata_test.go b/internal/generate/clusterserviceversion/bases/metadata_test.go new file mode 100644 index 0000000000..2605ee2fdd --- /dev/null +++ b/internal/generate/clusterserviceversion/bases/metadata_test.go @@ -0,0 +1,68 @@ +// 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 bases + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" +) + +func TestMetadata(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Metadata Suite") +} + +var _ = Describe("Metadata", func() { + + meta := uiMetadata{ + DisplayName: "Memcached Application", + Keywords: []string{"memcached", "app"}, + Description: "Main enterprise application providing business critical features with " + + "high availability and no manual intervention.", + ProviderName: "Example", + ProviderURL: "www.example.com", + Maintainers: []string{"Some Corp:corp@example.com"}, + } + + It("populates an empty CSV", func() { + csv := v1alpha1.ClusterServiceVersion{} + + meta.apply(&csv) + + Expect(csv.Spec.DisplayName).To(Equal(meta.DisplayName)) + Expect(csv.Spec.Keywords).To(Equal(meta.Keywords)) + Expect(csv.Spec.Description).To(Equal(meta.Description)) + Expect(csv.Spec.Maintainers).To(Equal([]v1alpha1.Maintainer{{Name: "Some Corp", Email: "corp@example.com"}})) + Expect(csv.Spec.Provider).To(Equal(v1alpha1.AppLink{Name: meta.ProviderName, URL: meta.ProviderURL})) + }) + + It("populates a CSV with existing values", func() { + b := ClusterServiceVersion{OperatorName: "test-operator"} + b.setDefaults() + csv := b.makeNewBase() + + meta.apply(csv) + + Expect(csv.Spec.DisplayName).To(Equal(meta.DisplayName)) + Expect(csv.Spec.Keywords).To(Equal(meta.Keywords)) + Expect(csv.Spec.Description).To(Equal(meta.Description)) + Expect(csv.Spec.Maintainers).To(Equal([]v1alpha1.Maintainer{{Name: "Some Corp", Email: "corp@example.com"}})) + Expect(csv.Spec.Provider).To(Equal(v1alpha1.AppLink{Name: meta.ProviderName, URL: meta.ProviderURL})) + }) +}) diff --git a/internal/generate/crd/crd.go b/internal/generate/crd/crd.go index b7e1f2205b..79849efb97 100644 --- a/internal/generate/crd/crd.go +++ b/internal/generate/crd/crd.go @@ -15,6 +15,7 @@ package crd import ( + "bytes" "errors" "fmt" "io/ioutil" @@ -157,7 +158,7 @@ func (g Generator) generateGo() (map[string][]byte, error) { if err != nil { return nil, fmt.Errorf("error reading cached CRD file %s: %w", path, err) } - scanner := k8sutil.NewYAMLScanner(b) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) modifiedCRD := []byte{} for scanner.Scan() { crd := unstructured.Unstructured{} diff --git a/internal/generate/olm-catalog/csv.go b/internal/generate/olm-catalog/csv.go index 69c301d2ec..1e5135962f 100644 --- a/internal/generate/olm-catalog/csv.go +++ b/internal/generate/olm-catalog/csv.go @@ -15,25 +15,25 @@ package olmcatalog import ( + "bytes" "errors" "fmt" "io/ioutil" "os" "path/filepath" - "regexp" "strings" + "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion/bases" "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" - olmversion "github.com/operator-framework/api/pkg/lib/version" olmapiv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-registry/pkg/lib/bundle" log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/yaml" ) @@ -56,7 +56,7 @@ type BundleGenerator struct { CSVVersion string // These directories specify where to retrieve manifests from. DeployDir, ApisDir, CRDsDir string - // Interactivepreference refers to the user preference to enable/disable + // InteractivePreference refers to the user preference to enable/disable // interactive prompts. InteractivePreference projutil.InteractiveLevel // updateCRDs directs the generator to also add CustomResourceDefinition @@ -73,9 +73,6 @@ type BundleGenerator struct { // toBundleDir is the bundle directory filepath where the CSV will be generated // This is set according to the generator's OutputDir toBundleDir string - // Subcommand includes the list of csv metadata fields which the user - // provides to the interactive prompts which appear while generating csv. - interactiveCSVCmd interactiveCSVCmd } // getBundleDirs gets directory names of the new bundle and, if it exists, @@ -96,7 +93,7 @@ func getBundleDirs(operatorName, csvVersion, outputDir, deployDir string) (toBun switch { case isBundleDirExist(outputOperatorDir, bundle.ManifestsDir): fromBundleDir = filepath.Join(outputOperatorDir, bundle.ManifestsDir) - case isDirExist(toBundleDir): + case isExist(toBundleDir): fromBundleDir = toBundleDir case isBundleDirExist(outputOperatorDir, csvVersion): // Updating an existing CSV version @@ -110,7 +107,7 @@ func getBundleDirs(operatorName, csvVersion, outputDir, deployDir string) (toBun switch { case isBundleDirExist(defaultOperatorDir, bundle.ManifestsDir): fromBundleDir = filepath.Join(defaultOperatorDir, bundle.ManifestsDir) - case isDirExist(toBundleDir): + case isExist(toBundleDir): fromBundleDir = toBundleDir case isBundleDirExist(defaultOperatorDir, csvVersion): // Updating an existing CSV version @@ -186,13 +183,6 @@ func (g *BundleGenerator) setDefaults() { func (g BundleGenerator) Generate() error { g.setDefaults() - csvPath := g.getCSVPath(g.OperatorName) - - if (g.InteractivePreference == projutil.InteractiveSoftOff && !isFileExist(csvPath)) || - g.InteractivePreference == projutil.InteractiveOnAll { - g.interactiveCSVCmd.generateInteractivePrompt() - } - fileMap, err := g.generateCSV() if err != nil { return err @@ -234,36 +224,20 @@ func getCSVFileNameLegacy(name, version string) string { return getCSVName(strings.ToLower(name), version) + csvYamlFileExt } -// getCSVPath returns the location of CSV in the project. -func (g BundleGenerator) getCSVPath(operatorName string) string { - return filepath.Join(g.fromBundleDir, getCSVFileName(operatorName)) -} - func (g BundleGenerator) generateCSV() (fileMap map[string][]byte, err error) { - // Get current CSV to update, otherwise start with a fresh CSV. - var csv *olmapiv1alpha1.ClusterServiceVersion - if g.fromBundleDir != "" && !g.noUpdate { - // TODO: If bundle dir exists, but the CSV file does not - // then we should create a new one and not return an error. - if csv, err = getCSVFromDir(g.fromBundleDir); err != nil { - return nil, err - } - // TODO: validate existing CSV. - if err = g.updateCSVVersions(csv); err != nil { - return nil, err - } - } else { - if csv, err = newCSV(g.OperatorName, g.CSVVersion); err != nil { - return nil, err - } + + csv, err := g.getBase() + if err != nil { + return nil, err } - if err = g.updateCSVFromManifests(csv); err != nil { + if err = g.updateCSVVersions(csv); err != nil { return nil, err } - // populate the csv with the metadata obtained from the user. - g.interactiveCSVCmd.addUImetadata(csv) + if err = g.updateCSVFromManifests(csv); err != nil { + return nil, err + } path := "" if g.MakeManifests { @@ -293,57 +267,54 @@ func (g BundleGenerator) generateCSV() (fileMap map[string][]byte, err error) { return fileMap, nil } -// newCSV sets all csv fields that should be populated by a user -// to sane defaults. -func newCSV(name, version string) (*olmapiv1alpha1.ClusterServiceVersion, error) { - csv := &olmapiv1alpha1.ClusterServiceVersion{ - TypeMeta: metav1.TypeMeta{ - APIVersion: olmapiv1alpha1.ClusterServiceVersionAPIVersion, - Kind: olmapiv1alpha1.ClusterServiceVersionKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: getCSVName(name, version), - Namespace: "placeholder", - Annotations: map[string]string{ - "capabilities": "Basic Install", - "alm-examples": "[]", - }, - }, - Spec: olmapiv1alpha1.ClusterServiceVersionSpec{ - DisplayName: k8sutil.GetDisplayName(name), - Provider: olmapiv1alpha1.AppLink{}, - Maintainers: make([]olmapiv1alpha1.Maintainer, 1), - Links: []olmapiv1alpha1.AppLink{}, - Maturity: "alpha", - Icon: make([]olmapiv1alpha1.Icon, 1), - Keywords: make([]string, 1), - 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: olmapiv1alpha1.InstallStrategyNameDeployment, - StrategySpec: olmapiv1alpha1.StrategyDetailsDeployment{ - Permissions: []olmapiv1alpha1.StrategyDeploymentPermissions{}, - ClusterPermissions: []olmapiv1alpha1.StrategyDeploymentPermissions{}, - DeploymentSpecs: []olmapiv1alpha1.StrategyDeploymentSpec{}, - }, - }, - }, - } - - // An empty version string will evaluate to "v0.0.0". - if version != "" { - ver, err := semver.Parse(version) - if err != nil { - return nil, err +// getBase either reads an existing CSV from fromBundleDir or creates a new one. +func (g BundleGenerator) getBase() (*olmapiv1alpha1.ClusterServiceVersion, error) { + crds, err := k8sutil.GetCustomResourceDefinitions(g.CRDsDir) + if err != nil { + return nil, err + } + var gvks []schema.GroupVersionKind + for _, crd := range crds { + nameSplit := strings.SplitN(crd.GetName(), ".", 2) + group := crd.GetName() + if len(nameSplit) > 1 { + group = nameSplit[1] + } + for _, version := range crd.Spec.Versions { + gvks = append(gvks, schema.GroupVersionKind{ + Group: group, + Version: version.Name, + Kind: crd.Spec.Names.Kind, + }) } - csv.Spec.Version = olmversion.OperatorVersion{Version: ver} } - return csv, nil + 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. @@ -387,39 +358,24 @@ func getEmptyRequiredCSVFields(csv *olmapiv1alpha1.ClusterServiceVersion) (field // 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) error { - // If csvVersion is the same as the current version, or empty - // bevause manifests/ is being.Updated, no version update is needed. - oldVer, newVer := csv.Spec.Version.String(), g.CSVVersion - if newVer == "" || oldVer == newVer { - return nil - } +func (g BundleGenerator) updateCSVVersions(csv *olmapiv1alpha1.ClusterServiceVersion) (err error) { - // Replace all references to the old operator name. - oldCSVName := getCSVName(g.OperatorName, oldVer) - oldRe, err := regexp.Compile(fmt.Sprintf("\\b%s\\b", regexp.QuoteMeta(oldCSVName))) - if err != nil { - return fmt.Errorf("error compiling CSV name regexp %s: %v", oldRe, err) - } - b, err := yaml.Marshal(csv) - if err != nil { - return err - } + oldVer, newVer := csv.Spec.Version.String(), g.CSVVersion newCSVName := getCSVName(g.OperatorName, newVer) - b = oldRe.ReplaceAll(b, []byte(newCSVName)) - *csv = olmapiv1alpha1.ClusterServiceVersion{} - if err = yaml.Unmarshal(b, csv); err != nil { - return fmt.Errorf("error unmarshalling CSV %s after replacing old CSV name: %v", csv.GetName(), err) - } + oldCSVName := getCSVName(g.OperatorName, oldVer) - ver, err := semver.Parse(g.CSVVersion) - if err != nil { - return err + // 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.Spec.Version = olmversion.OperatorVersion{Version: ver} - csv.Spec.Replaces = oldCSVName - return nil + csv.SetName(newCSVName) + csv.Spec.Version.Version, err = semver.Parse(newVer) + return err } // updateCSVFromManifests gathers relevant data from generated and @@ -440,7 +396,7 @@ func (g BundleGenerator) updateCSVFromManifests(csv *olmapiv1alpha1.ClusterServi if err != nil { return err } - scanner := k8sutil.NewYAMLScanner(b) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) for scanner.Scan() { manifest := scanner.Bytes() typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) @@ -475,7 +431,7 @@ func (g BundleGenerator) updateCSVFromManifests(csv *olmapiv1alpha1.ClusterServi } // Add CRDs from input. - if isDirExist(g.CRDsDir) { + if isExist(g.CRDsDir) { collection.CustomResourceDefinitions, err = k8sutil.GetCustomResourceDefinitions(g.CRDsDir) if err != nil { return err @@ -495,16 +451,6 @@ func (g BundleGenerator) updateCSVFromManifests(csv *olmapiv1alpha1.ClusterServi return fmt.Errorf("error building CSV: %v", err) } - // Update descriptions from the APIs dir. - // FEAT(estroz): customresourcedefinition should not be updated for - // Ansible and Helm CSV's until annotated updates are implemented. - if projutil.IsOperatorGo() { - err = updateDescriptions(csv, g.ApisDir) - if err != nil { - return fmt.Errorf("error updating CSV customresourcedefinitions: %w", err) - } - } - // Finally sort all updated fields. sortUpdates(csv) diff --git a/internal/generate/olm-catalog/csv_go_test.go b/internal/generate/olm-catalog/csv_go_test.go index 475a64051c..a2962be75e 100644 --- a/internal/generate/olm-catalog/csv_go_test.go +++ b/internal/generate/olm-catalog/csv_go_test.go @@ -21,11 +21,13 @@ import ( "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" ) @@ -186,15 +188,16 @@ func TestGoCSVNew(t *testing.T) { defer cleanupFunc() g := BundleGenerator{ - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: csvVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: false, + 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() @@ -217,15 +220,16 @@ func TestGoCSVUpdate(t *testing.T) { defer cleanupFunc() g := BundleGenerator{ - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: csvVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: false, + 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() @@ -247,15 +251,16 @@ func TestGoCSVUpgrade(t *testing.T) { defer cleanupFunc() g := BundleGenerator{ - 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, + 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() @@ -277,15 +282,16 @@ func TestGoCSVNewManifests(t *testing.T) { defer cleanupFunc() g := BundleGenerator{ - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: csvVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: true, + 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() @@ -308,15 +314,16 @@ func TestGoCSVUpdateManifests(t *testing.T) { defer cleanupFunc() g := BundleGenerator{ - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: csvVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: true, + 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() @@ -338,15 +345,16 @@ func TestGoCSVNewWithInvalidDeployDir(t *testing.T) { defer cleanupFunc() g := BundleGenerator{ - OperatorName: testProjectName, - DeployDir: "notExist", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: "notExist", - OutputDir: "deploy", - CSVVersion: notExistVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: false, + 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() @@ -362,15 +370,16 @@ func TestGoCSVNewWithEmptyDeployDir(t *testing.T) { defer cleanupFunc() g := BundleGenerator{ - OperatorName: testProjectName, - DeployDir: "emptydir", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: "emptydir", - OutputDir: "emptydir", - CSVVersion: notExistVersion, - FromVersion: "", - UpdateCRDs: false, - MakeManifests: false, + 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() @@ -380,10 +389,20 @@ func TestGoCSVNewWithEmptyDeployDir(t *testing.T) { } // Create an empty CSV. - csv, err := newCSV(testProjectName, notExistVersion) + 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) @@ -396,49 +415,6 @@ func TestGoCSVNewWithEmptyDeployDir(t *testing.T) { } } -func TestCSVPrompt(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - s := interactiveCSVCmd{ - DisplayName: "Memcached Application", - Keywords: []string{"memcached", "app"}, - Description: "Main enterprise application providing business critical features with " + - "high availability and no manual intervention.", - ProviderName: "Example", - ProviderURL: "www.example.com", - Maintainers: []string{"Some Corp:corp@example.com"}, - } - - g := BundleGenerator{ - OperatorName: testProjectName, - DeployDir: "deploy", - ApisDir: filepath.Join("pkg", "apis"), - CRDsDir: filepath.Join("deploy", "crds_v1beta1"), - OutputDir: "deploy", - CSVVersion: "0.0.2", - FromVersion: "", - UpdateCRDs: false, - MakeManifests: true, - interactiveCSVCmd: s, - } - - g.setDefaults() - fileMap, err := g.generateCSV() - if err != nil { - t.Fatalf("Failed to execute CSV generator: %v", err) - } - - csvExpFile := getCSVFileName(testProjectName) - csvExpBytes := readFile(t, filepath.Join(OLMCatalogDir, testProjectName, "manifests", 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 TestUpdateCSVVersion(t *testing.T) { cleanupFunc := chDirWithCleanup(t, testGoDataDir) defer cleanupFunc() @@ -449,15 +425,16 @@ func TestUpdateCSVVersion(t *testing.T) { } g := BundleGenerator{ - 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, + 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 { diff --git a/internal/generate/olm-catalog/csv_updaters.go b/internal/generate/olm-catalog/csv_updaters.go index 6c7dbe6e60..3277911825 100644 --- a/internal/generate/olm-catalog/csv_updaters.go +++ b/internal/generate/olm-catalog/csv_updaters.go @@ -18,13 +18,11 @@ import ( "bytes" "crypto/sha256" "encoding/json" - goerrors "errors" "fmt" "sort" "strings" "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-sdk/internal/generate/olm-catalog/descriptor" "github.com/operator-framework/operator-sdk/pkg/k8sutil" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" @@ -313,45 +311,6 @@ func (c manifestCollection) applyCustomResourceDefinitions(csv *operatorsv1alpha csv.Spec.CustomResourceDefinitions.Owned = ownedDescs } -// updateDescriptions parses APIs in apisDir for code and annotations that -// can build a verbose crdDescription and updates existing crdDescriptions in -// csv. If no code/annotations are found, the crdDescription is appended as-is. -func updateDescriptions(csv *operatorsv1alpha1.ClusterServiceVersion, apisDir string) error { - updatedDescriptions := []operatorsv1alpha1.CRDDescription{} - for _, currDescription := range csv.Spec.CustomResourceDefinitions.Owned { - group := currDescription.Name - if split := strings.Split(currDescription.Name, "."); len(split) > 1 { - group = strings.Join(split[1:], ".") - } - // Parse CRD descriptors from source code comments and annotations. - gvk := schema.GroupVersionKind{ - Group: group, - Version: currDescription.Version, - Kind: currDescription.Kind, - } - newDescription, err := descriptor.GetCRDDescriptionForGVK(apisDir, gvk) - if err != nil { - if goerrors.Is(err, descriptor.ErrAPIDirNotExist) { - log.Debugf("Directory for API %s does not exist. Skipping CSV annotation parsing for API.", gvk) - } else if goerrors.Is(err, descriptor.ErrAPITypeNotFound) { - log.Debugf("No kind type found for API %s. Skipping CSV annotation parsing for API.", gvk) - } else { - // TODO: Should we ignore all CSV annotation parsing errors and simply log the error - // like we do for the above cases. - return fmt.Errorf("failed to set CRD descriptors for %s: %v", gvk, err) - } - // Keep the existing description and don't update on error - updatedDescriptions = append(updatedDescriptions, currDescription) - } else { - // Replace the existing description with the newly parsed one - newDescription.Name = currDescription.Name - updatedDescriptions = append(updatedDescriptions, newDescription) - } - } - csv.Spec.CustomResourceDefinitions.Owned = updatedDescriptions - return nil -} - // applyCustomResources updates csv's "alm-examples" annotation with the // Custom Resources in the collection. func (c manifestCollection) applyCustomResources(csv *operatorsv1alpha1.ClusterServiceVersion) error { diff --git a/internal/generate/olm-catalog/csv_util.go b/internal/generate/olm-catalog/csv_util.go index 101cf4ec19..d43e657af2 100644 --- a/internal/generate/olm-catalog/csv_util.go +++ b/internal/generate/olm-catalog/csv_util.go @@ -15,6 +15,8 @@ package olmcatalog import ( + "bytes" + "errors" "fmt" "io/ioutil" "os" @@ -31,13 +33,17 @@ import ( // isBundleDirExist returns true if "parentDir/version" exists on disk. func isBundleDirExist(parentDir, version string) bool { // Ensure full path is constructed. - return version != "" && isDirExist(filepath.Join(parentDir, version)) + return version != "" && isExist(filepath.Join(parentDir, version)) } -// isDirExist returns true if dir exists on disk. -func isDirExist(dir string) bool { - info, err := os.Stat(dir) - return (err == nil && info.IsDir()) || os.IsExist(err) +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 @@ -59,7 +65,7 @@ func addCustomResourceDefinitionsToFileSet(dir string, fileMap map[string][]byte return fmt.Errorf("error reading manifest %s: %v", fromPath, err) } - scanner := k8sutil.NewYAMLScanner(b) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) manifests := []byte{} for scanner.Scan() { manifest := scanner.Bytes() @@ -104,7 +110,7 @@ func getCSVFromDir(dir string) (*olmapiv1alpha1.ClusterServiceVersion, error) { return nil, fmt.Errorf("error reading manifest %s: %v", path, err) } - scanner := k8sutil.NewYAMLScanner(b) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) for scanner.Scan() { manifest := scanner.Bytes() typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) diff --git a/internal/generate/olm-catalog/package_manifest.go b/internal/generate/olm-catalog/package_manifest.go index bf4a891f06..c7f6c1c277 100644 --- a/internal/generate/olm-catalog/package_manifest.go +++ b/internal/generate/olm-catalog/package_manifest.go @@ -51,18 +51,6 @@ type PkgGenerator struct { fileName string } -func isFileExist(path string) bool { - _, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - return false - } - // TODO: return and handle this error - log.Fatalf("Failed to stat %s: %v", path, err) - } - return true -} - // getPkgFileName will return the name of the PackageManifestFile func getPkgFileName(operatorName string) string { return strings.ToLower(operatorName) + packageManifestFileExt @@ -131,7 +119,7 @@ func (g PkgGenerator) buildPackageManifest() (registry.PackageManifest, error) { pkgManifestOutputDir := filepath.Join(g.OutputDir, OLMCatalogChildDir, g.OperatorName) path := filepath.Join(pkgManifestOutputDir, g.fileName) pkg := registry.PackageManifest{} - if isFileExist(path) { + if isExist(path) { b, err := ioutil.ReadFile(path) if err != nil { return pkg, fmt.Errorf("failed to read package manifest %s: %v", path, err) diff --git a/internal/generate/testdata/go/deploy/olm-catalog/memcached-operator/noupdate/memcached-operator.v0.0.3.clusterserviceversion.yaml b/internal/generate/testdata/go/deploy/olm-catalog/memcached-operator/noupdate/memcached-operator.v0.0.3.clusterserviceversion.yaml index de628f997e..31afb9a1d1 100644 --- a/internal/generate/testdata/go/deploy/olm-catalog/memcached-operator/noupdate/memcached-operator.v0.0.3.clusterserviceversion.yaml +++ b/internal/generate/testdata/go/deploy/olm-catalog/memcached-operator/noupdate/memcached-operator.v0.0.3.clusterserviceversion.yaml @@ -58,6 +58,7 @@ spec: displayName: Nodes path: nodes version: v1alpha1 + description: Memcached Operator description. TODO. displayName: Memcached Operator icon: - base64data: "" @@ -166,11 +167,17 @@ spec: - supported: true type: AllNamespaces keywords: - - "" + - memcached-operator + links: + - name: Memcached Operator + url: https://memcached-operator.domain maintainers: - - {} + - email: your@email.com + name: Maintainer Name maturity: alpha - provider: {} + provider: + name: Provider Name + url: https://your.domain version: 0.0.3 webhookdefinitions: - admissionReviewVersions: null diff --git a/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.1/memcached-operator.v0.0.1.clusterserviceversion.yaml b/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.1/memcached-operator.v0.0.1.clusterserviceversion.yaml index 65825e9192..d8377e4fd3 100644 --- a/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.1/memcached-operator.v0.0.1.clusterserviceversion.yaml +++ b/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.1/memcached-operator.v0.0.1.clusterserviceversion.yaml @@ -37,6 +37,7 @@ spec: displayName: Nodes path: nodes version: v1alpha1 + description: Memcached Operator description. TODO. displayName: Memcached Operator icon: - base64data: "" @@ -145,9 +146,15 @@ spec: - supported: true type: AllNamespaces keywords: - - "" + - memcached-operator + links: + - name: Memcached Operator + url: https://memcached-operator.domain maintainers: - - {} + - email: your@email.com + name: Maintainer Name maturity: alpha - provider: {} + provider: + name: Provider Name + url: https://your.domain version: 0.0.1 diff --git a/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.3/memcached-operator.v0.0.3.clusterserviceversion.yaml b/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.3/memcached-operator.v0.0.3.clusterserviceversion.yaml index 3e3d7f26f8..e5c1982b18 100644 --- a/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.3/memcached-operator.v0.0.3.clusterserviceversion.yaml +++ b/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.3/memcached-operator.v0.0.3.clusterserviceversion.yaml @@ -37,6 +37,7 @@ spec: displayName: Nodes path: nodes version: v1alpha1 + description: Memcached Operator description. TODO. displayName: Memcached Operator icon: - base64data: "" @@ -145,10 +146,16 @@ spec: - supported: true type: AllNamespaces keywords: - - "FooBar" - - "These keywords must be preserved in the CSV update tests from 0.0.3 to 0.0.4" + - FooBar + - These keywords must be preserved in the CSV update tests from 0.0.3 to 0.0.4 + links: + - name: Memcached Operator + url: https://memcached-operator.domain maintainers: - - {} + - email: your@email.com + name: Maintainer Name maturity: alpha - provider: {} + provider: + name: Provider Name + url: https://your.domain version: 0.0.3 diff --git a/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.4/memcached-operator.v0.0.4.clusterserviceversion.yaml b/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.4/memcached-operator.v0.0.4.clusterserviceversion.yaml index d40de55874..6972ee00bb 100644 --- a/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.4/memcached-operator.v0.0.4.clusterserviceversion.yaml +++ b/internal/generate/testdata/non-standard-layout/expected-catalog/olm-catalog/memcached-operator/0.0.4/memcached-operator.v0.0.4.clusterserviceversion.yaml @@ -37,6 +37,7 @@ spec: displayName: Nodes path: nodes version: v1alpha1 + description: Memcached Operator description. TODO. displayName: Memcached Operator icon: - base64data: "" @@ -147,9 +148,15 @@ spec: keywords: - FooBar - These keywords must be preserved in the CSV update tests from 0.0.3 to 0.0.4 + links: + - name: Memcached Operator + url: https://memcached-operator.domain maintainers: - - {} + - email: your@email.com + name: Maintainer Name maturity: alpha - provider: {} + provider: + name: Provider Name + url: https://your.domain replaces: memcached-operator.v0.0.3 version: 0.0.4 diff --git a/internal/olm/operator/operator_manager.go b/internal/olm/operator/operator_manager.go index 365bd9a7bb..ca4c333d2b 100644 --- a/internal/olm/operator/operator_manager.go +++ b/internal/olm/operator/operator_manager.go @@ -15,6 +15,7 @@ package olm import ( + "bytes" "context" "fmt" "io/ioutil" @@ -197,7 +198,7 @@ func readObjectsFromFile(path string) (objs []*unstructured.Unstructured, err er if err != nil { return nil, err } - scanner := k8sutil.NewYAMLScanner(b) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) for scanner.Scan() { b, err := yaml.YAMLToJSON(scanner.Bytes()) if err != nil { diff --git a/internal/scorecard/plugins/resource_handler.go b/internal/scorecard/plugins/resource_handler.go index 9501839976..dec24de0d6 100644 --- a/internal/scorecard/plugins/resource_handler.go +++ b/internal/scorecard/plugins/resource_handler.go @@ -94,7 +94,7 @@ func createFromYAMLFile(cfg BasicAndOLMPluginConfig, yamlPath string) error { if err != nil { return fmt.Errorf("failed to read file %s: %v", yamlPath, err) } - scanner := internalk8sutil.NewYAMLScanner(yamlSpecs) + scanner := internalk8sutil.NewYAMLScanner(bytes.NewBuffer(yamlSpecs)) for scanner.Scan() { obj := &unstructured.Unstructured{} jsonSpec, err := yaml.YAMLToJSON(scanner.Bytes()) @@ -405,7 +405,7 @@ func getProxyLogs(proxyPod *v1.Pod) (string, error) { func getGVKs(yamlFile []byte) ([]schema.GroupVersionKind, error) { var gvks []schema.GroupVersionKind - scanner := internalk8sutil.NewYAMLScanner(yamlFile) + scanner := internalk8sutil.NewYAMLScanner(bytes.NewBuffer(yamlFile)) for scanner.Scan() { yamlSpec := scanner.Bytes() diff --git a/internal/util/k8sutil/api.go b/internal/util/k8sutil/api.go index 1b00bc9ab5..34817013f2 100644 --- a/internal/util/k8sutil/api.go +++ b/internal/util/k8sutil/api.go @@ -15,6 +15,7 @@ package k8sutil import ( + "bytes" "fmt" "io/ioutil" "path" @@ -46,7 +47,7 @@ func GetCustomResourceDefinitions(crdsDir string) (crds []apiextv1beta1.CustomRe return nil, fmt.Errorf("error reading manifest %s: %w", path, err) } - scanner := NewYAMLScanner(b) + scanner := NewYAMLScanner(bytes.NewBuffer(b)) for scanner.Scan() { manifest := scanner.Bytes() typeMeta, err := GetTypeMetaFromBytes(manifest) diff --git a/internal/util/k8sutil/scan.go b/internal/util/k8sutil/scan.go index 85deb10e28..14e07905b7 100644 --- a/internal/util/k8sutil/scan.go +++ b/internal/util/k8sutil/scan.go @@ -34,9 +34,8 @@ type Scanner struct { done bool // Scan has finished. } -func NewYAMLScanner(b []byte) *Scanner { - r := bufio.NewReader(bytes.NewBuffer(b)) - return &Scanner{reader: k8syaml.NewYAMLReader(r)} +func NewYAMLScanner(r io.Reader) *Scanner { + return &Scanner{reader: k8syaml.NewYAMLReader(bufio.NewReader(r))} } func (s *Scanner) Err() error { diff --git a/pkg/test/resource_creator.go b/pkg/test/resource_creator.go index 3b3088e523..dd045b95de 100644 --- a/pkg/test/resource_creator.go +++ b/pkg/test/resource_creator.go @@ -15,6 +15,7 @@ package test import ( + "bytes" goctx "context" "fmt" "io/ioutil" @@ -100,7 +101,7 @@ func (ctx *Context) createFromYAML(yamlFile []byte, skipIfExists bool, cleanupOp if err != nil { return err } - scanner := k8sutil.NewYAMLScanner(yamlFile) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(yamlFile)) for scanner.Scan() { yamlSpec := scanner.Bytes()