diff --git a/internal/generate/clusterserviceversion/clusterserviceversion_updaters.go b/internal/generate/clusterserviceversion/clusterserviceversion_updaters.go new file mode 100644 index 0000000000..92ea84c5ad --- /dev/null +++ b/internal/generate/clusterserviceversion/clusterserviceversion_updaters.go @@ -0,0 +1,279 @@ +// 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 clusterserviceversion + +import ( + "encoding/json" + "errors" + "fmt" + "sort" + "strings" + + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/api/pkg/validation" + "github.com/operator-framework/operator-registry/pkg/registry" + log "github.com/sirupsen/logrus" + admissionregv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/version" + + "github.com/operator-framework/operator-sdk/internal/generate/collector" +) + +// ApplyTo applies relevant manifests in c to csv, sorts the applied updates, +// and validates the result. +func ApplyTo(c *collector.Manifests, csv *operatorsv1alpha1.ClusterServiceVersion) error { + // Apply manifests to the CSV object. + if err := apply(c, csv); err != nil { + return fmt.Errorf("error updating ClusterServiceVersion: %v", err) + } + + // Sort all updated fields. + sortUpdates(csv) + + return validate(csv) +} + +// apply applies relevant manifests in c to csv. +func apply(c *collector.Manifests, csv *operatorsv1alpha1.ClusterServiceVersion) error { + strategy := getCSVInstallStrategy(csv) + switch strategy.StrategyName { + case operatorsv1alpha1.InstallStrategyNameDeployment: + applyRoles(c, &strategy.StrategySpec) + applyClusterRoles(c, &strategy.StrategySpec) + applyDeployments(c, &strategy.StrategySpec) + } + csv.Spec.InstallStrategy = strategy + + applyCustomResourceDefinitions(c, csv) + if err := applyCustomResources(c, csv); err != nil { + return fmt.Errorf("error applying Custom Resource: %v", err) + } + applyWebhooks(c, csv) + return nil +} + +// Get install strategy from csv. +func getCSVInstallStrategy(csv *operatorsv1alpha1.ClusterServiceVersion) operatorsv1alpha1.NamedInstallStrategy { + // Default to a deployment strategy if none found. + if csv.Spec.InstallStrategy.StrategyName == "" { + csv.Spec.InstallStrategy.StrategyName = operatorsv1alpha1.InstallStrategyNameDeployment + } + return csv.Spec.InstallStrategy +} + +// applyRoles updates strategy's permissions with the Roles in the collector. +func applyRoles(c *collector.Manifests, strategy *operatorsv1alpha1.StrategyDetailsDeployment) { + perms := []operatorsv1alpha1.StrategyDeploymentPermissions{} + for _, role := range c.Roles { + perms = append(perms, operatorsv1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: role.GetName(), + Rules: role.Rules, + }) + } + strategy.Permissions = perms +} + +// applyClusterRoles updates strategy's cluserPermissions with the ClusterRoles +// in the collector. +func applyClusterRoles(c *collector.Manifests, strategy *operatorsv1alpha1.StrategyDetailsDeployment) { + perms := []operatorsv1alpha1.StrategyDeploymentPermissions{} + for _, role := range c.ClusterRoles { + perms = append(perms, operatorsv1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: role.GetName(), + Rules: role.Rules, + }) + } + strategy.ClusterPermissions = perms +} + +// applyDeployments updates strategy's deployments with the Deployments +// in the collector. +func applyDeployments(c *collector.Manifests, strategy *operatorsv1alpha1.StrategyDetailsDeployment) { + depSpecs := []operatorsv1alpha1.StrategyDeploymentSpec{} + for _, dep := range c.Deployments { + depSpecs = append(depSpecs, operatorsv1alpha1.StrategyDeploymentSpec{ + Name: dep.GetName(), + Spec: dep.Spec, + }) + } + strategy.DeploymentSpecs = depSpecs +} + +// applyCustomResourceDefinitions updates csv's customresourcedefinitions.owned +// with CustomResourceDefinitions in the collector. +// customresourcedefinitions.required are left as-is, since they are +// manually-defined values. +func applyCustomResourceDefinitions(c *collector.Manifests, csv *operatorsv1alpha1.ClusterServiceVersion) { + ownedDescs := []operatorsv1alpha1.CRDDescription{} + descMap := map[registry.DefinitionKey]operatorsv1alpha1.CRDDescription{} + for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { + defKey := registry.DefinitionKey{ + Name: owned.Name, + Version: owned.Version, + Kind: owned.Kind, + } + descMap[defKey] = owned + } + for _, crd := range c.CustomResourceDefinitions { + for _, ver := range crd.Spec.Versions { + defKey := registry.DefinitionKey{ + Name: crd.GetName(), + Version: ver.Name, + Kind: crd.Spec.Names.Kind, + } + if owned, ownedExists := descMap[defKey]; ownedExists { + ownedDescs = append(ownedDescs, owned) + } else { + ownedDescs = append(ownedDescs, operatorsv1alpha1.CRDDescription{ + Name: defKey.Name, + Version: defKey.Version, + Kind: defKey.Kind, + }) + } + } + } + csv.Spec.CustomResourceDefinitions.Owned = ownedDescs +} + +// applyWebhooks updates csv's webhookDefinitions with any +// mutating and validating webhooks in the collector. +func applyWebhooks(c *collector.Manifests, csv *operatorsv1alpha1.ClusterServiceVersion) { + webhookDescriptions := []operatorsv1alpha1.WebhookDescription{} + for _, webhook := range c.ValidatingWebhooks { + webhookDescriptions = append(webhookDescriptions, validatingToWebhookDescription(webhook)) + } + for _, webhook := range c.MutatingWebhooks { + webhookDescriptions = append(webhookDescriptions, mutatingToWebhookDescription(webhook)) + } + csv.Spec.WebhookDefinitions = webhookDescriptions +} + +// validatingToWebhookDescription transforms webhook into a WebhookDescription. +func validatingToWebhookDescription(webhook admissionregv1.ValidatingWebhook) operatorsv1alpha1.WebhookDescription { + description := operatorsv1alpha1.WebhookDescription{ + Type: operatorsv1alpha1.ValidatingAdmissionWebhook, + GenerateName: webhook.Name, + Rules: webhook.Rules, + FailurePolicy: webhook.FailurePolicy, + MatchPolicy: webhook.MatchPolicy, + ObjectSelector: webhook.ObjectSelector, + SideEffects: webhook.SideEffects, + TimeoutSeconds: webhook.TimeoutSeconds, + AdmissionReviewVersions: webhook.AdmissionReviewVersions, + } + if serviceRef := webhook.ClientConfig.Service; serviceRef != nil { + if serviceRef.Port != nil { + description.ContainerPort = *serviceRef.Port + } + description.DeploymentName = strings.TrimSuffix(serviceRef.Name, "-service") + description.WebhookPath = serviceRef.Path + } + return description +} + +// mutatingToWebhookDescription transforms webhook into a WebhookDescription. +func mutatingToWebhookDescription(webhook admissionregv1.MutatingWebhook) operatorsv1alpha1.WebhookDescription { + description := operatorsv1alpha1.WebhookDescription{ + Type: operatorsv1alpha1.MutatingAdmissionWebhook, + GenerateName: webhook.Name, + Rules: webhook.Rules, + FailurePolicy: webhook.FailurePolicy, + MatchPolicy: webhook.MatchPolicy, + ObjectSelector: webhook.ObjectSelector, + SideEffects: webhook.SideEffects, + TimeoutSeconds: webhook.TimeoutSeconds, + AdmissionReviewVersions: webhook.AdmissionReviewVersions, + ReinvocationPolicy: webhook.ReinvocationPolicy, + } + if serviceRef := webhook.ClientConfig.Service; serviceRef != nil { + if serviceRef.Port != nil { + description.ContainerPort = *serviceRef.Port + } + description.DeploymentName = strings.TrimSuffix(serviceRef.Name, "-service") + description.WebhookPath = serviceRef.Path + } + return description +} + +// applyCustomResources updates csv's "alm-examples" annotation with the +// Custom Resources in the collector. +func applyCustomResources(c *collector.Manifests, csv *operatorsv1alpha1.ClusterServiceVersion) error { + examples := []json.RawMessage{} + for _, cr := range c.CustomResources { + fmt.Println("applying", cr.GetName()) + crBytes, err := cr.MarshalJSON() + if err != nil { + return err + } + examples = append(examples, json.RawMessage(crBytes)) + } + + examplesJSON, err := json.MarshalIndent(examples, "", " ") + if err != nil { + return err + } + if csv.GetAnnotations() == nil { + csv.SetAnnotations(make(map[string]string)) + } + csv.GetAnnotations()["alm-examples"] = string(examplesJSON) + fmt.Println("applied") + + return nil +} + +// sortUpdates sorts all fields updated in csv. +// TODO(estroz): sort other modified fields. +func sortUpdates(csv *operatorsv1alpha1.ClusterServiceVersion) { + sort.Sort(descSorter(csv.Spec.CustomResourceDefinitions.Owned)) + sort.Sort(descSorter(csv.Spec.CustomResourceDefinitions.Required)) +} + +// descSorter sorts a set of crdDescriptions. +type descSorter []operatorsv1alpha1.CRDDescription + +var _ sort.Interface = descSorter{} + +func (descs descSorter) Len() int { return len(descs) } +func (descs descSorter) Less(i, j int) bool { + if descs[i].Name == descs[j].Name { + if descs[i].Kind == descs[j].Kind { + return version.CompareKubeAwareVersionStrings(descs[i].Version, descs[j].Version) > 0 + } + return descs[i].Kind < descs[j].Kind + } + return descs[i].Name < descs[j].Name +} +func (descs descSorter) Swap(i, j int) { descs[i], descs[j] = descs[j], descs[i] } + +// validate will validate csv using the api validation library. +// More info: https://github.com/operator-framework/api +func validate(csv *operatorsv1alpha1.ClusterServiceVersion) error { + if csv == nil { + return errors.New("empty ClusterServiceVersion") + } + results := validation.ClusterServiceVersionValidator.Validate(csv) + for _, r := range results { + for _, w := range r.Warnings { + log.Warnf("ClusterServiceVersion validation: [%s] %s", w.Type, w.Detail) + } + for _, e := range r.Errors { + log.Errorf("ClusterServiceVersion validation: [%s] %s", e.Type, e.Detail) + } + if r.HasError() { + return errors.New("got ClusterServiceVersion validation errors") + } + } + return nil +} diff --git a/internal/generate/collector/collect.go b/internal/generate/collector/collect.go new file mode 100644 index 0000000000..c0ba6d81a9 --- /dev/null +++ b/internal/generate/collector/collect.go @@ -0,0 +1,262 @@ +// 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 collector + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" + admissionregv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/operator-sdk/internal/util/k8sutil" +) + +// Manifests holds a collector of all manifests relevant to CSV updates. +type Manifests struct { + Roles []rbacv1.Role + ClusterRoles []rbacv1.ClusterRole + Deployments []appsv1.Deployment + CustomResourceDefinitions []apiextv1beta1.CustomResourceDefinition + ValidatingWebhooks []admissionregv1.ValidatingWebhook + MutatingWebhooks []admissionregv1.MutatingWebhook + CustomResources []unstructured.Unstructured + Others []unstructured.Unstructured +} + +// UpdateFromDirs adds Roles, ClusterRoles, Deployments, and Custom Resources +// found in manifestRoot, and CustomResourceDefinitions found in crdsDir, +// to their respective fields in a Manifests, then filters and deduplicates them. +// All other objects are added to Manifests.Others. +func (c *Manifests) UpdateFromDirs(manifestRoot, crdsDir string) error { + // Collect all manifests in paths. + err := filepath.Walk(manifestRoot, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) + for scanner.Scan() { + manifest := scanner.Bytes() + typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) + if err != nil { + log.Debugf("No TypeMeta in %s, skipping file", path) + continue + } + switch typeMeta.GroupVersionKind().Kind { + case "Role": + err = c.addRoles(manifest) + case "ClusterRole": + err = c.addClusterRoles(manifest) + case "Deployment": + err = c.addDeployments(manifest) + case "CustomResourceDefinition": + // Skip for now and add explicitly from CRDsDir input. + case "ValidatingWebhookConfiguration": + err = c.addValidatingWebhookConfigurations(manifest) + case "MutatingWebhookConfiguration": + err = c.addMutatingWebhookConfigurations(manifest) + default: + err = c.addOthers(manifest) + } + if err != nil { + return err + } + } + return scanner.Err() + }) + if err != nil { + return fmt.Errorf("error collecting manifests from directory %s: %v", manifestRoot, err) + } + + // Add CRDs from input. + if isDirExist(crdsDir) { + c.CustomResourceDefinitions, err = k8sutil.GetCustomResourceDefinitions(crdsDir) + if err != nil { + return err + } + } + + // Filter manifests based on data collected. + c.filter() + + // Remove duplicate manifests. + if err := c.deduplicate(); err != nil { + return fmt.Errorf("error removing duplicate manifests: %v", err) + } + + return nil +} + +// UpdateFromReader adds Roles, ClusterRoles, Deployments, CustomResourceDefinitions, +// and Custom Resources found in r to their respective fields in a Manifests, then +// filters and deduplicates them. All other objects are added to Manifests.Others. +func (c *Manifests) UpdateFromReader(r io.Reader) error { + // Bundle contents. + scanner := k8sutil.NewYAMLScanner(r) + for scanner.Scan() { + manifest := scanner.Bytes() + typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) + if err != nil { + log.Debug("No TypeMeta found, skipping manifest") + continue + } + switch typeMeta.GroupVersionKind().Kind { + case "Role": + err = c.addRoles(manifest) + case "ClusterRole": + err = c.addClusterRoles(manifest) + case "Deployment": + err = c.addDeployments(manifest) + case "CustomResourceDefinition": + err = c.addCustomResourceDefinitions(manifest) + case "ValidatingWebhookConfiguration": + err = c.addValidatingWebhookConfigurations(manifest) + case "MutatingWebhookConfiguration": + err = c.addMutatingWebhookConfigurations(manifest) + default: + err = c.addOthers(manifest) + } + if err != nil { + return err + } + } + if err := scanner.Err(); err != nil { + return fmt.Errorf("error collecting manifests from reader: %v", err) + } + + // Filter manifests based on data collected. + c.filter() + + // Remove duplicate manifests. + if err := c.deduplicate(); err != nil { + return fmt.Errorf("error removing duplicate manifests: %v", err) + } + + return nil +} + +// addRoles assumes add manifest data in rawManifests are Roles and adds them +// to the collector. +func (c *Manifests) addRoles(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + role := rbacv1.Role{} + if err := yaml.Unmarshal(rawManifest, &role); err != nil { + return fmt.Errorf("error adding Role to manifest collector: %v", err) + } + c.Roles = append(c.Roles, role) + } + return nil +} + +// addClusterRoles assumes add manifest data in rawManifests are ClusterRoles +// and adds them to the collector. +func (c *Manifests) addClusterRoles(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + role := rbacv1.ClusterRole{} + if err := yaml.Unmarshal(rawManifest, &role); err != nil { + return fmt.Errorf("error adding ClusterRole to manifest collector: %v", err) + } + c.ClusterRoles = append(c.ClusterRoles, role) + } + return nil +} + +// addDeployments assumes add manifest data in rawManifests are Deployments +// and adds them to the collector. +func (c *Manifests) addDeployments(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + dep := appsv1.Deployment{} + if err := yaml.Unmarshal(rawManifest, &dep); err != nil { + return fmt.Errorf("error adding Deployment to manifest collector: %v", err) + } + c.Deployments = append(c.Deployments, dep) + } + return nil +} + +// addCustomResourceDefinitions assumes add manifest data in rawManifests are +// CustomResourceDefinitions and adds them to the collector. +func (c *Manifests) addCustomResourceDefinitions(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + crd := apiextv1beta1.CustomResourceDefinition{} + if err := yaml.Unmarshal(rawManifest, &crd); err != nil { + return fmt.Errorf("error adding Deployment to manifest collector: %v", err) + } + c.CustomResourceDefinitions = append(c.CustomResourceDefinitions, crd) + } + return nil +} + +// addValidatingWebhookConfigurations assumes all manifest data in rawManifests +// are ValidatingWebhookConfigurations and adds their webhooks to the collector. +func (c *Manifests) addValidatingWebhookConfigurations(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + webhookConfig := admissionregv1.ValidatingWebhookConfiguration{} + if err := yaml.Unmarshal(rawManifest, &webhookConfig); err != nil { + return fmt.Errorf("error adding ValidatingWebhookConfig to manifest collection: %v", err) + } + c.ValidatingWebhooks = append(c.ValidatingWebhooks, webhookConfig.Webhooks...) + } + return nil +} + +// addMutatingWebhookConfigurations assumes all manifest data in rawManifests +// are MutatingWebhookConfigurations and adds their webhooks to the collector. +func (c *Manifests) addMutatingWebhookConfigurations(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + webhookConfig := admissionregv1.MutatingWebhookConfiguration{} + if err := yaml.Unmarshal(rawManifest, &webhookConfig); err != nil { + return fmt.Errorf("error adding MutatingWebhookConfig to manifest collection: %v", err) + } + c.MutatingWebhooks = append(c.MutatingWebhooks, webhookConfig.Webhooks...) + } + return nil +} + +// addOthers assumes add manifest data in rawManifests are able to be +// unmarshalled into an Unstructured object and adds them to the collector. +func (c *Manifests) addOthers(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + u := unstructured.Unstructured{} + if err := yaml.Unmarshal(rawManifest, &u); err != nil { + return fmt.Errorf("error adding manifest collector: %v", err) + } + c.Others = append(c.Others, u) + } + return nil +} + +// isDirExist returns true if dir exists on disk. +func isDirExist(dir string) bool { + if dir == "" { + return false + } + info, err := os.Stat(dir) + return (err == nil && info.IsDir()) || os.IsExist(err) +} diff --git a/internal/generate/collector/filter.go b/internal/generate/collector/filter.go new file mode 100644 index 0000000000..4acbe51587 --- /dev/null +++ b/internal/generate/collector/filter.go @@ -0,0 +1,177 @@ +// 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 collector + +import ( + "crypto/sha256" + + admissionregv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// filter applies filtering rules to certain manifest types in a collection. +func (c *Manifests) filter() { + c.filterCustomResources() +} + +// filterCustomResources filters "other" objects, which contain likely +// Custom Resources corresponding to a CustomResourceDefinition, by GVK. +func (c *Manifests) filterCustomResources() { + crdGVKSet := make(map[schema.GroupVersionKind]struct{}) + for _, crd := range c.CustomResourceDefinitions { + for _, version := range crd.Spec.Versions { + gvk := schema.GroupVersionKind{ + Group: crd.Spec.Group, + Version: version.Name, + Kind: crd.Spec.Names.Kind, + } + crdGVKSet[gvk] = struct{}{} + } + } + + customResources := []unstructured.Unstructured{} + for _, other := range c.Others { + if _, gvkMatches := crdGVKSet[other.GroupVersionKind()]; gvkMatches { + customResources = append(customResources, other) + } + } + c.CustomResources = customResources +} + +// deduplicate removes duplicate objects from the collection, since we are +// collecting an arbitrary list of manifests. +func (c *Manifests) deduplicate() error { + hashes := make(map[string]struct{}) + + roles := []rbacv1.Role{} + for _, role := range c.Roles { + hasHash, err := addToHashes(&role, hashes) + if err != nil { + return err + } + if !hasHash { + roles = append(roles, role) + } + } + c.Roles = roles + + clusterRoles := []rbacv1.ClusterRole{} + for _, clusterRole := range c.ClusterRoles { + hasHash, err := addToHashes(&clusterRole, hashes) + if err != nil { + return err + } + if !hasHash { + clusterRoles = append(clusterRoles, clusterRole) + } + } + c.ClusterRoles = clusterRoles + + deps := []appsv1.Deployment{} + for _, dep := range c.Deployments { + hasHash, err := addToHashes(&dep, hashes) + if err != nil { + return err + } + if !hasHash { + deps = append(deps, dep) + } + } + c.Deployments = deps + + crds := []apiextv1beta1.CustomResourceDefinition{} + for _, crd := range c.CustomResourceDefinitions { + hasHash, err := addToHashes(&crd, hashes) + if err != nil { + return err + } + if !hasHash { + crds = append(crds, crd) + } + } + c.CustomResourceDefinitions = crds + + validatingWebhooks := []admissionregv1.ValidatingWebhook{} + for _, webhook := range c.ValidatingWebhooks { + hasHash, err := addToHashes(&webhook, hashes) + if err != nil { + return err + } + if !hasHash { + validatingWebhooks = append(validatingWebhooks, webhook) + } + } + c.ValidatingWebhooks = validatingWebhooks + + mutatingWebhooks := []admissionregv1.MutatingWebhook{} + for _, webhook := range c.MutatingWebhooks { + hasHash, err := addToHashes(&webhook, hashes) + if err != nil { + return err + } + if !hasHash { + mutatingWebhooks = append(mutatingWebhooks, webhook) + } + } + c.MutatingWebhooks = mutatingWebhooks + + crs := []unstructured.Unstructured{} + for _, cr := range c.CustomResources { + b, err := cr.MarshalJSON() + if err != nil { + return err + } + hash := hashContents(b) + if _, hasHash := hashes[hash]; !hasHash { + crs = append(crs, cr) + hashes[hash] = struct{}{} + } + } + c.CustomResources = crs + + return nil +} + +// marshaller is an interface used to generalize hashing for deduplication. +type marshaller interface { + Marshal() ([]byte, error) +} + +// addToHashes calls m.Marshal(), hashes the returned bytes, and adds the +// hash to hashes if it does not exist. addToHashes returns true if m's hash +// was not in hashes. +func addToHashes(m marshaller, hashes map[string]struct{}) (bool, error) { + b, err := m.Marshal() + if err != nil { + return false, err + } + hash := hashContents(b) + _, hasHash := hashes[hash] + if !hasHash { + hashes[hash] = struct{}{} + } + return hasHash, nil +} + +// hashContents creates a sha256 md5 digest of b's bytes. +func hashContents(b []byte) string { + h := sha256.New() + _, _ = h.Write(b) + return string(h.Sum(nil)) +} diff --git a/internal/generate/olm-catalog/csv.go b/internal/generate/olm-catalog/csv.go index 1e5135962f..c5252c5dbd 100644 --- a/internal/generate/olm-catalog/csv.go +++ b/internal/generate/olm-catalog/csv.go @@ -15,7 +15,6 @@ package olmcatalog import ( - "bytes" "errors" "fmt" "io/ioutil" @@ -23,7 +22,9 @@ import ( "path/filepath" "strings" + "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion" "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion/bases" + "github.com/operator-framework/operator-sdk/internal/generate/collector" "github.com/operator-framework/operator-sdk/internal/scaffold" "github.com/operator-framework/operator-sdk/internal/util/fileutil" "github.com/operator-framework/operator-sdk/internal/util/k8sutil" @@ -382,77 +383,17 @@ func (g BundleGenerator) updateCSVVersions(csv *olmapiv1alpha1.ClusterServiceVer // user-defined manifests and updates csv. func (g BundleGenerator) updateCSVFromManifests(csv *olmapiv1alpha1.ClusterServiceVersion) (err error) { // Collect all manifests in paths. - collection := manifestCollection{} - err = filepath.Walk(g.DeployDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - // Only read manifest from files, not directories - if info.IsDir() { - return nil - } - - b, err := ioutil.ReadFile(path) - if err != nil { - return err - } - scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) - for scanner.Scan() { - manifest := scanner.Bytes() - typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) - if err != nil { - log.Debugf("No TypeMeta in %s, skipping file", path) - continue - } - switch typeMeta.GroupVersionKind().Kind { - case "Role": - err = collection.addRoles(manifest) - case "ClusterRole": - err = collection.addClusterRoles(manifest) - case "Deployment": - err = collection.addDeployments(manifest) - case "CustomResourceDefinition": - // Skip for now and add explicitly from CRDsDir input. - case "ValidatingWebhookConfiguration": - err = collection.addValidatingWebhookConfigurations(manifest) - case "MutatingWebhookConfiguration": - err = collection.addMutatingWebhookConfigurations(manifest) - default: - err = collection.addOthers(manifest) - } - if err != nil { - return err - } - } - return scanner.Err() - }) - if err != nil { - return fmt.Errorf("failed to walk manifests directory for CSV updates: %v", err) - } - - // Add CRDs from input. - if isExist(g.CRDsDir) { - collection.CustomResourceDefinitions, err = k8sutil.GetCustomResourceDefinitions(g.CRDsDir) - if err != nil { - return err - } - } - - // Filter the collection based on data collected. - collection.filter() - - // Remove duplicate manifests. - if err = collection.deduplicate(); err != nil { - return fmt.Errorf("error removing duplicate manifests: %v", err) + col := &collector.Manifests{} + if err := col.UpdateFromDirs(g.DeployDir, g.CRDsDir); err != nil { + return err } // Apply manifests to the CSV object. - if err = collection.apply(csv); err != nil { + if err = clusterserviceversion.ApplyTo(col, csv); err != nil { return fmt.Errorf("error building CSV: %v", err) } - // Finally sort all updated fields. - sortUpdates(csv) + handleWatchNamespaces(csv) return nil } diff --git a/internal/generate/olm-catalog/csv_updaters.go b/internal/generate/olm-catalog/csv_updaters.go index 3277911825..7942a5870f 100644 --- a/internal/generate/olm-catalog/csv_updaters.go +++ b/internal/generate/olm-catalog/csv_updaters.go @@ -16,232 +16,39 @@ package olmcatalog import ( "bytes" - "crypto/sha256" - "encoding/json" - "fmt" - "sort" - "strings" - - "github.com/operator-framework/operator-registry/pkg/registry" - "github.com/operator-framework/operator-sdk/pkg/k8sutil" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" log "github.com/sirupsen/logrus" - admissionregv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/version" - "sigs.k8s.io/yaml" -) - -// manifestCollection holds a collection of all manifests relevant to CSV updates. -type manifestCollection struct { - Roles []rbacv1.Role - ClusterRoles []rbacv1.ClusterRole - Deployments []appsv1.Deployment - CustomResourceDefinitions []apiextv1beta1.CustomResourceDefinition - ValidatingWebhooks []admissionregv1.ValidatingWebhook - MutatingWebhooks []admissionregv1.MutatingWebhook - CustomResources []unstructured.Unstructured - Others []unstructured.Unstructured -} - -// addRoles assumes add manifest data in rawManifests are Roles and adds them -// to the collection. -func (c *manifestCollection) addRoles(rawManifests ...[]byte) error { - for _, rawManifest := range rawManifests { - role := rbacv1.Role{} - if err := yaml.Unmarshal(rawManifest, &role); err != nil { - return fmt.Errorf("error adding Role to manifest collection: %v", err) - } - c.Roles = append(c.Roles, role) - } - return nil -} - -// addClusterRoles assumes add manifest data in rawManifests are ClusterRoles -// and adds them to the collection. -func (c *manifestCollection) addClusterRoles(rawManifests ...[]byte) error { - for _, rawManifest := range rawManifests { - role := rbacv1.ClusterRole{} - if err := yaml.Unmarshal(rawManifest, &role); err != nil { - return fmt.Errorf("error adding ClusterRole to manifest collection: %v", err) - } - c.ClusterRoles = append(c.ClusterRoles, role) - } - return nil -} - -// addDeployments assumes add manifest data in rawManifests are Deployments -// and adds them to the collection. -func (c *manifestCollection) addDeployments(rawManifests ...[]byte) error { - for _, rawManifest := range rawManifests { - dep := appsv1.Deployment{} - if err := yaml.Unmarshal(rawManifest, &dep); err != nil { - return fmt.Errorf("error adding Deployment to manifest collection: %v", err) - } - c.Deployments = append(c.Deployments, dep) - } - return nil -} - -// addValidatingWebhookConfigurations assumes all manifest data in rawManifests -// are ValidatingWebhookConfigurations and adds their webhooks to the collection. -func (c *manifestCollection) addValidatingWebhookConfigurations(rawManifests ...[]byte) error { - for _, rawManifest := range rawManifests { - webhookConfig := admissionregv1.ValidatingWebhookConfiguration{} - if err := yaml.Unmarshal(rawManifest, &webhookConfig); err != nil { - return fmt.Errorf("error adding ValidatingWebhookConfig to manifest collection: %v", err) - } - c.ValidatingWebhooks = append(c.ValidatingWebhooks, webhookConfig.Webhooks...) - } - return nil -} - -// addMutatingWebhookConfigurations assumes all manifest data in rawManifests -// are MutatingWebhookConfigurations and adds their webhooks to the collection. -func (c *manifestCollection) addMutatingWebhookConfigurations(rawManifests ...[]byte) error { - for _, rawManifest := range rawManifests { - webhookConfig := admissionregv1.MutatingWebhookConfiguration{} - if err := yaml.Unmarshal(rawManifest, &webhookConfig); err != nil { - return fmt.Errorf("error adding MutatingWebhookConfig to manifest collection: %v", err) - } - c.MutatingWebhooks = append(c.MutatingWebhooks, webhookConfig.Webhooks...) - } - return nil -} - -// addOthers assumes add manifest data in rawManifests are able to be -// unmarshalled into an Unstructured object and adds them to the collection. -func (c *manifestCollection) addOthers(rawManifests ...[]byte) error { - for _, rawManifest := range rawManifests { - u := unstructured.Unstructured{} - if err := yaml.Unmarshal(rawManifest, &u); err != nil { - return fmt.Errorf("error adding manifest collection: %v", err) - } - c.Others = append(c.Others, u) - } - return nil -} - -// filter applies filtering rules to certain manifest types in a collection. -func (c *manifestCollection) filter() { - c.filterCustomResources() -} - -// filterCustomResources filters "other" objects, which contain likely -// Custom Resources corresponding to a CustomResourceDefinition, by GVK. -func (c *manifestCollection) filterCustomResources() { - crdGVKSet := make(map[schema.GroupVersionKind]struct{}) - for _, crd := range c.CustomResourceDefinitions { - for _, version := range crd.Spec.Versions { - gvk := schema.GroupVersionKind{ - Group: crd.Spec.Group, - Version: version.Name, - Kind: crd.Spec.Names.Kind, - } - crdGVKSet[gvk] = struct{}{} - } - } - customResources := []unstructured.Unstructured{} - for _, other := range c.Others { - if _, gvkMatches := crdGVKSet[other.GroupVersionKind()]; gvkMatches { - customResources = append(customResources, other) - } - } - c.CustomResources = customResources -} - -// apply applies the manifests in the collection to csv. -func (c manifestCollection) apply(csv *operatorsv1alpha1.ClusterServiceVersion) error { - strategy := getCSVInstallStrategy(csv) - switch strategy.StrategyName { - case operatorsv1alpha1.InstallStrategyNameDeployment: - c.applyRoles(&strategy.StrategySpec) - c.applyClusterRoles(&strategy.StrategySpec) - c.applyDeployments(&strategy.StrategySpec) - } - csv.Spec.InstallStrategy = strategy - - c.applyCustomResourceDefinitions(csv) - if err := c.applyCustomResources(csv); err != nil { - return fmt.Errorf("error applying Custom Resource: %v", err) - } - c.applyWebhooks(csv) - return nil -} - -// Get install strategy from csv. -func getCSVInstallStrategy(csv *operatorsv1alpha1.ClusterServiceVersion) operatorsv1alpha1.NamedInstallStrategy { - // Default to a deployment strategy if none found. - if csv.Spec.InstallStrategy.StrategyName == "" { - csv.Spec.InstallStrategy.StrategyName = operatorsv1alpha1.InstallStrategyNameDeployment - } - return csv.Spec.InstallStrategy -} - -// applyRoles updates strategy's permissions with the Roles in the collection. -func (c manifestCollection) applyRoles(strategy *operatorsv1alpha1.StrategyDetailsDeployment) { - perms := []operatorsv1alpha1.StrategyDeploymentPermissions{} - for _, role := range c.Roles { - perms = append(perms, operatorsv1alpha1.StrategyDeploymentPermissions{ - ServiceAccountName: role.GetName(), - Rules: role.Rules, - }) - } - strategy.Permissions = perms -} + "github.com/operator-framework/operator-sdk/pkg/k8sutil" +) -// applyClusterRoles updates strategy's cluserPermissions with the ClusterRoles -// in the collection. -func (c manifestCollection) applyClusterRoles(strategy *operatorsv1alpha1.StrategyDetailsDeployment) { - perms := []operatorsv1alpha1.StrategyDeploymentPermissions{} - for _, role := range c.ClusterRoles { - perms = append(perms, operatorsv1alpha1.StrategyDeploymentPermissions{ - ServiceAccountName: role.GetName(), - Rules: role.Rules, - }) - } - strategy.ClusterPermissions = perms -} +const olmTNMeta = "metadata.annotations['olm.targetNamespaces']" -// applyDeployments updates strategy's deployments with the Deployments -// in the collection. -func (c manifestCollection) applyDeployments(strategy *operatorsv1alpha1.StrategyDetailsDeployment) { - depSpecs := []operatorsv1alpha1.StrategyDeploymentSpec{} - for _, dep := range c.Deployments { - setWatchNamespacesEnv(&dep) +func handleWatchNamespaces(csv *operatorsv1alpha1.ClusterServiceVersion) { + for _, dep := range csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs { + setWatchNamespacesEnv(&dep.Spec) // Make sure "olm.targetNamespaces" is referenced somewhere in dep, // and emit a warning of not. - if !depHasOLMNamespaces(dep) { + if !depHasOLMNamespaces(dep.Spec) { log.Warnf(`No WATCH_NAMESPACE environment variable nor reference to "%s"`+ ` detected in operator Deployment. For OLM compatibility, your operator`+ ` MUST watch namespaces defined in "%s"`, olmTNMeta, olmTNMeta) } - depSpecs = append(depSpecs, operatorsv1alpha1.StrategyDeploymentSpec{ - Name: dep.GetName(), - Spec: dep.Spec, - }) } - strategy.DeploymentSpecs = depSpecs } -const olmTNMeta = "metadata.annotations['olm.targetNamespaces']" - // setWatchNamespacesEnv sets WATCH_NAMESPACE to olmTNString in dep if // WATCH_NAMESPACE exists in a pod spec container's env list. -func setWatchNamespacesEnv(dep *appsv1.Deployment) { +func setWatchNamespacesEnv(dep *appsv1.DeploymentSpec) { envVar := newEnvVar(k8sutil.WatchNamespaceEnvVar, olmTNMeta) overwriteContainerEnvVar(dep, k8sutil.WatchNamespaceEnvVar, envVar) } -func overwriteContainerEnvVar(dep *appsv1.Deployment, name string, ev corev1.EnvVar) { - for _, c := range dep.Spec.Template.Spec.Containers { +func overwriteContainerEnvVar(dep *appsv1.DeploymentSpec, name string, ev corev1.EnvVar) { + for _, c := range dep.Template.Spec.Containers { for i := 0; i < len(c.Env); i++ { if c.Env[i].Name == name { c.Env[i] = ev @@ -264,288 +71,13 @@ func newEnvVar(name, fieldPath string) corev1.EnvVar { // OLM places the set of target namespaces for the operator in // "metadata.annotations['olm.targetNamespaces']". This value should be // referenced in either: -// - The Deployment's pod spec WATCH_NAMESPACE env variable. -// - Some other Deployment pod spec field. -func depHasOLMNamespaces(dep appsv1.Deployment) bool { - b, err := dep.Spec.Template.Marshal() +// - The DeploymentSpec's pod spec WATCH_NAMESPACE env variable. +// - Some other DeploymentSpec pod spec field. +func depHasOLMNamespaces(dep appsv1.DeploymentSpec) bool { + b, err := dep.Template.Marshal() if err != nil { // Something is wrong with the deployment manifest, not with CLI inputs. log.Fatalf("Marshal Deployment spec: %v", err) } return bytes.Contains(b, []byte(olmTNMeta)) } - -// applyCustomResourceDefinitions updates csv's customresourcedefinitions.owned -// with CustomResourceDefinitions in the collection. -// customresourcedefinitions.required are left as-is, since they are -// manually-defined values. -func (c manifestCollection) applyCustomResourceDefinitions(csv *operatorsv1alpha1.ClusterServiceVersion) { - ownedDescs := []operatorsv1alpha1.CRDDescription{} - descMap := map[registry.DefinitionKey]operatorsv1alpha1.CRDDescription{} - for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { - defKey := registry.DefinitionKey{ - Name: owned.Name, - Version: owned.Version, - Kind: owned.Kind, - } - descMap[defKey] = owned - } - for _, crd := range c.CustomResourceDefinitions { - for _, ver := range crd.Spec.Versions { - defKey := registry.DefinitionKey{ - Name: crd.GetName(), - Version: ver.Name, - Kind: crd.Spec.Names.Kind, - } - if owned, ownedExists := descMap[defKey]; ownedExists { - ownedDescs = append(ownedDescs, owned) - } else { - ownedDescs = append(ownedDescs, operatorsv1alpha1.CRDDescription{ - Name: defKey.Name, - Version: defKey.Version, - Kind: defKey.Kind, - }) - } - } - } - csv.Spec.CustomResourceDefinitions.Owned = ownedDescs -} - -// applyCustomResources updates csv's "alm-examples" annotation with the -// Custom Resources in the collection. -func (c manifestCollection) applyCustomResources(csv *operatorsv1alpha1.ClusterServiceVersion) error { - examples := []json.RawMessage{} - for _, cr := range c.CustomResources { - crBytes, err := cr.MarshalJSON() - if err != nil { - return err - } - examples = append(examples, json.RawMessage(crBytes)) - } - examplesJSON, err := json.Marshal(examples) - if err != nil { - return err - } - examplesJSON, err = prettifyJSON(examplesJSON) - if err != nil { - return err - } - if csv.GetAnnotations() == nil { - csv.SetAnnotations(make(map[string]string)) - } - csv.GetAnnotations()["alm-examples"] = string(examplesJSON) - return nil -} - -// prettifyJSON returns a JSON in a pretty format -func prettifyJSON(b []byte) ([]byte, error) { - var out bytes.Buffer - err := json.Indent(&out, b, "", " ") - return out.Bytes(), err -} - -// applyWebhooks updates csv's webhookDefinitions with any -// mutating and validating webhooks in the collection. -func (c manifestCollection) applyWebhooks(csv *operatorsv1alpha1.ClusterServiceVersion) { - webhookDescriptions := []operatorsv1alpha1.WebhookDescription{} - for _, webhook := range c.ValidatingWebhooks { - webhookDescriptions = append(webhookDescriptions, validatingToWebhookDescription(webhook)) - } - for _, webhook := range c.MutatingWebhooks { - webhookDescriptions = append(webhookDescriptions, mutatingToWebhookDescription(webhook)) - } - csv.Spec.WebhookDefinitions = webhookDescriptions -} - -// validatingToWebhookDescription transforms webhook into a WebhookDescription. -func validatingToWebhookDescription(webhook admissionregv1.ValidatingWebhook) operatorsv1alpha1.WebhookDescription { - description := operatorsv1alpha1.WebhookDescription{ - Type: operatorsv1alpha1.ValidatingAdmissionWebhook, - GenerateName: webhook.Name, - Rules: webhook.Rules, - FailurePolicy: webhook.FailurePolicy, - MatchPolicy: webhook.MatchPolicy, - ObjectSelector: webhook.ObjectSelector, - SideEffects: webhook.SideEffects, - TimeoutSeconds: webhook.TimeoutSeconds, - AdmissionReviewVersions: webhook.AdmissionReviewVersions, - } - if serviceRef := webhook.ClientConfig.Service; serviceRef != nil { - if serviceRef.Port != nil { - description.ContainerPort = *serviceRef.Port - } - description.DeploymentName = strings.TrimSuffix(serviceRef.Name, "-service") - description.WebhookPath = serviceRef.Path - } - return description -} - -// mutatingToWebhookDescription transforms webhook into a WebhookDescription. -func mutatingToWebhookDescription(webhook admissionregv1.MutatingWebhook) operatorsv1alpha1.WebhookDescription { - description := operatorsv1alpha1.WebhookDescription{ - Type: operatorsv1alpha1.MutatingAdmissionWebhook, - GenerateName: webhook.Name, - Rules: webhook.Rules, - FailurePolicy: webhook.FailurePolicy, - MatchPolicy: webhook.MatchPolicy, - ObjectSelector: webhook.ObjectSelector, - SideEffects: webhook.SideEffects, - TimeoutSeconds: webhook.TimeoutSeconds, - AdmissionReviewVersions: webhook.AdmissionReviewVersions, - ReinvocationPolicy: webhook.ReinvocationPolicy, - } - if serviceRef := webhook.ClientConfig.Service; serviceRef != nil { - if serviceRef.Port != nil { - description.ContainerPort = *serviceRef.Port - } - description.DeploymentName = strings.TrimSuffix(serviceRef.Name, "-service") - description.WebhookPath = serviceRef.Path - } - return description -} - -// deduplicate removes duplicate objects from the collection, since we are -// collecting an arbitrary list of manifests. -func (c *manifestCollection) deduplicate() error { - hashes := make(map[string]struct{}) - - roles := []rbacv1.Role{} - for _, role := range c.Roles { - hasHash, err := addToHashes(&role, hashes) - if err != nil { - return err - } - if !hasHash { - roles = append(roles, role) - } - } - c.Roles = roles - - clusterRoles := []rbacv1.ClusterRole{} - for _, clusterRole := range c.ClusterRoles { - hasHash, err := addToHashes(&clusterRole, hashes) - if err != nil { - return err - } - if !hasHash { - clusterRoles = append(clusterRoles, clusterRole) - } - } - c.ClusterRoles = clusterRoles - - deps := []appsv1.Deployment{} - for _, dep := range c.Deployments { - hasHash, err := addToHashes(&dep, hashes) - if err != nil { - return err - } - if !hasHash { - deps = append(deps, dep) - } - } - c.Deployments = deps - - crds := []apiextv1beta1.CustomResourceDefinition{} - for _, crd := range c.CustomResourceDefinitions { - hasHash, err := addToHashes(&crd, hashes) - if err != nil { - return err - } - if !hasHash { - crds = append(crds, crd) - } - } - c.CustomResourceDefinitions = crds - - crs := []unstructured.Unstructured{} - for _, cr := range c.CustomResources { - b, err := cr.MarshalJSON() - if err != nil { - return err - } - hash := hashContents(b) - if _, hasHash := hashes[hash]; !hasHash { - crs = append(crs, cr) - hashes[hash] = struct{}{} - } - } - c.CustomResources = crs - - validatingWebhooks := []admissionregv1.ValidatingWebhook{} - for _, webhook := range c.ValidatingWebhooks { - hasHash, err := addToHashes(&webhook, hashes) - if err != nil { - return err - } - if !hasHash { - validatingWebhooks = append(validatingWebhooks, webhook) - } - } - c.ValidatingWebhooks = validatingWebhooks - - mutatingWebhooks := []admissionregv1.MutatingWebhook{} - for _, webhook := range c.MutatingWebhooks { - hasHash, err := addToHashes(&webhook, hashes) - if err != nil { - return err - } - if !hasHash { - mutatingWebhooks = append(mutatingWebhooks, webhook) - } - } - c.MutatingWebhooks = mutatingWebhooks - - return nil -} - -// marshaller is an interface used to generalize hashing for deduplication. -type marshaller interface { - Marshal() ([]byte, error) -} - -// addToHashes calls m.Marshal(), hashes the returned bytes, and adds the -// hash to hashes if it does not exist. addToHashes returns true if m's hash -// was not in hashes. -func addToHashes(m marshaller, hashes map[string]struct{}) (bool, error) { - b, err := m.Marshal() - if err != nil { - return false, err - } - hash := hashContents(b) - _, hasHash := hashes[hash] - if !hasHash { - hashes[hash] = struct{}{} - } - return hasHash, nil -} - -// hashContents creates a sha256 md5 digest of b's bytes. -func hashContents(b []byte) string { - h := sha256.New() - _, _ = h.Write(b) - return string(h.Sum(nil)) -} - -// sortUpdates sorts all fields updated in csv. -// TODO(estroz): sort other modified fields. -func sortUpdates(csv *operatorsv1alpha1.ClusterServiceVersion) { - sort.Sort(descSorter(csv.Spec.CustomResourceDefinitions.Owned)) - sort.Sort(descSorter(csv.Spec.CustomResourceDefinitions.Required)) -} - -// descSorter sorts a set of crdDescriptions. -type descSorter []operatorsv1alpha1.CRDDescription - -var _ sort.Interface = descSorter{} - -func (descs descSorter) Len() int { return len(descs) } -func (descs descSorter) Less(i, j int) bool { - if descs[i].Name == descs[j].Name { - if descs[i].Kind == descs[j].Kind { - return version.CompareKubeAwareVersionStrings(descs[i].Version, descs[j].Version) > 0 - } - return descs[i].Kind < descs[j].Kind - } - return descs[i].Name < descs[j].Name -} -func (descs descSorter) Swap(i, j int) { descs[i], descs[j] = descs[j], descs[i] } diff --git a/internal/generate/olm-catalog/csv_updaters_test.go b/internal/generate/olm-catalog/csv_updaters_test.go deleted file mode 100644 index 6e7f6edbaa..0000000000 --- a/internal/generate/olm-catalog/csv_updaters_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package olmcatalog - -import ( - "io/ioutil" - "path/filepath" - "testing" - - "github.com/operator-framework/operator-sdk/internal/scaffold" - "github.com/operator-framework/operator-sdk/pkg/k8sutil" - - appsv1 "k8s.io/api/apps/v1" - "sigs.k8s.io/yaml" -) - -func TestSetAndCheckOLMNamespaces(t *testing.T) { - cleanupFunc := chDirWithCleanup(t, testGoDataDir) - defer cleanupFunc() - - depBytes, err := ioutil.ReadFile(filepath.Join(scaffold.DeployDir, "operator.yaml")) - if err != nil { - t.Fatalf("Failed to read Deployment bytes: %v", err) - } - - // The test operator.yaml doesn't have "olm.targetNamespaces", so first - // check that depHasOLMNamespaces() returns false. - dep := appsv1.Deployment{} - if err := yaml.Unmarshal(depBytes, &dep); err != nil { - t.Fatalf("Failed to unmarshal Deployment bytes: %v", err) - } - if depHasOLMNamespaces(dep) { - t.Error("Expected depHasOLMNamespaces to return false, got true") - } - - // Insert "olm.targetNamespaces" into WATCH_NAMESPACE and check that - // depHasOLMNamespaces() returns true. - setWatchNamespacesEnv(&dep) - if !depHasOLMNamespaces(dep) { - t.Error("Expected depHasOLMNamespaces to return true, got false") - } - - // Overwrite WATCH_NAMESPACE and check that depHasOLMNamespaces() returns - // false. - overwriteContainerEnvVar(&dep, k8sutil.WatchNamespaceEnvVar, newEnvVar("FOO", "bar")) - if depHasOLMNamespaces(dep) { - t.Error("Expected depHasOLMNamespaces to return false, got true") - } - - // Insert "olm.targetNamespaces" elsewhere in the deployment pod spec - // and check that depHasOLMNamespaces() returns true. - dep = appsv1.Deployment{} - if err := yaml.Unmarshal(depBytes, &dep); err != nil { - t.Fatalf("Failed to unmarshal Deployment bytes: %v", err) - } - dep.Spec.Template.ObjectMeta.Labels["namespace"] = olmTNMeta - if !depHasOLMNamespaces(dep) { - t.Error("Expected depHasOLMNamespaces to return true, got false") - } -}