diff --git a/staging/api/RELEASE.md b/staging/api/RELEASE.md index 79897f9958..598a42a2bd 100644 --- a/staging/api/RELEASE.md +++ b/staging/api/RELEASE.md @@ -8,7 +8,8 @@ post to the [operator-framework group][of-ggroup]. ## Tags As per semver, all releases containing new features must map to a major or minor version increase. -Patch releases must only contain fixes to features released in a prior release. + +**Patch releases must only contain bug fixes. Releases containing features must be major or minor releases.** ## Process @@ -31,6 +32,8 @@ Then create release notes while still on the `master` branch: while read -r line; do echo $line | awk '{f = $1; $1 = ""; print "-"$0; }'; done <<< $(git log $PREVIOUS_RELEASE_TAG..$RELEASE_TAG --format=oneline --no-merges) ``` +**You cannot cut a patch release if any of these release notes start with `feat:` or `feature:`.** + Copy them into the Github release [description form][release-desc-page], select `vX.Y.Z` in the `Tag version` form, and click `Publish release`. diff --git a/staging/api/pkg/apis/scorecard/v1alpha3/configuration_types.go b/staging/api/pkg/apis/scorecard/v1alpha3/configuration_types.go index 9a447b22c4..07d617440c 100644 --- a/staging/api/pkg/apis/scorecard/v1alpha3/configuration_types.go +++ b/staging/api/pkg/apis/scorecard/v1alpha3/configuration_types.go @@ -22,6 +22,9 @@ type Configuration struct { // Storage is the optional storage configuration Storage Storage `json:"storage,omitempty" yaml:"storage,omitempty"` + + // ServiceAccount is the service account under which scorecard tests are run. This field is optional. If left unset, the `default` service account will be used. + ServiceAccount string `json:"serviceaccount,omitempty" yaml:"serviceaccount,omitempty"` } // StageConfiguration configures a set of tests to be run. diff --git a/staging/api/pkg/validation/internal/bundle.go b/staging/api/pkg/validation/internal/bundle.go index d1e7217dee..79fa93e35c 100644 --- a/staging/api/pkg/validation/internal/bundle.go +++ b/staging/api/pkg/validation/internal/bundle.go @@ -8,6 +8,8 @@ import ( operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/api/pkg/validation/errors" interfaces "github.com/operator-framework/api/pkg/validation/interfaces" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -26,9 +28,42 @@ func validateBundles(objs ...interface{}) (results []errors.ManifestResult) { func validateBundle(bundle *manifests.Bundle) (result errors.ManifestResult) { result = validateOwnedCRDs(bundle, bundle.CSV) result.Name = bundle.CSV.Spec.Version.String() + saErrors := validateServiceAccounts(bundle) + if saErrors != nil { + result.Add(saErrors...) + } return result } +func validateServiceAccounts(bundle *manifests.Bundle) []errors.Error { + // get service account names defined in the csv + saNamesFromCSV := make(map[string]struct{}, 0) + for _, deployment := range bundle.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs { + saName := deployment.Spec.Template.Spec.ServiceAccountName + saNamesFromCSV[saName] = struct{}{} + } + + // find any hardcoded service account objects are in the bundle, then check if they match any sa definition in the csv + var errs []errors.Error + for _, obj := range bundle.Objects { + if obj.GroupVersionKind() != v1.SchemeGroupVersion.WithKind("ServiceAccount") { + continue + } + sa := v1.ServiceAccount{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &sa); err == nil { + if _, ok := saNamesFromCSV[sa.Name]; ok { + errs = append(errs, errors.ErrInvalidBundle(fmt.Sprintf("invalid service account found in bundle. " + + "This service account %s in your bundle is not valid, because a service account with the same name " + + "was already specified in your CSV. If this was unintentional, please remove the service account " + + "manifest from your bundle. If it was intentional to specify a separate service account, " + + "please rename the SA in either the bundle manifest or the CSV.",sa.Name), sa.Name)) + } + } + } + + return errs +} + func validateOwnedCRDs(bundle *manifests.Bundle, csv *operatorsv1alpha1.ClusterServiceVersion) (result errors.ManifestResult) { ownedKeys := getOwnedCustomResourceDefintionKeys(csv) diff --git a/staging/api/pkg/validation/internal/bundle_test.go b/staging/api/pkg/validation/internal/bundle_test.go index 762343faa0..6469247e2e 100644 --- a/staging/api/pkg/validation/internal/bundle_test.go +++ b/staging/api/pkg/validation/internal/bundle_test.go @@ -4,6 +4,8 @@ import ( "testing" "github.com/operator-framework/api/pkg/manifests" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/stretchr/testify/require" ) @@ -43,20 +45,118 @@ func TestValidateBundle(t *testing.T) { hasError: true, errString: `duplicate CRD "test.example.com/v1alpha1, Kind=Test" in bundle "test-operator.v0.0.1"`, }, + { + description: "invalid bundle service account can't match sa in csv", + directory: "./testdata/invalid_bundle_sa", + hasError: true, + errString: `invalid service account found in bundle. This service account etcd-operator in your bundle is not valid, because a service account with the same name was already specified in your CSV. If this was unintentional, please remove the service account manifest from your bundle. If it was intentional to specify a separate service account, please rename the SA in either the bundle manifest or the CSV.`, + }, } for _, tt := range table { - // Validate the bundle object - bundle, err := manifests.GetBundleFromDir(tt.directory) - require.NoError(t, err) + t.Run(tt.description, func(t *testing.T) { + // Validate the bundle object + bundle, err := manifests.GetBundleFromDir(tt.directory) + require.NoError(t, err) - results := BundleValidator.Validate(bundle) + results := BundleValidator.Validate(bundle) - if len(results) > 0 { - require.Equal(t, results[0].HasError(), tt.hasError) - if results[0].HasError() { + require.Greater(t, len(results), 0) + if tt.hasError { + require.True(t, results[0].HasError(), "found no error when an error was expected") require.Contains(t, results[0].Errors[0].Error(), tt.errString) + } else { + require.False(t, results[0].HasError(), "found error when an error was not expected") } + }) + } +} + +func TestValidateServiceAccount(t *testing.T) { + csvWithSAs := func(saNames ...string) *v1alpha1.ClusterServiceVersion { + csv := &v1alpha1.ClusterServiceVersion{} + depSpecs := make([]v1alpha1.StrategyDeploymentSpec, len(saNames)) + for i, saName := range saNames { + depSpecs[i].Spec.Template.Spec.ServiceAccountName = saName } + csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs = depSpecs + return csv + } + + var table = []struct { + description string + bundle *manifests.Bundle + hasError bool + errString string + }{ + { + description: "an object with the same name as the service account", + bundle: &manifests.Bundle{ + CSV: csvWithSAs("foo"), + Objects: []*unstructured.Unstructured{ + {Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "foo", + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "serviceAccountName": "foo", + }, + }, + }, + }}, + }, + }, + hasError: false, + }, + { + description: "service account included in both CSV and bundle", + bundle: &manifests.Bundle{ + CSV: csvWithSAs("foo"), + Objects: []*unstructured.Unstructured{ + {Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "foo", + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "serviceAccountName": "foo", + }, + }, + }, + }}, + {Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": map[string]interface{}{ + "name": "foo", + }, + }}, + }, + }, + hasError: true, + errString: `invalid service account found in bundle. This service account foo in your bundle is not valid, because a service account with the same name was already specified in your CSV. If this was unintentional, please remove the service account manifest from your bundle. If it was intentional to specify a separate service account, please rename the SA in either the bundle manifest or the CSV.`, + }, + } + + for _, tt := range table { + t.Run(tt.description, func(t *testing.T) { + // Validate the bundle object + results := BundleValidator.Validate(tt.bundle) + + require.Greater(t, len(results), 0) + if tt.hasError { + require.True(t, results[0].HasError(), "found no error when an error was expected") + require.Contains(t, results[0].Errors[0].Error(), tt.errString) + } else { + require.False(t, results[0].HasError(), "found error when an error was not expected") + } + }) } } diff --git a/staging/api/pkg/validation/internal/community.go b/staging/api/pkg/validation/internal/community.go index 4157332370..3a42d7ec2e 100644 --- a/staging/api/pkg/validation/internal/community.go +++ b/staging/api/pkg/validation/internal/community.go @@ -21,6 +21,9 @@ const IndexImagePathKey = "index-path" // where the bundle will be distributed const ocpLabelindex = "com.redhat.openshift.versions" +// OCP version where the apis v1beta1 is no longer supported +const ocpVerV1beta1Unsupported = "4.9" + // CommunityOperatorValidator validates the bundle manifests against the required criteria to publish // the projects on the community operators // diff --git a/staging/api/pkg/validation/internal/operatorhub.go b/staging/api/pkg/validation/internal/operatorhub.go index 2aa4553050..1b56b6bf28 100644 --- a/staging/api/pkg/validation/internal/operatorhub.go +++ b/staging/api/pkg/validation/internal/operatorhub.go @@ -18,20 +18,72 @@ import ( interfaces "github.com/operator-framework/api/pkg/validation/interfaces" ) -// k8sVersionKey defines the key which can be used by its consumers -// to inform what is the K8S version that should be used to do the tests against. -const k8sVersionKey = "k8s-version" - -const minKubeVersionWarnMessage = "csv.Spec.minKubeVersion is not informed. It is recommended you provide this information. " + - "Otherwise, it would mean that your operator project can be distributed and installed in any cluster version " + - "available, which is not necessarily the case for all projects." - // OperatorHubValidator validates the bundle manifests against the required criteria to publish // the projects on OperatorHub.io. // +// This validator will ensure that: +// - The annotations capabilities into the CSV has a valid option, which are: +// * "Basic Install" +// * "Seamless Upgrades" +// * "Full Lifecycle" +// * "Deep Insights" +// * "Auto Pilot" +// - The annotations categories into the CSV has a valid option, which are: +// * "AI/Machine Learning" +// * "Application Runtime" +// * "Big Data" +// * "Cloud Provider" +// * "Developer Tools" +// * "Database" +// * "Integration & Delivery" +// * "Logging & Tracing" +// * "Modernization & Migration" +// * "Monitoring" +// * "Networking" +// * "OpenShift Optional" +// * "Security" +// * "Storage" +// * "Streaming & Messaging" +// +// NOTE: The operatorhub validator can verify against custom bundle categories by setting the OPERATOR_BUNDLE_CATEGORIES +// environment variable. Setting the OPERATOR_BUNDLE_CATEGORIES environment variable to the path to a json file +// containing a list of categories will enable those categories to be used when comparing CSV categories for +// operatorhub validation. The json file should be in the following format: +// +// ```json +// { +// "categories":[ +// "Cloud Pak", +// "Registry", +// "MyCoolThing", +// ] +// } +// ``` +// +// - The csv.Spec.Provider.Name was provided +// - The csv.Spec.Maintainers elements contains both name and email +// - The csv.Spec.Links elements contains both name and url +// - The csv.Spec.Links.Url is a valid value +// - The csv.Spec.Version is provided +// - The checks.csv.Spec.Icon was provided and has not more than one element +// - The csv.Spec.Icon elements should contain both data and mediatype +// - The csv.Spec.Icon elements should contain both data and mediatype +// - The csv.Spec.Icon has a valid mediatype, which are +// * "image/gif" +// * "image/jpeg" +// * "image/png" +// * "image/svg+xml" +// - If informed ONLY, check if the value csv.Spec.MinKubeVersion is parsable according to semver (https://semver.org/) +// Also, this validator will raise warnings when: +// - The bundle name (CSV.metadata.name) does not follow the naming convention: .v e.g. memcached-operator.v0.0.1 +// NOTE: The bundle name must be 63 characters or less because it will be used as k8s ownerref label which only allows max of 63 characters. +// - The channel names seems are not following the convention https://olm.operatorframework.io/docs/best-practices/channel-naming/ +// - The usage of the removed APIs on Kubernetes 1.22 is found. More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22 +// // Note that this validator allows to receive a List of optional values as key=values. Currently, only the // `k8s-version` key is allowed. If informed, it will perform the checks against this specific Kubernetes version where the -// operator bundle is intend to be distribute for. +// operator bundle is intend to be used and will raise errors instead of warnings. +// Currently, this check is capable of verifying the removed APIs only for Kubernetes 1.22 version. var OperatorHubValidator interfaces.Validator = interfaces.ValidatorFunc(validateOperatorHub) var validCapabilities = map[string]struct{}{ @@ -50,20 +102,21 @@ var validMediatypes = map[string]struct{}{ } var validCategories = map[string]struct{}{ - "AI/Machine Learning": {}, - "Application Runtime": {}, - "Big Data": {}, - "Cloud Provider": {}, - "Developer Tools": {}, - "Database": {}, - "Integration & Delivery": {}, - "Logging & Tracing": {}, - "Monitoring": {}, - "Networking": {}, - "OpenShift Optional": {}, - "Security": {}, - "Storage": {}, - "Streaming & Messaging": {}, + "AI/Machine Learning": {}, + "Application Runtime": {}, + "Big Data": {}, + "Cloud Provider": {}, + "Developer Tools": {}, + "Database": {}, + "Integration & Delivery": {}, + "Logging & Tracing": {}, + "Monitoring": {}, + "Modernization & Migration": {}, + "Networking": {}, + "OpenShift Optional": {}, + "Security": {}, + "Storage": {}, + "Streaming & Messaging": {}, } func validateOperatorHub(objs ...interface{}) (results []errors.ManifestResult) { @@ -111,7 +164,7 @@ func validateBundleOperatorHub(bundle *manifests.Bundle, k8sVersion string) erro result.Add(errors.WarnInvalidCSV(warn.Error(), bundle.CSV.GetName())) } - errs, warns := validateHubDeprecatedAPIS(bundle, k8sVersion) + errs, warns := validateDeprecatedAPIS(bundle, k8sVersion) for _, err := range errs { result.Add(errors.ErrFailedValidation(err.Error(), bundle.CSV.GetName())) } @@ -153,77 +206,6 @@ func validateHubChannels(channels []string) error { return nil } -// validateHubDeprecatedAPIS will check if the operator bundle is using a deprecated or no longer supported k8s api -// Note if the k8s was informed via "k8s=1.22" it will be used. Otherwise, we will use the minKubeVersion in -// the CSV to do the checks. So, the criteria is >=minKubeVersion. By last, if the minKubeVersion is not provided -// then, we should consider the operator bundle is intend to work well in any Kubernetes version. -// Then, it means that: -//--optional-values="k8s-version=value" flag with a value => 1.16 <= 1.22 the validator will return result as warning. -//--optional-values="k8s-version=value" flag with a value => 1.22 the validator will return result as error. -//minKubeVersion >= 1.22 return the error result. -//minKubeVersion empty returns a warning since it would mean the same of allow install in any supported version -func validateHubDeprecatedAPIS(bundle *manifests.Bundle, versionProvided string) (errs, warns []error) { - // K8s version where the apis v1betav1 is no longer supported - const k8sVerV1betav1Unsupported = "1.22.0" - // K8s version where the apis v1betav1 was deprecated - const k8sVerV1betav1Deprecated = "1.16.0" - // semver of the K8s version where the apis v1betav1 is no longer supported to allow us compare - semVerK8sVerV1betav1Unsupported := semver.MustParse(k8sVerV1betav1Unsupported) - // semver of the K8s version where the apis v1betav1 is deprecated to allow us compare - semVerk8sVerV1betav1Deprecated := semver.MustParse(k8sVerV1betav1Deprecated) - // isVersionProvided defines if the k8s version to test against was or not informed - isVersionProvided := len(versionProvided) > 0 - - // Transform the key/option versionProvided in semver Version to compare - var semVerVersionProvided semver.Version - if isVersionProvided { - var err error - semVerVersionProvided, err = semver.ParseTolerant(versionProvided) - if err != nil { - errs = append(errs, fmt.Errorf("invalid value informed via the k8s key option : %s", versionProvided)) - } else { - // we might want to return it as info instead of warning in the future. - warns = append(warns, fmt.Errorf("checking APIs against Kubernetes version : %s", versionProvided)) - } - } - - // Transform the spec minKubeVersion in semver Version to compare - var semverMinKube semver.Version - if len(bundle.CSV.Spec.MinKubeVersion) > 0 { - var err error - if semverMinKube, err = semver.ParseTolerant(bundle.CSV.Spec.MinKubeVersion); err != nil { - errs = append(errs, fmt.Errorf("unable to use csv.Spec.MinKubeVersion to verify the CRD/Webhook apis "+ - "because it has an invalid value: %s", bundle.CSV.Spec.MinKubeVersion)) - } - } - - // if the k8s value was informed and it is >=1.16 we should check - // if the k8s value was not informed we also should check since the - // check should occurs with any minKubeVersion value: - // - if minKubeVersion empty then means that the project can be installed in any version - // - if minKubeVersion any version defined it means that we are considering install - // in any upper version from that where the check is always applied - if !isVersionProvided || semVerVersionProvided.GE(semVerk8sVerV1betav1Deprecated) { - deprecatedAPIs := getRemovedAPIsOn1_22From(bundle) - if len(deprecatedAPIs) > 0 { - deprecatedAPIsMessage := generateMessageWithDeprecatedAPIs(deprecatedAPIs) - // isUnsupported is true only if the key/value OR minKubeVersion were informed and are >= 1.22 - isUnsupported := semVerVersionProvided.GE(semVerK8sVerV1betav1Unsupported) || - semverMinKube.GE(semVerK8sVerV1betav1Unsupported) - // We only raise an error when the version >= 1.22 was informed via - // the k8s key/value option or is specifically defined in the CSV - msg := fmt.Errorf("this bundle is using APIs which were deprecated and removed in v1.22. More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. Migrate the API(s) for %s", deprecatedAPIsMessage) - if isUnsupported { - errs = append(errs, msg) - } else { - warns = append(warns, msg) - } - } - } - - return errs, warns -} - // validateHubCSVSpec will check the CSV against the criteria to publish an // operator bundle in the OperatorHub.io func validateHubCSVSpec(csv v1alpha1.ClusterServiceVersion) CSVChecks { diff --git a/staging/api/pkg/validation/internal/operatorhub_test.go b/staging/api/pkg/validation/internal/operatorhub_test.go index f616276b1b..a046ad321c 100644 --- a/staging/api/pkg/validation/internal/operatorhub_test.go +++ b/staging/api/pkg/validation/internal/operatorhub_test.go @@ -343,125 +343,6 @@ func TestCheckSpecMinKubeVersion(t *testing.T) { } } -func TestValidateHubDeprecatedAPIS(t *testing.T) { - type args struct { - minKubeVersion string - k8sVersion string - directory string - } - tests := []struct { - name string - args args - wantError bool - wantWarning bool - errStrings []string - warnStrings []string - }{ - { - name: "should not return error or warning when the k8sVersion is <= 1.15", - args: args{ - k8sVersion: "1.15", - minKubeVersion: "", - directory: "./testdata/valid_bundle_v1beta1", - }, - wantWarning: true, - warnStrings: []string{"checking APIs against Kubernetes version : 1.15"}, - }, - { - name: "should return a warning when has the CRD v1beta1 and minKubeVersion is informed", - args: args{ - k8sVersion: "", - minKubeVersion: "1.11.3", - directory: "./testdata/valid_bundle_v1beta1", - }, - wantWarning: true, - warnStrings: []string{"this bundle is using APIs which were deprecated and removed in v1.22. " + - "More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. " + - "Migrate the API(s) for CRD: ([\"etcdbackups.etcd.database.coreos.com\" " + - "\"etcdclusters.etcd.database.coreos.com\" \"etcdrestores.etcd.database.coreos.com\"])"}, - }, - { - name: "should not return a warning or error when has minKubeVersion but the k8sVersion informed is <= 1.15", - args: args{ - k8sVersion: "1.15", - minKubeVersion: "1.11.3", - directory: "./testdata/valid_bundle_v1beta1", - }, - wantWarning: true, - warnStrings: []string{"checking APIs against Kubernetes version : 1.15"}, - }, - { - name: "should return an error when the k8sVersion is >= 1.22 and has the deprecated API", - args: args{ - k8sVersion: "1.22", - minKubeVersion: "", - directory: "./testdata/valid_bundle_v1beta1", - }, - wantError: true, - errStrings: []string{"this bundle is using APIs which were deprecated and removed in v1.22. " + - "More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. " + - "Migrate the API(s) for CRD: ([\"etcdbackups.etcd.database.coreos.com\"" + - " \"etcdclusters.etcd.database.coreos.com\" \"etcdrestores.etcd.database.coreos.com\"])"}, - wantWarning: true, - warnStrings: []string{"checking APIs against Kubernetes version : 1.22"}, - }, - { - name: "should return an error when the k8sVersion informed is invalid", - args: args{ - k8sVersion: "invalid", - minKubeVersion: "", - directory: "./testdata/valid_bundle_v1beta1", - }, - wantError: true, - errStrings: []string{"invalid value informed via the k8s key option : invalid"}, - }, - { - name: "should return an error when the csv.spec.minKubeVersion informed is invalid", - args: args{ - minKubeVersion: "invalid", - directory: "./testdata/valid_bundle_v1beta1", - }, - wantError: true, - wantWarning: true, - errStrings: []string{"unable to use csv.Spec.MinKubeVersion to verify the CRD/Webhook apis because it " + - "has an invalid value: invalid"}, - warnStrings: []string{"this bundle is using APIs which were deprecated and removed in v1.22. " + - "More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. " + - "Migrate the API(s) for CRD: ([\"etcdbackups.etcd.database.coreos.com\" " + - "\"etcdclusters.etcd.database.coreos.com\" \"etcdrestores.etcd.database.coreos.com\"])"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Validate the bundle object - bundle, err := manifests.GetBundleFromDir(tt.args.directory) - require.NoError(t, err) - - bundle.CSV.Spec.MinKubeVersion = tt.args.minKubeVersion - - errsResult, warnsResult := validateHubDeprecatedAPIS(bundle, tt.args.k8sVersion) - - require.Equal(t, tt.wantWarning, len(warnsResult) > 0) - if tt.wantWarning { - require.Equal(t, len(tt.warnStrings), len(warnsResult)) - for _, w := range warnsResult { - wString := w.Error() - require.Contains(t, tt.warnStrings, wString) - } - } - - require.Equal(t, tt.wantError, len(errsResult) > 0) - if tt.wantError { - require.Equal(t, len(tt.errStrings), len(errsResult)) - for _, err := range errsResult { - errString := err.Error() - require.Contains(t, tt.errStrings, errString) - } - } - }) - } -} - func TestValidateHubChannels(t *testing.T) { type args struct { channels []string diff --git a/staging/api/pkg/validation/internal/removed_apis.go b/staging/api/pkg/validation/internal/removed_apis.go index 028176e64a..9377b50b00 100644 --- a/staging/api/pkg/validation/internal/removed_apis.go +++ b/staging/api/pkg/validation/internal/removed_apis.go @@ -2,13 +2,148 @@ package internal import ( "fmt" + "github.com/blang/semver" "github.com/operator-framework/api/pkg/manifests" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/operator-framework/api/pkg/validation/errors" + interfaces "github.com/operator-framework/api/pkg/validation/interfaces" ) -// OCP version where the apis v1beta1 is no longer supported -const ocpVerV1beta1Unsupported = "4.9" +// k8sVersionKey defines the key which can be used by its consumers +// to inform what is the K8S version that should be used to do the tests against. +const k8sVersionKey = "k8s-version" + +const minKubeVersionWarnMessage = "csv.Spec.minKubeVersion is not informed. It is recommended you provide this information. " + + "Otherwise, it would mean that your operator project can be distributed and installed in any cluster version " + + "available, which is not necessarily the case for all projects." + +// K8s version where the apis v1betav1 is no longer supported +const k8sVerV1betav1Unsupported = "1.22.0" + +// K8s version where the apis v1betav1 was deprecated +const k8sVerV1betav1Deprecated = "1.16.0" + +// AlphaDeprecatedAPIsValidator validates if the bundles is using versions API version which are deprecate or +// removed in specific Kubernetes versions informed via optional key value `k8s-version`. +var AlphaDeprecatedAPIsValidator interfaces.Validator = interfaces.ValidatorFunc(validateDeprecatedAPIsValidator) + +func validateDeprecatedAPIsValidator(objs ...interface{}) (results []errors.ManifestResult) { + + // Obtain the k8s version if informed via the objects an optional + k8sVersion := "" + for _, obj := range objs { + switch obj.(type) { + case map[string]string: + k8sVersion = obj.(map[string]string)[k8sVersionKey] + if len(k8sVersion) > 0 { + break + } + } + } + + for _, obj := range objs { + switch v := obj.(type) { + case *manifests.Bundle: + results = append(results, validateDeprecatedAPIs(v, k8sVersion)) + } + } + + return results +} + +func validateDeprecatedAPIs(bundle *manifests.Bundle, k8sVersion string) errors.ManifestResult { + result := errors.ManifestResult{Name: bundle.Name} + + if bundle == nil { + result.Add(errors.ErrInvalidBundle("Bundle is nil", nil)) + return result + } + + if bundle.CSV == nil { + result.Add(errors.ErrInvalidBundle("Bundle csv is nil", bundle.Name)) + return result + } + + errs, warns := validateDeprecatedAPIS(bundle, k8sVersion) + for _, err := range errs { + result.Add(errors.ErrFailedValidation(err.Error(), bundle.CSV.GetName())) + } + for _, warn := range warns { + result.Add(errors.WarnFailedValidation(warn.Error(), bundle.CSV.GetName())) + } + + return result +} + +// validateDeprecatedAPIS will check if the operator bundle is using a deprecated or no longer supported k8s api +// Note if the k8s was informed via "k8s=1.22" it will be used. Otherwise, we will use the minKubeVersion in +// the CSV to do the checks. So, the criteria is >=minKubeVersion. By last, if the minKubeVersion is not provided +// then, we should consider the operator bundle is intend to work well in any Kubernetes version. +// Then, it means that: +//--optional-values="k8s-version=value" flag with a value => 1.16 <= 1.22 the validator will return result as warning. +//--optional-values="k8s-version=value" flag with a value => 1.22 the validator will return result as error. +//minKubeVersion >= 1.22 return the error result. +//minKubeVersion empty returns a warning since it would mean the same of allow install in any supported version +func validateDeprecatedAPIS(bundle *manifests.Bundle, versionProvided string) (errs, warns []error) { + + // semver of the K8s version where the apis v1betav1 is no longer supported to allow us compare + semVerK8sVerV1betav1Unsupported := semver.MustParse(k8sVerV1betav1Unsupported) + // semver of the K8s version where the apis v1betav1 is deprecated to allow us compare + semVerk8sVerV1betav1Deprecated := semver.MustParse(k8sVerV1betav1Deprecated) + // isVersionProvided defines if the k8s version to test against was or not informed + isVersionProvided := len(versionProvided) > 0 + + // Transform the key/option versionProvided in semver Version to compare + var semVerVersionProvided semver.Version + if isVersionProvided { + var err error + semVerVersionProvided, err = semver.ParseTolerant(versionProvided) + if err != nil { + errs = append(errs, fmt.Errorf("invalid value informed via the k8s key option : %s", versionProvided)) + } else { + // we might want to return it as info instead of warning in the future. + warns = append(warns, fmt.Errorf("checking APIs against Kubernetes version : %s", versionProvided)) + } + } + + // Transform the spec minKubeVersion in semver Version to compare + var semverMinKube semver.Version + if len(bundle.CSV.Spec.MinKubeVersion) > 0 { + var err error + if semverMinKube, err = semver.ParseTolerant(bundle.CSV.Spec.MinKubeVersion); err != nil { + errs = append(errs, fmt.Errorf("unable to use csv.Spec.MinKubeVersion to verify the CRD/Webhook apis "+ + "because it has an invalid value: %s", bundle.CSV.Spec.MinKubeVersion)) + } + } + + // if the k8s value was informed and it is >=1.16 we should check + // if the k8s value was not informed we also should check since the + // check should occurs with any minKubeVersion value: + // - if minKubeVersion empty then means that the project can be installed in any version + // - if minKubeVersion any version defined it means that we are considering install + // in any upper version from that where the check is always applied + if !isVersionProvided || semVerVersionProvided.GE(semVerk8sVerV1betav1Deprecated) { + deprecatedAPIs := getRemovedAPIsOn1_22From(bundle) + if len(deprecatedAPIs) > 0 { + deprecatedAPIsMessage := generateMessageWithDeprecatedAPIs(deprecatedAPIs) + // isUnsupported is true only if the key/value OR minKubeVersion were informed and are >= 1.22 + isUnsupported := semVerVersionProvided.GE(semVerK8sVerV1betav1Unsupported) || + semverMinKube.GE(semVerK8sVerV1betav1Unsupported) + // We only raise an error when the version >= 1.22 was informed via + // the k8s key/value option or is specifically defined in the CSV + msg := fmt.Errorf("this bundle is using APIs which were deprecated and removed in v1.22. More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. Migrate the API(s) for %s", deprecatedAPIsMessage) + if isUnsupported { + errs = append(errs, msg) + } else { + warns = append(warns, msg) + } + } + } + + return errs, warns +} // generateMessageWithDeprecatedAPIs will return a list with the kind and the name // of the resource which were found and required to be upgraded diff --git a/staging/api/pkg/validation/internal/removed_apis_test.go b/staging/api/pkg/validation/internal/removed_apis_test.go index a3bd71c68a..440b34a69c 100644 --- a/staging/api/pkg/validation/internal/removed_apis_test.go +++ b/staging/api/pkg/validation/internal/removed_apis_test.go @@ -63,3 +63,122 @@ func Test_getDeprecatedAPIs(t *testing.T) { }) } } + +func TestValidateDeprecatedAPIS(t *testing.T) { + type args struct { + minKubeVersion string + k8sVersion string + directory string + } + tests := []struct { + name string + args args + wantError bool + wantWarning bool + errStrings []string + warnStrings []string + }{ + { + name: "should not return error or warning when the k8sVersion is <= 1.15", + args: args{ + k8sVersion: "1.15", + minKubeVersion: "", + directory: "./testdata/valid_bundle_v1beta1", + }, + wantWarning: true, + warnStrings: []string{"checking APIs against Kubernetes version : 1.15"}, + }, + { + name: "should return a warning when has the CRD v1beta1 and minKubeVersion is informed", + args: args{ + k8sVersion: "", + minKubeVersion: "1.11.3", + directory: "./testdata/valid_bundle_v1beta1", + }, + wantWarning: true, + warnStrings: []string{"this bundle is using APIs which were deprecated and removed in v1.22. " + + "More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. " + + "Migrate the API(s) for CRD: ([\"etcdbackups.etcd.database.coreos.com\" " + + "\"etcdclusters.etcd.database.coreos.com\" \"etcdrestores.etcd.database.coreos.com\"])"}, + }, + { + name: "should not return a warning or error when has minKubeVersion but the k8sVersion informed is <= 1.15", + args: args{ + k8sVersion: "1.15", + minKubeVersion: "1.11.3", + directory: "./testdata/valid_bundle_v1beta1", + }, + wantWarning: true, + warnStrings: []string{"checking APIs against Kubernetes version : 1.15"}, + }, + { + name: "should return an error when the k8sVersion is >= 1.22 and has the deprecated API", + args: args{ + k8sVersion: "1.22", + minKubeVersion: "", + directory: "./testdata/valid_bundle_v1beta1", + }, + wantError: true, + errStrings: []string{"this bundle is using APIs which were deprecated and removed in v1.22. " + + "More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. " + + "Migrate the API(s) for CRD: ([\"etcdbackups.etcd.database.coreos.com\"" + + " \"etcdclusters.etcd.database.coreos.com\" \"etcdrestores.etcd.database.coreos.com\"])"}, + wantWarning: true, + warnStrings: []string{"checking APIs against Kubernetes version : 1.22"}, + }, + { + name: "should return an error when the k8sVersion informed is invalid", + args: args{ + k8sVersion: "invalid", + minKubeVersion: "", + directory: "./testdata/valid_bundle_v1beta1", + }, + wantError: true, + errStrings: []string{"invalid value informed via the k8s key option : invalid"}, + }, + { + name: "should return an error when the csv.spec.minKubeVersion informed is invalid", + args: args{ + minKubeVersion: "invalid", + directory: "./testdata/valid_bundle_v1beta1", + }, + wantError: true, + wantWarning: true, + errStrings: []string{"unable to use csv.Spec.MinKubeVersion to verify the CRD/Webhook apis because it " + + "has an invalid value: invalid"}, + warnStrings: []string{"this bundle is using APIs which were deprecated and removed in v1.22. " + + "More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. " + + "Migrate the API(s) for CRD: ([\"etcdbackups.etcd.database.coreos.com\" " + + "\"etcdclusters.etcd.database.coreos.com\" \"etcdrestores.etcd.database.coreos.com\"])"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Validate the bundle object + bundle, err := manifests.GetBundleFromDir(tt.args.directory) + require.NoError(t, err) + + bundle.CSV.Spec.MinKubeVersion = tt.args.minKubeVersion + + errsResult, warnsResult := validateDeprecatedAPIS(bundle, tt.args.k8sVersion) + + require.Equal(t, tt.wantWarning, len(warnsResult) > 0) + if tt.wantWarning { + require.Equal(t, len(tt.warnStrings), len(warnsResult)) + for _, w := range warnsResult { + wString := w.Error() + require.Contains(t, tt.warnStrings, wString) + } + } + + require.Equal(t, tt.wantError, len(errsResult) > 0) + if tt.wantError { + require.Equal(t, len(tt.errStrings), len(errsResult)) + for _, err := range errsResult { + errString := err.Error() + require.Contains(t, tt.errStrings, errString) + } + } + }) + } +} diff --git a/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdbackups.etcd.database.coreos.com.crd.yaml b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdbackups.etcd.database.coreos.com.crd.yaml new file mode 100644 index 0000000000..5afc088b99 --- /dev/null +++ b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdbackups.etcd.database.coreos.com.crd.yaml @@ -0,0 +1,13 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: etcdbackups.etcd.database.coreos.com +spec: + group: etcd.database.coreos.com + names: + kind: EtcdBackup + listKind: EtcdBackupList + plural: etcdbackups + singular: etcdbackup + scope: Namespaced + version: v1beta2 diff --git a/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdclusters.etcd.database.coreos.com.crd.yaml b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdclusters.etcd.database.coreos.com.crd.yaml new file mode 100644 index 0000000000..01111e5c5d --- /dev/null +++ b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdclusters.etcd.database.coreos.com.crd.yaml @@ -0,0 +1,16 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: etcdclusters.etcd.database.coreos.com +spec: + group: etcd.database.coreos.com + names: + kind: EtcdCluster + listKind: EtcdClusterList + plural: etcdclusters + shortNames: + - etcdclus + - etcd + singular: etcdcluster + scope: Namespaced + version: v1beta2 diff --git a/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdoperator.v0.9.4.clusterserviceversion.yaml b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdoperator.v0.9.4.clusterserviceversion.yaml new file mode 100644 index 0000000000..67e2d1562f --- /dev/null +++ b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdoperator.v0.9.4.clusterserviceversion.yaml @@ -0,0 +1,309 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: "[\n {\n \"apiVersion\": \"etcd.database.coreos.com/v1beta2\"\ + ,\n \"kind\": \"EtcdCluster\",\n \"metadata\": {\n \"name\": \"example\"\ + \n },\n \"spec\": {\n \"size\": 3,\n \"version\": \"3.2.13\"\ + \n }\n },\n {\n \"apiVersion\": \"etcd.database.coreos.com/v1beta2\"\ + ,\n \"kind\": \"EtcdRestore\",\n \"metadata\": {\n \"name\": \"example-etcd-cluster-restore\"\ + \n },\n \"spec\": {\n \"etcdCluster\": {\n \"name\": \"example-etcd-cluster\"\ + \n },\n \"backupStorageType\": \"S3\",\n \"s3\": {\n \"\ + path\": \"\",\n \"awsSecret\": \"\"\n \ + \ }\n }\n },\n {\n \"apiVersion\": \"etcd.database.coreos.com/v1beta2\"\ + ,\n \"kind\": \"EtcdBackup\",\n \"metadata\": {\n \"name\": \"example-etcd-cluster-backup\"\ + \n },\n \"spec\": {\n \"etcdEndpoints\": [\"\"\ + ],\n \"storageType\":\"S3\",\n \"s3\": {\n \"path\": \"\"\ + ,\n \"awsSecret\": \"\"\n }\n }\n }\n]\n" + capabilities: Full Lifecycle + categories: Database, Big Data + containerImage: quay.io/coreos/etcd-operator@sha256:66a37fd61a06a43969854ee6d3e21087a98b93838e284a6086b13917f96b0d9b + createdAt: 2019-02-28 01:03:00 + description: Create and maintain highly-available etcd clusters on Kubernetes + repository: https://github.com/coreos/etcd-operator + tectonic-visibility: ocs + name: etcdoperator.v0.9.4 + namespace: placeholder +spec: + customresourcedefinitions: + owned: + - description: Represents a cluster of etcd nodes. + displayName: etcd Cluster + kind: EtcdCluster + name: etcdclusters.etcd.database.coreos.com + resources: + - kind: Service + version: v1 + - kind: Pod + version: v1 + specDescriptors: + - description: The desired number of member Pods for the etcd cluster. + displayName: Size + path: size + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podCount + - description: Limits describes the minimum/maximum amount of compute resources + required/allowed + displayName: Resource Requirements + path: pod.resources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + statusDescriptors: + - description: The status of each of the member Pods for the etcd cluster. + displayName: Member Status + path: members + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podStatuses + - description: The service at which the running etcd cluster can be accessed. + displayName: Service + path: serviceName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Service + - description: The current size of the etcd cluster. + displayName: Cluster Size + path: size + - description: The current version of the etcd cluster. + displayName: Current Version + path: currentVersion + - description: The target version of the etcd cluster, after upgrading. + displayName: Target Version + path: targetVersion + - description: The current status of the etcd cluster. + displayName: Status + path: phase + x-descriptors: + - urn:alm:descriptor:io.kubernetes.phase + - description: Explanation for the current status of the cluster. + displayName: Status Details + path: reason + x-descriptors: + - urn:alm:descriptor:io.kubernetes.phase:reason + version: v1beta2 + - description: Represents the intent to backup an etcd cluster. + displayName: etcd Backup + kind: EtcdBackup + name: etcdbackups.etcd.database.coreos.com + specDescriptors: + - description: Specifies the endpoints of an etcd cluster. + displayName: etcd Endpoint(s) + path: etcdEndpoints + x-descriptors: + - urn:alm:descriptor:etcd:endpoint + - description: The full AWS S3 path where the backup is saved. + displayName: S3 Path + path: s3.path + x-descriptors: + - urn:alm:descriptor:aws:s3:path + - description: The name of the secret object that stores the AWS credential + and config files. + displayName: AWS Secret + path: s3.awsSecret + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret + statusDescriptors: + - description: Indicates if the backup was successful. + displayName: Succeeded + path: succeeded + x-descriptors: + - urn:alm:descriptor:text + - description: Indicates the reason for any backup related failures. + displayName: Reason + path: reason + x-descriptors: + - urn:alm:descriptor:io.kubernetes.phase:reason + version: v1beta2 + - description: Represents the intent to restore an etcd cluster from a backup. + displayName: etcd Restore + kind: EtcdRestore + name: etcdrestores.etcd.database.coreos.com + specDescriptors: + - description: References the EtcdCluster which should be restored, + displayName: etcd Cluster + path: etcdCluster.name + x-descriptors: + - urn:alm:descriptor:io.kubernetes:EtcdCluster + - urn:alm:descriptor:text + - description: The full AWS S3 path where the backup is saved. + displayName: S3 Path + path: s3.path + x-descriptors: + - urn:alm:descriptor:aws:s3:path + - description: The name of the secret object that stores the AWS credential + and config files. + displayName: AWS Secret + path: s3.awsSecret + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret + statusDescriptors: + - description: Indicates if the restore was successful. + displayName: Succeeded + path: succeeded + x-descriptors: + - urn:alm:descriptor:text + - description: Indicates the reason for any restore related failures. + displayName: Reason + path: reason + x-descriptors: + - urn:alm:descriptor:io.kubernetes.phase:reason + version: v1beta2 + description: "The etcd Operater creates and maintains highly-available etcd clusters\ + \ on Kubernetes, allowing engineers to easily deploy and manage etcd clusters\ + \ for their applications.\n\netcd is a distributed key value store that provides\ + \ a reliable way to store data across a cluster of machines. It\xE2\u20AC\u2122\ + s open-source and available on GitHub. etcd gracefully handles leader elections\ + \ during network partitions and will tolerate machine failure, including the leader.\n\ + \n\n### Reading and writing to etcd\n\nCommunicate with etcd though its command\ + \ line utility `etcdctl` via port forwarding:\n\n $ kubectl --namespace default\ + \ port-forward service/example-client 2379:2379\n $ etcdctl --endpoints http://127.0.0.1:2379\ + \ get /\n\nOr directly to the API using the automatically generated Kubernetes\ + \ Service:\n\n $ etcdctl --endpoints http://example-client.default.svc:2379\ + \ get /\n\nBe sure to secure your etcd cluster (see Common Configurations) before\ + \ exposing it outside of the namespace or cluster.\n\n\n### Supported Features\n\ + \n* **High availability** - Multiple instances of etcd are networked together\ + \ and secured. Individual failures or networking issues are transparently handled\ + \ to keep your cluster up and running.\n\n* **Automated updates** - Rolling out\ + \ a new etcd version works like all Kubernetes rolling updates. Simply declare\ + \ the desired version, and the etcd service starts a safe rolling update to the\ + \ new version automatically.\n\n* **Backups included** - Create etcd backups and\ + \ restore them through the etcd Operator.\n\n### Common Configurations\n\n* **Configure\ + \ TLS** - Specify [static TLS certs](https://github.com/coreos/etcd-operator/blob/master/doc/user/cluster_tls.md)\ + \ as Kubernetes secrets.\n\n* **Set Node Selector and Affinity** - [Spread your\ + \ etcd Pods](https://github.com/coreos/etcd-operator/blob/master/doc/user/spec_examples.md#three-member-cluster-with-node-selector-and-anti-affinity-across-nodes)\ + \ across Nodes and availability zones.\n\n* **Set Resource Limits** - [Set the\ + \ Kubernetes limit and request](https://github.com/coreos/etcd-operator/blob/master/doc/user/spec_examples.md#three-member-cluster-with-resource-requirement)\ + \ values for your etcd Pods.\n\n* **Customize Storage** - [Set a custom StorageClass](https://github.com/coreos/etcd-operator/blob/master/doc/user/spec_examples.md#custom-persistentvolumeclaim-definition)\ + \ that you would like to use.\n" + displayName: etcd + icon: + - base64data: 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 + mediatype: image/png + install: + spec: + deployments: + - name: etcd-operator + spec: + replicas: 1 + selector: + matchLabels: + name: etcd-operator-alm-owned + template: + metadata: + labels: + name: etcd-operator-alm-owned + name: etcd-operator-alm-owned + spec: + containers: + - command: + - etcd-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + image: quay.io/coreos/etcd-operator@sha256:66a37fd61a06a43969854ee6d3e21087a98b93838e284a6086b13917f96b0d9b + name: etcd-operator + - command: + - etcd-backup-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + image: quay.io/coreos/etcd-operator@sha256:66a37fd61a06a43969854ee6d3e21087a98b93838e284a6086b13917f96b0d9b + name: etcd-backup-operator + - command: + - etcd-restore-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + image: quay.io/coreos/etcd-operator@sha256:66a37fd61a06a43969854ee6d3e21087a98b93838e284a6086b13917f96b0d9b + name: etcd-restore-operator + serviceAccountName: etcd-operator + permissions: + - rules: + - apiGroups: + - etcd.database.coreos.com + resources: + - etcdclusters + - etcdbackups + - etcdrestores + verbs: + - '*' + - apiGroups: + - '' + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + verbs: + - '*' + - apiGroups: + - apps + resources: + - deployments + verbs: + - '*' + - apiGroups: + - '' + resources: + - secrets + verbs: + - get + serviceAccountName: etcd-operator + strategy: deployment + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: false + type: AllNamespaces + keywords: + - etcd + - key value + - database + - coreos + - open source + labels: + alm-owner-etcd: etcdoperator + operated-by: etcdoperator + links: + - name: Blog + url: https://coreos.com/etcd + - name: Documentation + url: https://coreos.com/operators/etcd/docs/latest/ + - name: etcd Operator Source Code + url: https://github.com/coreos/etcd-operator + maintainers: + - email: etcd-dev@googlegroups.com + name: etcd Community + maturity: alpha + provider: + name: CNCF + replaces: etcdoperator.v0.9.2 + selector: + matchLabels: + alm-owner-etcd: etcdoperator + operated-by: etcdoperator + version: 0.9.4 diff --git a/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdrestores.etcd.database.coreos.com.crd.yaml b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdrestores.etcd.database.coreos.com.crd.yaml new file mode 100644 index 0000000000..5b851cd128 --- /dev/null +++ b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/etcdrestores.etcd.database.coreos.com.crd.yaml @@ -0,0 +1,13 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: etcdrestores.etcd.database.coreos.com +spec: + group: etcd.database.coreos.com + names: + kind: EtcdRestore + listKind: EtcdRestoreList + plural: etcdrestores + singular: etcdrestore + scope: Namespaced + version: v1beta2 diff --git a/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/sa.yaml b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/sa.yaml new file mode 100644 index 0000000000..0676e81d1f --- /dev/null +++ b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/sa.yaml @@ -0,0 +1,5 @@ +kind: ServiceAccount +apiVersion: v1 +metadata: + name: etcd-operator + namespace: etcd diff --git a/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/sa2.yaml b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/sa2.yaml new file mode 100644 index 0000000000..73121d5b80 --- /dev/null +++ b/staging/api/pkg/validation/internal/testdata/invalid_bundle_sa/sa2.yaml @@ -0,0 +1,5 @@ +kind: ServiceAccount +apiVersion: v1 +metadata: + name: etcd-operator2 + namespace: etcd diff --git a/staging/api/pkg/validation/validation.go b/staging/api/pkg/validation/validation.go index 4084a8a7a6..3c35db4b15 100644 --- a/staging/api/pkg/validation/validation.go +++ b/staging/api/pkg/validation/validation.go @@ -42,6 +42,10 @@ var OperatorGroupValidator = internal.OperatorGroupValidator // for the Community Operator requirements. var CommunityOperatorValidator = internal.CommunityOperatorValidator +// AlphaDeprecatedAPIsValidator implements Validator to validate bundle objects +// for API deprecation requirements. +var AlphaDeprecatedAPIsValidator = internal.AlphaDeprecatedAPIsValidator + // AllValidators implements Validator to validate all Operator manifest types. var AllValidators = interfaces.Validators{ PackageManifestValidator, @@ -52,6 +56,7 @@ var AllValidators = interfaces.Validators{ ObjectValidator, OperatorGroupValidator, CommunityOperatorValidator, + AlphaDeprecatedAPIsValidator, } var DefaultBundleValidators = interfaces.Validators{ diff --git a/staging/operator-registry/.github/workflows/goreleaser.yaml b/staging/operator-registry/.github/workflows/goreleaser.yaml index 9edf58a161..974baaf82b 100644 --- a/staging/operator-registry/.github/workflows/goreleaser.yaml +++ b/staging/operator-registry/.github/workflows/goreleaser.yaml @@ -47,7 +47,9 @@ jobs: UNATTENDED=1 ./build.sh - name: "Install linux cross-compilers" - run: sudo apt-get install -y gcc-aarch64-linux-gnu gcc-s390x-linux-gnu gcc-powerpc64le-linux-gnu gcc-mingw-w64-x86-64 + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu gcc-s390x-linux-gnu gcc-powerpc64le-linux-gnu gcc-mingw-w64-x86-64 - name: "Install yq" run: | @@ -88,6 +90,8 @@ jobs: password: ${{ secrets.QUAY_PASSWORD }} registry: quay.io + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 - name: "Run GoReleaser" run: make release diff --git a/staging/operator-registry/.goreleaser.yaml b/staging/operator-registry/.goreleaser.yaml index 38f0d5b876..a50dfba12e 100644 --- a/staging/operator-registry/.goreleaser.yaml +++ b/staging/operator-registry/.goreleaser.yaml @@ -8,15 +8,17 @@ builds: - amd64 env: - CC=gcc + - CGO_ENABLED=1 mod_timestamp: "{{ .CommitTimestamp }}" flags: &build-flags - - -tags=json1 + - -tags=json1,netgo,osusergo asmflags: &build-asmflags - all=-trimpath={{ .Env.PWD }} gcflags: &build-gcflags - all=-trimpath={{ .Env.PWD }} ldflags: &build-ldflags - -s -w + - -extldflags=-static - -X {{ .Env.PKG }}/cmd/opm/version.gitCommit={{ .Env.GIT_COMMIT }} - -X {{ .Env.PKG }}/cmd/opm/version.opmVersion={{ .Env.OPM_VERSION }} - -X {{ .Env.PKG }}/cmd/opm/version.buildDate={{ .Env.BUILD_DATE }} @@ -29,6 +31,7 @@ builds: - arm64 env: - CC=aarch64-linux-gnu-gcc + - CGO_ENABLED=1 mod_timestamp: "{{ .CommitTimestamp }}" flags: *build-flags asmflags: *build-asmflags @@ -43,6 +46,7 @@ builds: - ppc64le env: - CC=powerpc64le-linux-gnu-gcc + - CGO_ENABLED=1 mod_timestamp: "{{ .CommitTimestamp }}" flags: *build-flags asmflags: *build-asmflags @@ -57,6 +61,7 @@ builds: - s390x env: - CC=s390x-linux-gnu-gcc + - CGO_ENABLED=1 mod_timestamp: "{{ .CommitTimestamp }}" flags: *build-flags asmflags: *build-asmflags @@ -71,6 +76,7 @@ builds: - amd64 env: - CC=x86_64-w64-mingw32-gcc-posix + - CGO_ENABLED=1 mod_timestamp: "{{ .CommitTimestamp }}" flags: *build-flags asmflags: *build-asmflags diff --git a/staging/operator-registry/Makefile b/staging/operator-registry/Makefile index 09780e55f2..41c1674e52 100644 --- a/staging/operator-registry/Makefile +++ b/staging/operator-registry/Makefile @@ -1,7 +1,9 @@ +SHELL = /bin/bash GO := GOFLAGS="-mod=vendor" go CMDS := $(addprefix bin/, $(shell ls ./cmd | grep -v opm)) OPM := $(addprefix bin/, opm) SPECIFIC_UNIT_TEST := $(if $(TEST),-run $(TEST),) +extra_env := $(GOENV) export PKG := github.com/operator-framework/operator-registry export GIT_COMMIT := $(or $(SOURCE_GIT_COMMIT),$(shell git rev-parse --short HEAD)) export OPM_VERSION := $(or $(SOURCE_GIT_TAG),$(shell git describe --always --tags HEAD)) @@ -34,11 +36,12 @@ endif all: clean test build $(CMDS): - $(GO) build $(extra_flags) $(TAGS) -o $@ ./cmd/$(notdir $@) + $(extra_env) $(GO) build $(extra_flags) $(TAGS) -o $@ ./cmd/$(notdir $@) +.PHONY: $(OPM) $(OPM): opm_version_flags=-ldflags "-X '$(PKG)/cmd/opm/version.gitCommit=$(GIT_COMMIT)' -X '$(PKG)/cmd/opm/version.opmVersion=$(OPM_VERSION)' -X '$(PKG)/cmd/opm/version.buildDate=$(BUILD_DATE)'" $(OPM): - $(GO) build $(opm_version_flags) $(extra_flags) $(TAGS) -o $@ ./cmd/$(notdir $@) + $(extra_env) $(GO) build $(opm_version_flags) $(extra_flags) $(TAGS) -o $@ ./cmd/$(notdir $@) .PHONY: build build: clean $(CMDS) $(OPM) @@ -57,7 +60,7 @@ static: build .PHONY: unit unit: - $(GO) test -coverprofile=coverage.out $(SPECIFIC_UNIT_TEST) $(TAGS) $(TEST_RACE) -count=1 -v ./pkg/... ./alpha/... + $(GO) test -coverprofile=coverage.out $(SPECIFIC_UNIT_TEST) $(TAGS) $(TEST_RACE) -count=1 ./pkg/... ./alpha/... .PHONY: sanity-check sanity-check: @@ -145,6 +148,6 @@ release: define tagged-or-empty $(shell \ echo $(OPM_VERSION) | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$$' \ - && git describe --exact-match HEAD >/dev/null 2>&1 \ + && git describe --tags --exact-match HEAD >/dev/null 2>&1 \ && echo "$(OPM_IMAGE_REPO):$(1)" || echo "" ) endef diff --git a/staging/operator-registry/alpha/action/diff.go b/staging/operator-registry/alpha/action/diff.go index 4f7427f95a..7912cf8a7c 100644 --- a/staging/operator-registry/alpha/action/diff.go +++ b/staging/operator-registry/alpha/action/diff.go @@ -4,8 +4,12 @@ import ( "context" "errors" "fmt" + "io" + "github.com/blang/semver/v4" "github.com/sirupsen/logrus" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/yaml" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/model" @@ -21,6 +25,10 @@ type Diff struct { // of bundles included in the diff if true. SkipDependencies bool + IncludeConfig DiffIncludeConfig + // IncludeAdditively catalog objects specified in IncludeConfig. + IncludeAdditively bool + Logger *logrus.Entry } @@ -63,8 +71,10 @@ func (a Diff) Run(ctx context.Context) (*declcfg.DeclarativeConfig, error) { } g := &declcfg.DiffGenerator{ - Logger: a.Logger, - SkipDependencies: a.SkipDependencies, + Logger: a.Logger, + SkipDependencies: a.SkipDependencies, + Includer: convertIncludeConfigToIncluder(a.IncludeConfig), + IncludeAdditively: a.IncludeAdditively, } diffModel, err := g.Run(oldModel, newModel) if err != nil { @@ -81,3 +91,90 @@ func (p Diff) validate() error { } return nil } + +// DiffIncludeConfig configures Diff.Run() to include a set of packages, +// channels, and/or bundles/versions in the output DeclarativeConfig. +// These override other diff mechanisms. For example, if running in +// heads-only mode but package "foo" channel "stable" is specified, +// the entire "stable" channel (all channel bundles) is added to the output. +type DiffIncludeConfig struct { + // Packages to include. + Packages []DiffIncludePackage `json:"packages" yaml:"packages"` +} + +// DiffIncludePackage contains a name (required) and channels and/or versions +// (optional) to include in the diff. The full package is only included if no channels +// or versions are specified. +type DiffIncludePackage struct { + // Name of package. + Name string `json:"name" yaml:"name"` + // Channels to include. + Channels []DiffIncludeChannel `json:"channels,omitempty" yaml:"channels,omitempty"` + // Versions to include. All channels containing these versions + // are parsed for an upgrade graph. + Versions []semver.Version `json:"versions,omitempty" yaml:"versions,omitempty"` + // Bundles are bundle names to include. All channels containing these bundles + // are parsed for an upgrade graph. + // Set this field only if the named bundle has no semantic version metadata. + Bundles []string `json:"bundles,omitempty" yaml:"bundles,omitempty"` +} + +// DiffIncludeChannel contains a name (required) and versions (optional) +// to include in the diff. The full channel is only included if no versions are specified. +type DiffIncludeChannel struct { + // Name of channel. + Name string `json:"name" yaml:"name"` + // Versions to include. + Versions []semver.Version `json:"versions,omitempty" yaml:"versions,omitempty"` + // Bundles are bundle names to include. + // Set this field only if the named bundle has no semantic version metadata. + Bundles []string `json:"bundles,omitempty" yaml:"bundles,omitempty"` +} + +// LoadDiffIncludeConfig loads a (YAML or JSON) DiffIncludeConfig from r. +func LoadDiffIncludeConfig(r io.Reader) (c DiffIncludeConfig, err error) { + dec := yaml.NewYAMLOrJSONDecoder(r, 8) + if err := dec.Decode(&c); err != nil { + return DiffIncludeConfig{}, err + } + + if len(c.Packages) == 0 { + return c, fmt.Errorf("must specify at least one package in include config") + } + + var errs []error + for pkgI, pkg := range c.Packages { + if pkg.Name == "" { + errs = append(errs, fmt.Errorf("package at index %v requires a name", pkgI)) + continue + } + for chI, ch := range pkg.Channels { + if ch.Name == "" { + errs = append(errs, fmt.Errorf("package %s: channel at index %v requires a name", pkg.Name, chI)) + continue + } + } + } + return c, utilerrors.NewAggregate(errs) +} + +func convertIncludeConfigToIncluder(c DiffIncludeConfig) (includer declcfg.DiffIncluder) { + includer.Packages = make([]declcfg.DiffIncludePackage, len(c.Packages)) + for pkgI, cpkg := range c.Packages { + pkg := &includer.Packages[pkgI] + pkg.Name = cpkg.Name + pkg.AllChannels.Versions = cpkg.Versions + pkg.AllChannels.Bundles = cpkg.Bundles + + if len(cpkg.Channels) != 0 { + pkg.Channels = make([]declcfg.DiffIncludeChannel, len(cpkg.Channels)) + for chI, cch := range cpkg.Channels { + ch := &pkg.Channels[chI] + ch.Name = cch.Name + ch.Versions = cch.Versions + ch.Bundles = cch.Bundles + } + } + } + return includer +} diff --git a/staging/operator-registry/alpha/action/diff_test.go b/staging/operator-registry/alpha/action/diff_test.go index 4ab09caf2b..e00c81e22c 100644 --- a/staging/operator-registry/alpha/action/diff_test.go +++ b/staging/operator-registry/alpha/action/diff_test.go @@ -1,6 +1,7 @@ -package action_test +package action import ( + "bytes" "context" "embed" "errors" @@ -10,10 +11,10 @@ import ( "strings" "testing" + "github.com/blang/semver/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/operator-framework/operator-registry/alpha/action" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/pkg/containertools" "github.com/operator-framework/operator-registry/pkg/image" @@ -23,7 +24,7 @@ import ( func TestDiff(t *testing.T) { type spec struct { name string - diff action.Diff + diff Diff expectedCfg *declcfg.DeclarativeConfig assertion require.ErrorAssertionFunc } @@ -34,7 +35,7 @@ func TestDiff(t *testing.T) { specs := []spec{ { name: "Success/Latest", - diff: action.Diff{ + diff: Diff{ Registry: registry, OldRefs: []string{filepath.Join("testdata", "index-declcfgs", "old")}, NewRefs: []string{filepath.Join("testdata", "index-declcfgs", "latest")}, @@ -44,16 +45,102 @@ func TestDiff(t *testing.T) { }, { name: "Success/HeadsOnly", - diff: action.Diff{ + diff: Diff{ Registry: registry, NewRefs: []string{filepath.Join("testdata", "index-declcfgs", "latest")}, }, expectedCfg: loadDirFS(t, indicesDir, filepath.Join("testdata", "index-declcfgs", "exp-headsonly")), assertion: require.NoError, }, + { + name: "Success/IncludePackage", + diff: Diff{ + Registry: registry, + NewRefs: []string{filepath.Join("testdata", "index-declcfgs", "latest")}, + IncludeConfig: DiffIncludeConfig{ + Packages: []DiffIncludePackage{{Name: "baz"}}, + }, + IncludeAdditively: true, + }, + expectedCfg: loadDirFS(t, indicesDir, filepath.Join("testdata", "index-declcfgs", "exp-include-pkg")), + assertion: require.NoError, + }, + { + name: "Success/IncludeChannel", + diff: Diff{ + Registry: registry, + NewRefs: []string{filepath.Join("testdata", "index-declcfgs", "latest")}, + IncludeConfig: DiffIncludeConfig{ + Packages: []DiffIncludePackage{ + { + Name: "baz", + Channels: []DiffIncludeChannel{{Name: "stable"}}, + }, + }, + }, + IncludeAdditively: true, + }, + expectedCfg: loadDirFS(t, indicesDir, filepath.Join("testdata", "index-declcfgs", "exp-include-channel")), + assertion: require.NoError, + }, + { + name: "Success/IncludeVersion", + diff: Diff{ + Registry: registry, + NewRefs: []string{filepath.Join("testdata", "index-declcfgs", "latest")}, + IncludeConfig: DiffIncludeConfig{ + Packages: []DiffIncludePackage{ + { + Name: "baz", + Versions: []semver.Version{semver.MustParse("1.0.0")}, + }, + }, + }, + IncludeAdditively: true, + }, + expectedCfg: loadDirFS(t, indicesDir, filepath.Join("testdata", "index-declcfgs", "exp-include-channel")), + assertion: require.NoError, + }, + { + name: "Success/IncludeBundle", + diff: Diff{ + Registry: registry, + NewRefs: []string{filepath.Join("testdata", "index-declcfgs", "latest")}, + IncludeConfig: DiffIncludeConfig{ + Packages: []DiffIncludePackage{ + { + Name: "baz", + Bundles: []string{"baz.v1.0.0"}, + }, + }, + }, + IncludeAdditively: true, + }, + expectedCfg: loadDirFS(t, indicesDir, filepath.Join("testdata", "index-declcfgs", "exp-include-channel")), + assertion: require.NoError, + }, + { + name: "Success/IncludeSameVersionAndBundle", + diff: Diff{ + Registry: registry, + NewRefs: []string{filepath.Join("testdata", "index-declcfgs", "latest")}, + IncludeConfig: DiffIncludeConfig{ + Packages: []DiffIncludePackage{ + { + Name: "baz", + Versions: []semver.Version{semver.MustParse("1.0.0")}, + Bundles: []string{"baz.v1.0.0"}, + }, + }, + }, + IncludeAdditively: true, + }, + expectedCfg: loadDirFS(t, indicesDir, filepath.Join("testdata", "index-declcfgs", "exp-include-channel")), + assertion: require.NoError, + }, { name: "Fail/NewBundleImage", - diff: action.Diff{ + diff: Diff{ Registry: registry, NewRefs: []string{"test.registry/foo-operator/foo-bundle:v0.1.0"}, }, @@ -61,14 +148,14 @@ func TestDiff(t *testing.T) { if !assert.Error(t, err) { require.Fail(t, "expected an error") } - if !errors.Is(err, action.ErrNotAllowed) { - require.Fail(t, "err is not action.ErrNotAllowed", err) + if !errors.Is(err, ErrNotAllowed) { + require.Fail(t, "err is not ErrNotAllowed", err) } }, }, { name: "Fail/OldBundleImage", - diff: action.Diff{ + diff: Diff{ Registry: registry, OldRefs: []string{"test.registry/foo-operator/foo-bundle:v0.1.0"}, NewRefs: []string{filepath.Join("testdata", "index-declcfgs", "latest")}, @@ -77,8 +164,8 @@ func TestDiff(t *testing.T) { if !assert.Error(t, err) { require.Fail(t, "expected an error") } - if !errors.Is(err, action.ErrNotAllowed) { - require.Fail(t, "err is not action.ErrNotAllowed", err) + if !errors.Is(err, ErrNotAllowed) { + require.Fail(t, "err is not ErrNotAllowed", err) } }, }, @@ -93,6 +180,151 @@ func TestDiff(t *testing.T) { } } +func TestLoadDiffIncludeConfig(t *testing.T) { + type spec struct { + name string + input string + expectedCfg DiffIncludeConfig + expectedIncluder declcfg.DiffIncluder + assertion require.ErrorAssertionFunc + } + + specs := []spec{ + { + name: "Success/Basic", + input: ` +packages: +- name: foo +`, + expectedCfg: DiffIncludeConfig{ + Packages: []DiffIncludePackage{{Name: "foo"}}, + }, + expectedIncluder: declcfg.DiffIncluder{ + Packages: []declcfg.DiffIncludePackage{{Name: "foo"}}, + }, + assertion: require.NoError, + }, + { + name: "Success/MultiPackage", + input: ` +packages: +- name: foo + channels: + - name: stable + bundles: + - foo.v0.3.0 + versions: + - 0.1.0 + - 0.2.0 + versions: + - 1.0.0 +- name: bar + channels: + - name: stable + versions: + - 0.1.0 + versions: + - 1.0.0 + bundles: + - bar.v1.2.0 +`, + expectedCfg: DiffIncludeConfig{ + Packages: []DiffIncludePackage{ + { + Name: "foo", + Channels: []DiffIncludeChannel{ + { + Name: "stable", + Versions: []semver.Version{semver.MustParse("0.1.0"), semver.MustParse("0.2.0")}, + Bundles: []string{"foo.v0.3.0"}, + }, + }, + Versions: []semver.Version{semver.MustParse("1.0.0")}, + }, + { + Name: "bar", + Channels: []DiffIncludeChannel{ + {Name: "stable", Versions: []semver.Version{ + semver.MustParse("0.1.0"), + }}, + }, + Versions: []semver.Version{semver.MustParse("1.0.0")}, + Bundles: []string{"bar.v1.2.0"}, + }, + }, + }, + expectedIncluder: declcfg.DiffIncluder{ + Packages: []declcfg.DiffIncludePackage{ + { + Name: "foo", + Channels: []declcfg.DiffIncludeChannel{ + { + Name: "stable", + Versions: []semver.Version{semver.MustParse("0.1.0"), semver.MustParse("0.2.0")}, + Bundles: []string{"foo.v0.3.0"}, + }, + }, + AllChannels: declcfg.DiffIncludeChannel{ + Versions: []semver.Version{semver.MustParse("1.0.0")}, + }, + }, + { + Name: "bar", + Channels: []declcfg.DiffIncludeChannel{ + {Name: "stable", Versions: []semver.Version{ + semver.MustParse("0.1.0"), + }}, + }, + AllChannels: declcfg.DiffIncludeChannel{ + Versions: []semver.Version{semver.MustParse("1.0.0")}, + Bundles: []string{"bar.v1.2.0"}, + }, + }, + }, + }, + assertion: require.NoError, + }, + { + name: "Fail/Empty", + input: ``, + assertion: require.Error, + }, + { + name: "Fail/NoPackageName", + input: ` +packages: +- channels: + - name: stable + versions: + - 0.1.0 +`, + assertion: require.Error, + }, + { + name: "Fail/NoChannelName", + input: ` +packages: +- name: foo + channels: + - versions: + - 0.1.0 +`, + assertion: require.Error, + }, + } + + for _, s := range specs { + t.Run(s.name, func(t *testing.T) { + actualCfg, err := LoadDiffIncludeConfig(bytes.NewBufferString(s.input)) + s.assertion(t, err) + if err == nil { + require.Equal(t, s.expectedCfg, actualCfg) + require.Equal(t, s.expectedIncluder, convertIncludeConfigToIncluder(actualCfg)) + } + }) + } +} + var ( //go:embed testdata/foo-bundle-v0.1.0/manifests/* //go:embed testdata/foo-bundle-v0.1.0/metadata/* diff --git a/staging/operator-registry/alpha/action/migrate.go b/staging/operator-registry/alpha/action/migrate.go new file mode 100644 index 0000000000..8cca8b8974 --- /dev/null +++ b/staging/operator-registry/alpha/action/migrate.go @@ -0,0 +1,99 @@ +package action + +import ( + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/pkg/image" +) + +type Migrate struct { + CatalogRef string + OutputDir string + + WriteFunc WriteFunc + FileExt string + Registry image.Registry +} + +type WriteFunc func(config declcfg.DeclarativeConfig, w io.Writer) error + +func (m Migrate) Run(ctx context.Context) error { + entries, err := ioutil.ReadDir(m.OutputDir) + if err != nil && !os.IsNotExist(err) { + return err + } + if len(entries) > 0 { + return fmt.Errorf("output dir %q must be empty", m.OutputDir) + } + + r := Render{ + Refs: []string{m.CatalogRef}, + + // Only allow sqlite images and files to be migrated. Other types cannot + // always be migrated cleanly because they may contain file references. + // Rendered sqlite databases never contain file references. + AllowedRefMask: RefSqliteImage | RefSqliteFile, + + skipSqliteDeprecationLog: true, + } + if m.Registry != nil { + r.Registry = m.Registry + } + + cfg, err := r.Run(ctx) + if err != nil { + return fmt.Errorf("render catalog image: %w", err) + } + + return writeToFS(*cfg, m.OutputDir, m.WriteFunc, m.FileExt) +} + +func writeToFS(cfg declcfg.DeclarativeConfig, rootDir string, writeFunc WriteFunc, fileExt string) error { + channelsByPackage := map[string][]declcfg.Channel{} + for _, c := range cfg.Channels { + channelsByPackage[c.Package] = append(channelsByPackage[c.Package], c) + } + bundlesByPackage := map[string][]declcfg.Bundle{} + for _, b := range cfg.Bundles { + bundlesByPackage[b.Package] = append(bundlesByPackage[b.Package], b) + } + + if err := os.MkdirAll(rootDir, 0777); err != nil { + return err + } + + for _, p := range cfg.Packages { + fcfg := declcfg.DeclarativeConfig{ + Packages: []declcfg.Package{p}, + Channels: channelsByPackage[p.Name], + Bundles: bundlesByPackage[p.Name], + } + pkgDir := filepath.Join(rootDir, p.Name) + if err := os.MkdirAll(pkgDir, 0777); err != nil { + return err + } + filename := filepath.Join(pkgDir, fmt.Sprintf("catalog%s", fileExt)) + if err := writeFile(fcfg, filename, writeFunc); err != nil { + return err + } + } + return nil +} + +func writeFile(cfg declcfg.DeclarativeConfig, filename string, writeFunc WriteFunc) error { + buf := &bytes.Buffer{} + if err := writeFunc(cfg, buf); err != nil { + return fmt.Errorf("write to buffer for %q: %v", filename, err) + } + if err := ioutil.WriteFile(filename, buf.Bytes(), 0666); err != nil { + return fmt.Errorf("write file %q: %v", filename, err) + } + return nil +} diff --git a/staging/operator-registry/alpha/action/migrate_test.go b/staging/operator-registry/alpha/action/migrate_test.go new file mode 100644 index 0000000000..1296cf80d0 --- /dev/null +++ b/staging/operator-registry/alpha/action/migrate_test.go @@ -0,0 +1,341 @@ +package action_test + +import ( + "context" + "fmt" + "io/fs" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/operator-framework/operator-registry/alpha/action" + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/pkg/containertools" + "github.com/operator-framework/operator-registry/pkg/image" + "github.com/operator-framework/operator-registry/pkg/lib/bundle" +) + +func TestMigrate(t *testing.T) { + type spec struct { + name string + migrate action.Migrate + expectedFiles map[string]string + expectErr error + } + + sqliteBundles := map[image.Reference]string{ + image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.1.0"): "testdata/foo-bundle-v0.1.0", + image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.2.0"): "testdata/foo-bundle-v0.2.0", + image.SimpleReference("test.registry/bar-operator/bar-bundle:v0.1.0"): "testdata/bar-bundle-v0.1.0", + image.SimpleReference("test.registry/bar-operator/bar-bundle:v0.2.0"): "testdata/bar-bundle-v0.2.0", + } + + tmpDir := t.TempDir() + dbFile := filepath.Join(tmpDir, "index.db") + err := generateSqliteFile(dbFile, sqliteBundles) + require.NoError(t, err) + + reg, err := newMigrateRegistry(sqliteBundles) + require.NoError(t, err) + + specs := []spec{ + { + name: "SqliteImage/Success", + migrate: action.Migrate{ + CatalogRef: "test.registry/migrate/catalog:sqlite", + OutputDir: filepath.Join(tmpDir, "sqlite-image"), + WriteFunc: declcfg.WriteYAML, + FileExt: ".yaml", + Registry: reg, + }, + expectedFiles: map[string]string{ + "foo/catalog.yaml": migrateFooCatalog(), + "bar/catalog.yaml": migrateBarCatalog(), + }, + }, + { + name: "SqliteFile/Success", + migrate: action.Migrate{ + CatalogRef: dbFile, + OutputDir: filepath.Join(tmpDir, "sqlite-file"), + WriteFunc: declcfg.WriteYAML, + FileExt: ".yaml", + Registry: reg, + }, + expectedFiles: map[string]string{ + "foo/catalog.yaml": migrateFooCatalog(), + "bar/catalog.yaml": migrateBarCatalog(), + }, + }, + { + name: "DeclcfgImage/Failure", + migrate: action.Migrate{ + CatalogRef: "test.registry/foo-operator/foo-index-declcfg:v0.2.0", + OutputDir: filepath.Join(tmpDir, "declcfg-image"), + WriteFunc: declcfg.WriteYAML, + FileExt: ".yaml", + Registry: reg, + }, + expectErr: action.ErrNotAllowed, + }, + { + name: "DeclcfgDir/Failure", + migrate: action.Migrate{ + CatalogRef: "testdata/foo-index-v0.2.0-declcfg", + OutputDir: filepath.Join(tmpDir, "declcfg-dir"), + WriteFunc: declcfg.WriteYAML, + FileExt: ".yaml", + Registry: reg, + }, + expectErr: action.ErrNotAllowed, + }, + { + name: "BundleImage/Failure", + migrate: action.Migrate{ + CatalogRef: "test.registry/foo-operator/foo-bundle:v0.1.0", + OutputDir: filepath.Join(tmpDir, "bundle-image"), + WriteFunc: declcfg.WriteYAML, + FileExt: ".yaml", + Registry: reg, + }, + expectErr: action.ErrNotAllowed, + }, + } + for _, s := range specs { + t.Run(s.name, func(t *testing.T) { + err := s.migrate.Run(context.Background()) + require.ErrorIs(t, err, s.expectErr) + for file, expectedData := range s.expectedFiles { + path := filepath.Join(s.migrate.OutputDir, file) + actualData, err := os.ReadFile(path) + require.NoError(t, err) + fmt.Println(string(actualData)) + require.Equal(t, expectedData, string(actualData)) + } + }) + } +} + +func newMigrateRegistry(imageMap map[image.Reference]string) (image.Registry, error) { + subSqliteImage, err := generateSqliteFS(imageMap) + if err != nil { + return nil, err + } + + subDeclcfgImage, err := fs.Sub(declcfgImage, "testdata/foo-index-v0.2.0-declcfg") + if err != nil { + return nil, err + } + + subBundleImageV1, err := fs.Sub(bundleImageV1, "testdata/foo-bundle-v0.1.0") + if err != nil { + return nil, err + } + + reg := &image.MockRegistry{RemoteImages: map[image.Reference]*image.MockImage{ + image.SimpleReference("test.registry/migrate/catalog:sqlite"): { + Labels: map[string]string{ + containertools.DbLocationLabel: "/database/index.db", + }, + FS: subSqliteImage, + }, + image.SimpleReference("test.registry/foo-operator/foo-index-declcfg:v0.2.0"): { + Labels: map[string]string{ + "operators.operatorframework.io.index.configs.v1": "/foo", + }, + FS: subDeclcfgImage, + }, + image.SimpleReference("test.registry/foo-operator/foo-bundle:v0.1.0"): { + Labels: map[string]string{ + bundle.PackageLabel: "foo", + }, + FS: subBundleImageV1, + }, + }} + + return reg, nil +} + +func migrateFooCatalog() string { + return `--- +defaultChannel: beta +name: foo +schema: olm.package +--- +entries: +- name: foo.v0.1.0 + skipRange: <0.1.0 +- name: foo.v0.2.0 + replaces: foo.v0.1.0 + skipRange: <0.2.0 + skips: + - foo.v0.1.1 + - foo.v0.1.2 +name: beta +package: foo +schema: olm.channel +--- +entries: +- name: foo.v0.1.0 + skipRange: <0.1.0 +- name: foo.v0.2.0 + replaces: foo.v0.1.0 + skipRange: <0.2.0 + skips: + - foo.v0.1.1 + - foo.v0.1.2 +name: stable +package: foo +schema: olm.channel +--- +image: test.registry/foo-operator/foo-bundle:v0.1.0 +name: foo.v0.1.0 +package: foo +properties: +- type: olm.gvk + value: + group: test.foo + kind: Foo + version: v1 +- type: olm.gvk.required + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: foo + version: 0.1.0 +- type: olm.package.required + value: + packageName: bar + versionRange: <0.1.0 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMS4wIn0sIm5hbWUiOiJmb28udjAuMS4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmZvbyIsImtpbmQiOiJGb28iLCJuYW1lIjoiZm9vcy50ZXN0LmZvbyIsInZlcnNpb24iOiJ2MSJ9XX0sImRpc3BsYXlOYW1lIjoiRm9vIE9wZXJhdG9yIiwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Zvby1vcGVyYXRvci9mb286djAuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMC4xLjAifX0= +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvb3MudGVzdC5mb28ifSwic3BlYyI6eyJncm91cCI6InRlc3QuZm9vIiwibmFtZXMiOnsia2luZCI6IkZvbyIsInBsdXJhbCI6ImZvb3MifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +relatedImages: +- image: test.registry/foo-operator/foo-bundle:v0.1.0 + name: "" +- image: test.registry/foo-operator/foo:v0.1.0 + name: operator +schema: olm.bundle +--- +image: test.registry/foo-operator/foo-bundle:v0.2.0 +name: foo.v0.2.0 +package: foo +properties: +- type: olm.gvk + value: + group: test.foo + kind: Foo + version: v1 +- type: olm.gvk.required + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: foo + version: 0.2.0 +- type: olm.package.required + value: + packageName: bar + versionRange: <0.1.0 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMi4wIn0sIm5hbWUiOiJmb28udjAuMi4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmZvbyIsImtpbmQiOiJGb28iLCJuYW1lIjoiZm9vcy50ZXN0LmZvbyIsInZlcnNpb24iOiJ2MSJ9XX0sImRpc3BsYXlOYW1lIjoiRm9vIE9wZXJhdG9yIiwiaW5zdGFsbCI6eyJzcGVjIjp7ImRlcGxveW1lbnRzIjpbeyJuYW1lIjoiZm9vLW9wZXJhdG9yIiwic3BlYyI6eyJ0ZW1wbGF0ZSI6eyJzcGVjIjp7ImNvbnRhaW5lcnMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vOnYwLjIuMCJ9XSwiaW5pdENvbnRhaW5lcnMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vLWluaXQ6djAuMi4wIn1dfX19fSx7Im5hbWUiOiJmb28tb3BlcmF0b3ItMiIsInNwZWMiOnsidGVtcGxhdGUiOnsic3BlYyI6eyJjb250YWluZXJzIjpbeyJpbWFnZSI6InRlc3QucmVnaXN0cnkvZm9vLW9wZXJhdG9yL2Zvby0yOnYwLjIuMCJ9XSwiaW5pdENvbnRhaW5lcnMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vLWluaXQtMjp2MC4yLjAifV19fX19XX0sInN0cmF0ZWd5IjoiZGVwbG95bWVudCJ9LCJyZWxhdGVkSW1hZ2VzIjpbeyJpbWFnZSI6InRlc3QucmVnaXN0cnkvZm9vLW9wZXJhdG9yL2Zvbzp2MC4yLjAiLCJuYW1lIjoib3BlcmF0b3IifSx7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9mb28tb3BlcmF0b3IvZm9vLW90aGVyOnYwLjIuMCIsIm5hbWUiOiJvdGhlciJ9XSwicmVwbGFjZXMiOiJmb28udjAuMS4wIiwic2tpcHMiOlsiZm9vLnYwLjEuMSIsImZvby52MC4xLjIiXSwidmVyc2lvbiI6IjAuMi4wIn19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvb3MudGVzdC5mb28ifSwic3BlYyI6eyJncm91cCI6InRlc3QuZm9vIiwibmFtZXMiOnsia2luZCI6IkZvbyIsInBsdXJhbCI6ImZvb3MifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +relatedImages: +- image: test.registry/foo-operator/foo-2:v0.2.0 + name: "" +- image: test.registry/foo-operator/foo-bundle:v0.2.0 + name: "" +- image: test.registry/foo-operator/foo-init-2:v0.2.0 + name: "" +- image: test.registry/foo-operator/foo-init:v0.2.0 + name: "" +- image: test.registry/foo-operator/foo-other:v0.2.0 + name: other +- image: test.registry/foo-operator/foo:v0.2.0 + name: operator +schema: olm.bundle +` +} + +func migrateBarCatalog() string { + return `--- +defaultChannel: alpha +name: bar +schema: olm.package +--- +entries: +- name: bar.v0.1.0 +- name: bar.v0.2.0 + skipRange: <0.2.0 + skips: + - bar.v0.1.0 +name: alpha +package: bar +schema: olm.channel +--- +image: test.registry/bar-operator/bar-bundle:v0.1.0 +name: bar.v0.1.0 +package: bar +properties: +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 0.1.0 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MC4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djAuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMC4xLjAifX0= +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 +relatedImages: +- image: test.registry/bar-operator/bar-bundle:v0.1.0 + name: "" +- image: test.registry/bar-operator/bar:v0.1.0 + name: operator +schema: olm.bundle +--- +image: test.registry/bar-operator/bar-bundle:v0.2.0 +name: bar.v0.2.0 +package: bar +properties: +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 0.2.0 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMi4wIn0sIm5hbWUiOiJiYXIudjAuMi4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJhciIsImtpbmQiOiJCYXIiLCJuYW1lIjoiYmFycy50ZXN0LmJhciIsInZlcnNpb24iOiJ2MWFscGhhMSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXItb3BlcmF0b3IvYmFyOnYwLjIuMCIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmFyLnYwLjEuMCJdLCJ2ZXJzaW9uIjoiMC4yLjAifX0= +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 +relatedImages: +- image: test.registry/bar-operator/bar-bundle:v0.2.0 + name: "" +- image: test.registry/bar-operator/bar:v0.2.0 + name: operator +schema: olm.bundle +` +} diff --git a/staging/operator-registry/alpha/action/render.go b/staging/operator-registry/alpha/action/render.go index 389c8ff44c..3f38390a0e 100644 --- a/staging/operator-registry/alpha/action/render.go +++ b/staging/operator-registry/alpha/action/render.go @@ -52,6 +52,8 @@ type Render struct { Refs []string Registry image.Registry AllowedRefMask RefType + + skipSqliteDeprecationLog bool } func nullLogger() *logrus.Entry { @@ -61,6 +63,10 @@ func nullLogger() *logrus.Entry { } func (r Render) Run(ctx context.Context) (*declcfg.DeclarativeConfig, error) { + if r.skipSqliteDeprecationLog { + // exhaust once with a no-op function. + logDeprecationMessage.Do(func() {}) + } if r.Registry == nil { reg, err := r.createRegistry() if err != nil { @@ -290,7 +296,7 @@ func populateDBRelatedImages(ctx context.Context, cfg *declcfg.DeclarativeConfig } func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error) { - bundleProperties, err := registry.PropertiesFromBundle(bundle) + objs, props, err := registry.ObjectsAndPropertiesFromBundle(bundle) if err != nil { return nil, fmt.Errorf("get properties for bundle %q: %v", bundle.Name, err) } @@ -298,14 +304,25 @@ func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error if err != nil { return nil, fmt.Errorf("get related images for bundle %q: %v", bundle.Name, err) } + var csvJson []byte + for _, obj := range bundle.Objects { + if obj.GetKind() == "ClusterServiceVersion" { + csvJson, err = json.Marshal(obj) + if err != nil { + return nil, fmt.Errorf("marshal CSV JSON for bundle %q: %v", bundle.Name, err) + } + } + } dBundle := declcfg.Bundle{ Schema: "olm.bundle", Name: bundle.Name, Package: bundle.Package, Image: bundle.BundleImage, - Properties: bundleProperties, + Properties: props, RelatedImages: relatedImages, + Objects: objs, + CsvJSON: string(csvJson), } return &declcfg.DeclarativeConfig{Bundles: []declcfg.Bundle{dBundle}}, nil @@ -322,14 +339,12 @@ func getRelatedImages(b *registry.Bundle) ([]declcfg.RelatedImage, error) { return nil, err } - rawValue, ok := objmap["relatedImages"] - if !ok || rawValue == nil { - return nil, err - } - var relatedImages []declcfg.RelatedImage - if err = json.Unmarshal(*rawValue, &relatedImages); err != nil { - return nil, err + rawValue, ok := objmap["relatedImages"] + if ok && rawValue != nil { + if err = json.Unmarshal(*rawValue, &relatedImages); err != nil { + return nil, err + } } // Keep track of the images we've already found, so that we don't add diff --git a/staging/operator-registry/alpha/action/render_test.go b/staging/operator-registry/alpha/action/render_test.go index 40cef52504..b355233ece 100644 --- a/staging/operator-registry/alpha/action/render_test.go +++ b/staging/operator-registry/alpha/action/render_test.go @@ -42,6 +42,10 @@ func TestRender(t *testing.T) { require.NoError(t, err) foov2crd, err := bundleImageV2.ReadFile("testdata/foo-bundle-v0.2.0/manifests/foos.test.foo.crd.yaml") require.NoError(t, err) + foov2csvNoRelatedImages, err := bundleImageV2NoCSVRelatedImages.ReadFile("testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foo.v0.2.0.csv.yaml") + require.NoError(t, err) + foov2crdNoRelatedImages, err := bundleImageV2NoCSVRelatedImages.ReadFile("testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foos.test.foo.crd.yaml") + require.NoError(t, err) foov1csv, err = yaml.ToJSON(foov1csv) require.NoError(t, err) @@ -51,6 +55,10 @@ func TestRender(t *testing.T) { require.NoError(t, err) foov2crd, err = yaml.ToJSON(foov2crd) require.NoError(t, err) + foov2csvNoRelatedImages, err = yaml.ToJSON(foov2csvNoRelatedImages) + require.NoError(t, err) + foov2crdNoRelatedImages, err = yaml.ToJSON(foov2crdNoRelatedImages) + require.NoError(t, err) dir := t.TempDir() dbFile := filepath.Join(dir, "index.db") @@ -445,7 +453,11 @@ func TestRender(t *testing.T) { property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), property.MustBuildPackage("foo", "0.2.0"), property.MustBuildPackageRequired("bar", "<0.1.0"), + property.MustBuildBundleObjectData(foov2csv), + property.MustBuildBundleObjectData(foov2crd), }, + Objects: []string{string(foov2csv), string(foov2crd)}, + CsvJSON: string(foov2csv), RelatedImages: []declcfg.RelatedImage{ { Image: "test.registry/foo-operator/foo-2:v0.2.0", @@ -473,6 +485,51 @@ func TestRender(t *testing.T) { }, assertion: require.NoError, }, + { + name: "Success/BundleImageWithNoCSVRelatedImages", + render: action.Render{ + Refs: []string{"test.registry/foo-operator/foo-bundle-no-csv-related-images:v0.2.0"}, + Registry: reg, + }, + expectCfg: &declcfg.DeclarativeConfig{ + Bundles: []declcfg.Bundle{ + { + Schema: "olm.bundle", + Name: "foo.v0.2.0", + Package: "foo", + Image: "test.registry/foo-operator/foo-bundle-no-csv-related-images:v0.2.0", + Properties: []property.Property{ + property.MustBuildGVK("test.foo", "v1", "Foo"), + property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), + property.MustBuildPackage("foo", "0.2.0"), + property.MustBuildPackageRequired("bar", "<0.1.0"), + property.MustBuildBundleObjectData(foov2csvNoRelatedImages), + property.MustBuildBundleObjectData(foov2crdNoRelatedImages), + }, + Objects: []string{string(foov2csvNoRelatedImages), string(foov2crdNoRelatedImages)}, + CsvJSON: string(foov2csvNoRelatedImages), + RelatedImages: []declcfg.RelatedImage{ + { + Image: "test.registry/foo-operator/foo-2:v0.2.0", + }, + { + Image: "test.registry/foo-operator/foo-bundle-no-csv-related-images:v0.2.0", + }, + { + Image: "test.registry/foo-operator/foo-init-2:v0.2.0", + }, + { + Image: "test.registry/foo-operator/foo-init:v0.2.0", + }, + { + Image: "test.registry/foo-operator/foo:v0.2.0", + }, + }, + }, + }, + }, + assertion: require.NoError, + }, } for _, s := range specs { @@ -687,6 +744,10 @@ var bundleImageV1 embed.FS //go:embed testdata/foo-bundle-v0.2.0/metadata/* var bundleImageV2 embed.FS +//go:embed testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/* +//go:embed testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/* +var bundleImageV2NoCSVRelatedImages embed.FS + //go:embed testdata/foo-index-v0.2.0-declcfg/foo/* var declcfgImage embed.FS @@ -704,7 +765,7 @@ func newRegistry() (image.Registry, error) { if err != nil { return nil, err } - subBundleImageV1, err := fs.Sub(bundleImageV2, "testdata/foo-bundle-v0.1.0") + subBundleImageV1, err := fs.Sub(bundleImageV1, "testdata/foo-bundle-v0.1.0") if err != nil { return nil, err } @@ -712,6 +773,10 @@ func newRegistry() (image.Registry, error) { if err != nil { return nil, err } + subBundleImageV2NoCSVRelatedImages, err := fs.Sub(bundleImageV2NoCSVRelatedImages, "testdata/foo-bundle-v0.2.0-no-csv-related-images") + if err != nil { + return nil, err + } return &image.MockRegistry{ RemoteImages: map[image.Reference]*image.MockImage{ image.SimpleReference("test.registry/foo-operator/foo-index-sqlite:v0.2.0"): { @@ -738,6 +803,12 @@ func newRegistry() (image.Registry, error) { }, FS: subBundleImageV2, }, + image.SimpleReference("test.registry/foo-operator/foo-bundle-no-csv-related-images:v0.2.0"): { + Labels: map[string]string{ + bundle.PackageLabel: "foo", + }, + FS: subBundleImageV2NoCSVRelatedImages, + }, }, }, nil } @@ -792,7 +863,7 @@ func generateSqliteFile(path string, imageMap map[image.Reference]string) error return err } - populator := registry.NewDirectoryPopulator(loader, graphLoader, dbQuerier, imageMap, nil, false) + populator := registry.NewDirectoryPopulator(loader, graphLoader, dbQuerier, imageMap, nil) if err := populator.Populate(registry.ReplacesMode); err != nil { return err } diff --git a/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/bundle.Dockerfile b/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/bundle.Dockerfile new file mode 100644 index 0000000000..df0043928a --- /dev/null +++ b/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/bundle.Dockerfile @@ -0,0 +1,12 @@ +FROM scratch + +# Core bundle labels. +LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 +LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ +LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ +LABEL operators.operatorframework.io.bundle.package.v1=foo +LABEL operators.operatorframework.io.bundle.channels.v1=beta + +# Copy files to locations specified by labels. +COPY manifests /manifests/ +COPY metadata /metadata/ diff --git a/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foo.v0.2.0.csv.yaml b/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foo.v0.2.0.csv.yaml new file mode 100644 index 0000000000..82aeecdaf0 --- /dev/null +++ b/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foo.v0.2.0.csv.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: foo.v0.2.0 + annotations: + olm.skipRange: <0.2.0 +spec: + displayName: "Foo Operator" + customresourcedefinitions: + owned: + - group: test.foo + version: v1 + kind: Foo + name: foos.test.foo + version: 0.2.0 + replaces: foo.v0.1.0 + skips: + - foo.v0.1.1 + - foo.v0.1.2 + install: + strategy: deployment + spec: + deployments: + - name: foo-operator + spec: + template: + spec: + initContainers: + - image: test.registry/foo-operator/foo-init:v0.2.0 + containers: + - image: test.registry/foo-operator/foo:v0.2.0 + - name: foo-operator-2 + spec: + template: + spec: + initContainers: + - image: test.registry/foo-operator/foo-init-2:v0.2.0 + containers: + - image: test.registry/foo-operator/foo-2:v0.2.0 diff --git a/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foos.test.foo.crd.yaml b/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foos.test.foo.crd.yaml new file mode 100644 index 0000000000..39b762b505 --- /dev/null +++ b/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foos.test.foo.crd.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: foos.test.foo +spec: + group: test.foo + names: + kind: Foo + plural: foos + versions: + - name: v1 diff --git a/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/annotations.yaml b/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/annotations.yaml new file mode 100644 index 0000000000..dc4cc05f68 --- /dev/null +++ b/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/annotations.yaml @@ -0,0 +1,4 @@ +annotations: + operators.operatorframework.io.bundle.package.v1: foo + operators.operatorframework.io.bundle.channels.v1: beta,stable + operators.operatorframework.io.bundle.channel.default.v1: beta diff --git a/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/dependencies.yaml b/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/dependencies.yaml new file mode 100644 index 0000000000..e5d50aefd0 --- /dev/null +++ b/staging/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/dependencies.yaml @@ -0,0 +1,11 @@ +--- +dependencies: + - type: olm.package + value: + packageName: bar + version: <0.1.0 + - type: olm.gvk + value: + group: "test.bar" + version: "v1alpha1" + kind: "Bar" diff --git a/staging/operator-registry/alpha/action/testdata/index-declcfgs/exp-headsonly/index.yaml b/staging/operator-registry/alpha/action/testdata/index-declcfgs/exp-headsonly/index.yaml index 6aef07f88e..4b8fc10458 100644 --- a/staging/operator-registry/alpha/action/testdata/index-declcfgs/exp-headsonly/index.yaml +++ b/staging/operator-registry/alpha/action/testdata/index-declcfgs/exp-headsonly/index.yaml @@ -10,6 +10,7 @@ entries: - name: bar.v0.1.0 - name: bar.v0.2.0 replaces: bar.v0.1.0 + skipRange: <0.2.0 skips: - bar.v0.1.0 - name: bar.v1.0.0 diff --git a/staging/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-channel/index.yaml b/staging/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-channel/index.yaml new file mode 100644 index 0000000000..f49e7ae202 --- /dev/null +++ b/staging/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-channel/index.yaml @@ -0,0 +1,248 @@ +--- +defaultChannel: stable +name: bar +schema: olm.package +--- +name: alpha +package: bar +schema: olm.channel +entries: + - name: bar.v0.1.0 + - name: bar.v0.2.0 + replaces: bar.v0.1.0 + skipRange: <0.2.0 + skips: + - bar.v0.1.0 + - name: bar.v1.0.0 + replaces: bar.v0.2.0 +--- +name: stable +package: bar +schema: olm.channel +entries: + - name: bar.v1.0.0 +--- +# Added because foo.v0.3.1 depends on bar <0.2.0 +image: test.registry/bar-operator/bar-bundle:v0.1.0 +name: bar.v0.1.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MC4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djAuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMC4xLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 0.1.0 +relatedImages: +- image: test.registry/bar-operator/bar:v0.1.0 + name: operator +schema: olm.bundle +--- +# Added because foo.v0.3.1 depends on Bar, test.bar/v1alpha1. +# Note that dependency selection cannot decide between bar 0.1.0 +# and 0.2.0, as this is resolver territory. +image: test.registry/bar-operator/bar-bundle:v0.2.0 +name: bar.v0.2.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMi4wIn0sIm5hbWUiOiJiYXIudjAuMi4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJhciIsImtpbmQiOiJCYXIiLCJuYW1lIjoiYmFycy50ZXN0LmJhciIsInZlcnNpb24iOiJ2MWFscGhhMSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXItb3BlcmF0b3IvYmFyOnYwLjIuMCIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmFyLnYwLjEuMCJdLCJ2ZXJzaW9uIjoiMC4yLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 0.2.0 +relatedImages: +- image: test.registry/bar-operator/bar:v0.2.0 + name: operator +schema: olm.bundle +--- +image: test.registry/bar-operator/bar-bundle:v1.0.0 +name: bar.v1.0.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSIsInNlcnZlZCI6dHJ1ZSwic3RvcmFnZSI6ZmFsc2V9LHsibmFtZSI6InYxIiwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfV19fQ== +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MS4wLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn0seyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djEuMC4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImJhci52MC4yLjAiLCJ2ZXJzaW9uIjoiMS4wLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1 +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 1.0.0 +relatedImages: +- image: test.registry/bar-operator/bar:v1.0.0 + name: operator +schema: olm.bundle +--- +defaultChannel: stable +name: baz +schema: olm.package +--- +schema: olm.channel +package: baz +name: stable +entries: +- name: baz.v1.0.0 + skipRange: <1.0.0 +- name: baz.v1.0.1 + replaces: baz.v1.0.0 + skipRange: <1.0.0 + skips: + - baz.v1.0.0 +- name: baz.v1.1.0 + replaces: baz.v1.0.0 + skips: + - baz.v1.0.1 +--- +image: test.registry/baz-operator/baz-bundle:v1.0.0 +name: baz.v1.0.0 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhei52MS4wLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmF6Iiwia2luZCI6IkJheiIsIm5hbWUiOiJiYXpzLnRlc3QuYmF6IiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhei1vcGVyYXRvci9iYXo6djEuMC4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMS4wLjAifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.0.0 +relatedImages: +- image: test.registry/baz-operator/baz:v1.0.0 + name: operator +schema: olm.bundle +--- +image: test.registry/baz-operator/baz-bundle:v1.0.1 +name: baz.v1.0.1 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzEuMC4xIn0sIm5hbWUiOiJiYXoudjEuMC4xIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJheiIsImtpbmQiOiJCYXoiLCJuYW1lIjoiYmF6cy50ZXN0LmJheiIsInZlcnNpb24iOiJ2MSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXotb3BlcmF0b3IvYmF6OnYxLjAuMSIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmF6LnYxLjAuMCJdLCJ2ZXJzaW9uIjoiMS4wLjEifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.0.1 +relatedImages: +- image: test.registry/baz-operator/baz:v1.0.1 + name: operator +schema: olm.bundle +--- +image: test.registry/baz-operator/baz-bundle:v1.1.0 +name: baz.v1.1.0 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhei52MS4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmF6Iiwia2luZCI6IkJheiIsIm5hbWUiOiJiYXpzLnRlc3QuYmF6IiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhei1vcGVyYXRvci9iYXo6djEuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImJhei52MS4wLjAiLCJ2ZXJzaW9uIjoiMS4xLjAifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.1.0 +relatedImages: +- image: test.registry/baz-operator/baz:v1.1.0 + name: operator +schema: olm.bundle +--- +defaultChannel: beta +name: foo +schema: olm.package +--- +name: beta +package: foo +schema: olm.channel +entries: + - name: foo.v0.3.1 + replaces: foo.v0.2.0 + skips: + - foo.v0.3.0 +--- +image: test.registry/foo-operator/foo-bundle:v0.3.1 +name: foo.v0.3.1 +package: foo +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvb3MudGVzdC5mb28ifSwic3BlYyI6eyJncm91cCI6InRlc3QuZm9vIiwibmFtZXMiOnsia2luZCI6IkZvbyIsInBsdXJhbCI6ImZvb3MifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSIsInNlcnZlZCI6dHJ1ZSwic3RvcmFnZSI6ZmFsc2V9LHsibmFtZSI6InYyIiwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfV19fQ== +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvby52MC4zLjEifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuZm9vIiwia2luZCI6IkZvbyIsIm5hbWUiOiJmb29zLnRlc3QuZm9vIiwidmVyc2lvbiI6InYxIn0seyJncm91cCI6InRlc3QuZm9vIiwia2luZCI6IkZvbyIsIm5hbWUiOiJmb29zLnRlc3QuZm9vIiwidmVyc2lvbiI6InYyIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Zvby1vcGVyYXRvci9mb286djAuMy4xIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImZvby52MC4yLjAiLCJza2lwcyI6WyJmb28udjAuMy4wIl0sInZlcnNpb24iOiIwLjMuMSJ9fQ== +- type: olm.gvk + value: + group: test.foo + kind: Foo + version: v1 +- type: olm.gvk + value: + group: test.foo + kind: Foo + version: v2 +- type: olm.gvk.required + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: foo + version: 0.3.1 +- type: olm.package.required + value: + packageName: bar + packageName: bar + versionRange: <0.2.0 +relatedImages: +- image: test.registry/foo-operator/foo:v0.3.1 + name: operator +schema: olm.bundle diff --git a/staging/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-pkg/index.yaml b/staging/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-pkg/index.yaml new file mode 100644 index 0000000000..f49e7ae202 --- /dev/null +++ b/staging/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-pkg/index.yaml @@ -0,0 +1,248 @@ +--- +defaultChannel: stable +name: bar +schema: olm.package +--- +name: alpha +package: bar +schema: olm.channel +entries: + - name: bar.v0.1.0 + - name: bar.v0.2.0 + replaces: bar.v0.1.0 + skipRange: <0.2.0 + skips: + - bar.v0.1.0 + - name: bar.v1.0.0 + replaces: bar.v0.2.0 +--- +name: stable +package: bar +schema: olm.channel +entries: + - name: bar.v1.0.0 +--- +# Added because foo.v0.3.1 depends on bar <0.2.0 +image: test.registry/bar-operator/bar-bundle:v0.1.0 +name: bar.v0.1.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MC4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djAuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMC4xLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 0.1.0 +relatedImages: +- image: test.registry/bar-operator/bar:v0.1.0 + name: operator +schema: olm.bundle +--- +# Added because foo.v0.3.1 depends on Bar, test.bar/v1alpha1. +# Note that dependency selection cannot decide between bar 0.1.0 +# and 0.2.0, as this is resolver territory. +image: test.registry/bar-operator/bar-bundle:v0.2.0 +name: bar.v0.2.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMi4wIn0sIm5hbWUiOiJiYXIudjAuMi4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJhciIsImtpbmQiOiJCYXIiLCJuYW1lIjoiYmFycy50ZXN0LmJhciIsInZlcnNpb24iOiJ2MWFscGhhMSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXItb3BlcmF0b3IvYmFyOnYwLjIuMCIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmFyLnYwLjEuMCJdLCJ2ZXJzaW9uIjoiMC4yLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 0.2.0 +relatedImages: +- image: test.registry/bar-operator/bar:v0.2.0 + name: operator +schema: olm.bundle +--- +image: test.registry/bar-operator/bar-bundle:v1.0.0 +name: bar.v1.0.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSIsInNlcnZlZCI6dHJ1ZSwic3RvcmFnZSI6ZmFsc2V9LHsibmFtZSI6InYxIiwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfV19fQ== +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MS4wLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn0seyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djEuMC4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImJhci52MC4yLjAiLCJ2ZXJzaW9uIjoiMS4wLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1 +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 1.0.0 +relatedImages: +- image: test.registry/bar-operator/bar:v1.0.0 + name: operator +schema: olm.bundle +--- +defaultChannel: stable +name: baz +schema: olm.package +--- +schema: olm.channel +package: baz +name: stable +entries: +- name: baz.v1.0.0 + skipRange: <1.0.0 +- name: baz.v1.0.1 + replaces: baz.v1.0.0 + skipRange: <1.0.0 + skips: + - baz.v1.0.0 +- name: baz.v1.1.0 + replaces: baz.v1.0.0 + skips: + - baz.v1.0.1 +--- +image: test.registry/baz-operator/baz-bundle:v1.0.0 +name: baz.v1.0.0 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhei52MS4wLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmF6Iiwia2luZCI6IkJheiIsIm5hbWUiOiJiYXpzLnRlc3QuYmF6IiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhei1vcGVyYXRvci9iYXo6djEuMC4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMS4wLjAifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.0.0 +relatedImages: +- image: test.registry/baz-operator/baz:v1.0.0 + name: operator +schema: olm.bundle +--- +image: test.registry/baz-operator/baz-bundle:v1.0.1 +name: baz.v1.0.1 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzEuMC4xIn0sIm5hbWUiOiJiYXoudjEuMC4xIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJheiIsImtpbmQiOiJCYXoiLCJuYW1lIjoiYmF6cy50ZXN0LmJheiIsInZlcnNpb24iOiJ2MSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXotb3BlcmF0b3IvYmF6OnYxLjAuMSIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmF6LnYxLjAuMCJdLCJ2ZXJzaW9uIjoiMS4wLjEifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.0.1 +relatedImages: +- image: test.registry/baz-operator/baz:v1.0.1 + name: operator +schema: olm.bundle +--- +image: test.registry/baz-operator/baz-bundle:v1.1.0 +name: baz.v1.1.0 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhei52MS4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmF6Iiwia2luZCI6IkJheiIsIm5hbWUiOiJiYXpzLnRlc3QuYmF6IiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhei1vcGVyYXRvci9iYXo6djEuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImJhei52MS4wLjAiLCJ2ZXJzaW9uIjoiMS4xLjAifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.1.0 +relatedImages: +- image: test.registry/baz-operator/baz:v1.1.0 + name: operator +schema: olm.bundle +--- +defaultChannel: beta +name: foo +schema: olm.package +--- +name: beta +package: foo +schema: olm.channel +entries: + - name: foo.v0.3.1 + replaces: foo.v0.2.0 + skips: + - foo.v0.3.0 +--- +image: test.registry/foo-operator/foo-bundle:v0.3.1 +name: foo.v0.3.1 +package: foo +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvb3MudGVzdC5mb28ifSwic3BlYyI6eyJncm91cCI6InRlc3QuZm9vIiwibmFtZXMiOnsia2luZCI6IkZvbyIsInBsdXJhbCI6ImZvb3MifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSIsInNlcnZlZCI6dHJ1ZSwic3RvcmFnZSI6ZmFsc2V9LHsibmFtZSI6InYyIiwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfV19fQ== +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvby52MC4zLjEifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuZm9vIiwia2luZCI6IkZvbyIsIm5hbWUiOiJmb29zLnRlc3QuZm9vIiwidmVyc2lvbiI6InYxIn0seyJncm91cCI6InRlc3QuZm9vIiwia2luZCI6IkZvbyIsIm5hbWUiOiJmb29zLnRlc3QuZm9vIiwidmVyc2lvbiI6InYyIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Zvby1vcGVyYXRvci9mb286djAuMy4xIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImZvby52MC4yLjAiLCJza2lwcyI6WyJmb28udjAuMy4wIl0sInZlcnNpb24iOiIwLjMuMSJ9fQ== +- type: olm.gvk + value: + group: test.foo + kind: Foo + version: v1 +- type: olm.gvk + value: + group: test.foo + kind: Foo + version: v2 +- type: olm.gvk.required + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: foo + version: 0.3.1 +- type: olm.package.required + value: + packageName: bar + packageName: bar + versionRange: <0.2.0 +relatedImages: +- image: test.registry/foo-operator/foo:v0.3.1 + name: operator +schema: olm.bundle diff --git a/staging/operator-registry/alpha/declcfg/declcfg_to_model.go b/staging/operator-registry/alpha/declcfg/declcfg_to_model.go index d539c165f6..55d7751797 100644 --- a/staging/operator-registry/alpha/declcfg/declcfg_to_model.go +++ b/staging/operator-registry/alpha/declcfg/declcfg_to_model.go @@ -3,7 +3,7 @@ package declcfg import ( "fmt" - "github.com/blang/semver" + "github.com/blang/semver/v4" "k8s.io/apimachinery/pkg/util/sets" "github.com/operator-framework/operator-registry/alpha/model" @@ -18,6 +18,10 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { return nil, fmt.Errorf("config contains package with no name") } + if _, ok := mpkgs[p.Name]; ok { + return nil, fmt.Errorf("duplicate package %q", p.Name) + } + mpkg := &model.Package{ Name: p.Name, Description: p.Description, @@ -44,6 +48,10 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { return nil, fmt.Errorf("package %q contains channel with no name", c.Package) } + if _, ok := mpkg.Channels[c.Name]; ok { + return nil, fmt.Errorf("package %q has duplicate channel %q", c.Package, c.Name) + } + mch := &model.Channel{ Package: mpkg, Name: c.Name, @@ -75,6 +83,10 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { } } + // packageBundles tracks the set of bundle names for each package + // and is used to detect duplicate bundles. + packageBundles := map[string]sets.String{} + for _, b := range cfg.Bundles { if b.Package == "" { return nil, fmt.Errorf("package name must be set for bundle %q", b.Name) @@ -84,6 +96,16 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { return nil, fmt.Errorf("unknown package %q for bundle %q", b.Package, b.Name) } + bundles, ok := packageBundles[b.Package] + if !ok { + bundles = sets.NewString() + } + if bundles.Has(b.Name) { + return nil, fmt.Errorf("package %q has duplicate bundle %q", b.Package, b.Name) + } + bundles.Insert(b.Name) + packageBundles[b.Package] = bundles + props, err := property.Parse(b.Properties) if err != nil { return nil, fmt.Errorf("parse properties for bundle %q: %v", b.Name, err) @@ -98,9 +120,10 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { } // Parse version from the package property. - ver, err := semver.Parse(props.Packages[0].Version) + rawVersion := props.Packages[0].Version + ver, err := semver.Parse(rawVersion) if err != nil { - return nil, fmt.Errorf("error parsing bundle version: %v", err) + return nil, fmt.Errorf("error parsing bundle %q version %q: %v", b.Name, rawVersion, err) } channelDefinedEntries[b.Package] = channelDefinedEntries[b.Package].Delete(b.Name) diff --git a/staging/operator-registry/alpha/declcfg/declcfg_to_model_test.go b/staging/operator-registry/alpha/declcfg/declcfg_to_model_test.go index 6cfceedb22..34c7bc020a 100644 --- a/staging/operator-registry/alpha/declcfg/declcfg_to_model_test.go +++ b/staging/operator-registry/alpha/declcfg/declcfg_to_model_test.go @@ -77,7 +77,7 @@ func TestConvertToModel(t *testing.T) { }, { name: "Error/BundleInvalidVersion", - assertion: hasError(`error parsing bundle version: Invalid character(s) found in patch number "0.1"`), + assertion: hasError(`error parsing bundle "foo.v0.1.0" version "0.1.0.1": Invalid character(s) found in patch number "0.1"`), cfg: DeclarativeConfig{ Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) { @@ -87,6 +87,14 @@ func TestConvertToModel(t *testing.T) { })}, }, }, + { + name: "Error/BundleMissingVersion", + assertion: hasError(`error parsing bundle "foo.v" version "": Version string empty`), + cfg: DeclarativeConfig{ + Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, + Bundles: []Bundle{newTestBundle("foo", "", func(b *Bundle) {})}, + }, + }, { name: "Error/PackageMissingDefaultChannel", assertion: hasError(`invalid index: @@ -195,6 +203,42 @@ func TestConvertToModel(t *testing.T) { Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, }, }, + { + name: "Error/DuplicatePackage", + assertion: hasError(`duplicate package "foo"`), + cfg: DeclarativeConfig{ + Packages: []Package{ + newTestPackage("foo", "alpha", svgSmallCircle), + newTestPackage("foo", "alpha", svgSmallCircle), + }, + Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, + Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, + }, + }, + { + name: "Error/DuplicateChannel", + assertion: hasError(`package "foo" has duplicate channel "alpha"`), + cfg: DeclarativeConfig{ + Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, + Channels: []Channel{ + newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"}), + newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"}), + }, + Bundles: []Bundle{newTestBundle("foo", "0.1.0")}, + }, + }, + { + name: "Error/DuplicateBundle", + assertion: hasError(`package "foo" has duplicate bundle "foo.v0.1.0"`), + cfg: DeclarativeConfig{ + Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)}, + Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: "foo.v0.1.0"})}, + Bundles: []Bundle{ + newTestBundle("foo", "0.1.0"), + newTestBundle("foo", "0.1.0"), + }, + }, + }, { name: "Success/ValidModel", assertion: require.NoError, @@ -234,7 +278,7 @@ func hasError(expectedError string) require.ErrorAssertionFunc { if stdt, ok := t.(*testing.T); ok { stdt.Helper() } - if actualError.Error() == expectedError { + if actualError != nil && actualError.Error() == expectedError { return } t.Errorf("expected error to be `%s`, got `%s`", expectedError, actualError) diff --git a/staging/operator-registry/alpha/declcfg/diff.go b/staging/operator-registry/alpha/declcfg/diff.go index c4f78d660f..077ddc1655 100644 --- a/staging/operator-registry/alpha/declcfg/diff.go +++ b/staging/operator-registry/alpha/declcfg/diff.go @@ -6,7 +6,7 @@ import ( "sort" "sync" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/mitchellh/hashstructure/v2" "github.com/sirupsen/logrus" @@ -21,6 +21,10 @@ type DiffGenerator struct { // SkipDependencies directs Run() to not include dependencies // of bundles included in the diff if true. SkipDependencies bool + // Includer for adding catalog objects to Run() output. + Includer DiffIncluder + // IncludeAdditively catalog objects specified in Includer in headsOnly mode. + IncludeAdditively bool initOnce sync.Once } @@ -30,13 +34,21 @@ func (g *DiffGenerator) init() { if g.Logger == nil { g.Logger = &logrus.Entry{} } + if g.Includer.Logger == nil { + g.Includer.Logger = g.Logger + } }) } -// Run returns a Model containing everything in newModel not in oldModel, -// and all bundles that exist in oldModel but are different in newModel. -// If oldModel is empty, only channel heads in newModel's packages are -// added to the output Model. All dependencies not in oldModel are also added. +// Run returns a Model containing a subset of catalog objects in newModel: +// - If g.Includer contains objects: +// - If g.IncludeAdditively is false, a diff will be generated only on those objects, +// depending on the mode. +// - If g.IncludeAdditionally is true, the diff will contain included objects, +// plus those added by the mode. +// - If in heads-only mode (oldModel == nil), then the heads of channels are added to the output. +// - If in latest mode, a diff between old and new Models is added to the output. +// - Dependencies are added in all modes if g.SkipDependencies is false. func (g *DiffGenerator) Run(oldModel, newModel model.Model) (model.Model, error) { g.init() @@ -45,53 +57,102 @@ func (g *DiffGenerator) Run(oldModel, newModel model.Model) (model.Model, error) // load by package. outputModel := model.Model{} - if len(oldModel) == 0 { - // Heads-only mode. - - // Make shallow copies of packages and channels that are only - // filled with channel heads. - for _, newPkg := range newModel { - outputPkg := copyPackageNoChannels(newPkg) - outputModel[outputPkg.Name] = outputPkg - for _, newCh := range newPkg.Channels { - outputCh := copyChannelNoBundles(newCh, outputPkg) - outputPkg.Channels[outputCh.Name] = outputCh - head, err := newCh.Head() - if err != nil { - return nil, err - } - outputBundle := copyBundle(head, outputCh, outputPkg) - outputModel.AddBundle(*outputBundle) - } - } - } else { - // Latest mode. - - // Copy newModel to create an output model by deletion, - // which is more succinct than by addition and potentially - // more memory efficient. - for _, newPkg := range newModel { - outputModel[newPkg.Name] = copyPackage(newPkg) - } - // NB(estroz): if a net-new package or channel is published, - // this currently adds the entire package. I'm fairly sure - // this behavior is ok because the next diff after a new - // package is published still has only new data. + // Prunes old objects from outputModel if they exist. + latestPruneFromOutput := func() error { + for _, outputPkg := range outputModel { oldPkg, oldHasPkg := oldModel[outputPkg.Name] if !oldHasPkg { // outputPkg was already copied to outputModel above. continue } - if err := diffPackages(oldPkg, outputPkg); err != nil { - return nil, err + if err := pruneOldFromNewPackage(oldPkg, outputPkg); err != nil { + return err } if len(outputPkg.Channels) == 0 { // Remove empty packages. delete(outputModel, outputPkg.Name) } } + + return nil + } + + headsOnlyMode := len(oldModel) == 0 + latestMode := !headsOnlyMode + isInclude := len(g.Includer.Packages) != 0 + + switch { + case !g.IncludeAdditively && isInclude: // Only diff between included objects. + + // Add included packages/channels/bundles from newModel to outputModel. + if err := g.Includer.Run(newModel, outputModel); err != nil { + return nil, err + } + + if latestMode { + if err := latestPruneFromOutput(); err != nil { + return nil, err + } + } + + case isInclude: // Add included objects to outputModel. + + // Add included packages/channels/bundles from newModel to outputModel. + if err := g.Includer.Run(newModel, outputModel); err != nil { + return nil, err + } + + fallthrough + default: + + if headsOnlyMode { // Net-new diff of heads only. + + // Make shallow copies of packages and channels that are only + // filled with channel heads. + for _, newPkg := range newModel { + // This package may have been created in the include step. + outputPkg, pkgIncluded := outputModel[newPkg.Name] + if !pkgIncluded { + outputPkg = copyPackageNoChannels(newPkg) + outputModel[outputPkg.Name] = outputPkg + } + for _, newCh := range newPkg.Channels { + if _, chIncluded := outputPkg.Channels[newCh.Name]; chIncluded { + // Head (and other bundles) were added in the include step. + continue + } + outputCh := copyChannelNoBundles(newCh, outputPkg) + outputPkg.Channels[outputCh.Name] = outputCh + head, err := newCh.Head() + if err != nil { + return nil, err + } + outputBundle := copyBundle(head, outputCh, outputPkg) + outputModel.AddBundle(*outputBundle) + } + } + + } else { // Diff between old and new Model. + + // Copy newModel to create an output model by deletion, + // which is more succinct than by addition. + for _, newPkg := range newModel { + if _, pkgIncluded := outputModel[newPkg.Name]; pkgIncluded { + // The user has specified the state they want this package to have in the diff + // via an inclusion entry, so the package created above should not be changed. + continue + } + outputModel[newPkg.Name] = copyPackage(newPkg) + } + + if err := latestPruneFromOutput(); err != nil { + return nil, err + } + + } + } if !g.SkipDependencies { @@ -103,8 +164,8 @@ func (g *DiffGenerator) Run(oldModel, newModel model.Model) (model.Model, error) // Default channel may not have been copied, so set it to the new default channel here. for _, outputPkg := range outputModel { - outputHasDefault := false newPkg := newModel[outputPkg.Name] + var outputHasDefault bool outputPkg.DefaultChannel, outputHasDefault = outputPkg.Channels[newPkg.DefaultChannel.Name] if !outputHasDefault { // Create a name-only channel since oldModel contains the channel already. @@ -115,9 +176,9 @@ func (g *DiffGenerator) Run(oldModel, newModel model.Model) (model.Model, error) return outputModel, nil } -// diffPackages removes any bundles and channels from newPkg that +// pruneOldFromNewPackage prune any bundles and channels from newPkg that // are in oldPkg, but not those that differ in any way. -func diffPackages(oldPkg, newPkg *model.Package) error { +func pruneOldFromNewPackage(oldPkg, newPkg *model.Package) error { for _, newCh := range newPkg.Channels { oldCh, oldHasCh := oldPkg.Channels[newCh.Name] if !oldHasCh { @@ -274,7 +335,6 @@ func getBundles(m model.Model) (bundles []*model.Bundle) { for _, pkg := range m { for _, ch := range pkg.Channels { for _, b := range ch.Bundles { - b := b bundles = append(bundles, b) } } @@ -332,7 +392,6 @@ func getBundlesThatProvide(pkg *model.Package, reqGVKs map[property.GVK]struct{} bundlesProvidingGVK := make(map[property.GVK][]*model.Bundle) for _, ch := range pkg.Channels { for _, b := range ch.Bundles { - b := b for _, gvk := range b.PropertiesP.GVKs { if _, hasGVK := reqGVKs[gvk]; hasGVK { bundlesProvidingGVK[gvk] = append(bundlesProvidingGVK[gvk], b) @@ -438,15 +497,19 @@ func copyChannel(in *model.Channel, pkg *model.Package) *model.Channel { func copyBundle(in *model.Bundle, ch *model.Channel, pkg *model.Package) *model.Bundle { cp := &model.Bundle{ - Name: in.Name, - Channel: ch, - Package: pkg, - Image: in.Image, - Replaces: in.Replaces, - Version: semver.MustParse(in.Version.String()), - CsvJSON: in.CsvJSON, + Name: in.Name, + Channel: ch, + Package: pkg, + Image: in.Image, + Replaces: in.Replaces, + Version: semver.MustParse(in.Version.String()), + CsvJSON: in.CsvJSON, + SkipRange: in.SkipRange, + } + if in.PropertiesP != nil { + cp.PropertiesP = new(property.Properties) + *cp.PropertiesP = *in.PropertiesP } - cp.PropertiesP, _ = property.Parse(in.Properties) if len(in.Skips) != 0 { cp.Skips = make([]string, len(in.Skips)) copy(cp.Skips, in.Skips) diff --git a/staging/operator-registry/alpha/declcfg/diff_include.go b/staging/operator-registry/alpha/declcfg/diff_include.go index 14a4d5c890..06e9a64a61 100644 --- a/staging/operator-registry/alpha/declcfg/diff_include.go +++ b/staging/operator-registry/alpha/declcfg/diff_include.go @@ -1,14 +1,215 @@ package declcfg import ( + "fmt" + "strings" + + "github.com/blang/semver/v4" + "github.com/sirupsen/logrus" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "github.com/operator-framework/operator-registry/alpha/model" ) +// DiffIncluder knows how to add packages, channels, and bundles +// from a source to a destination model.Model. +type DiffIncluder struct { + // Packages to add. + Packages []DiffIncludePackage + Logger *logrus.Entry +} + +// DiffIncludePackage specifies a package, and optionally channels +// or a set of bundles from all channels (wrapped by a DiffIncludeChannel), +// to include. +type DiffIncludePackage struct { + // Name of package. + Name string + // Channels in package. + Channels []DiffIncludeChannel + // AllChannels contains bundle versions in package. + // Upgrade graphs from all channels in the named package containing a version + // from this field are included. + AllChannels DiffIncludeChannel +} + +// DiffIncludeChannel specifies a channel, and optionally bundles and bundle versions to include. +type DiffIncludeChannel struct { + // Name of channel. + Name string + // Versions of bundles. + Versions []semver.Version + // Bundles are bundle names to include. + // Set this field only if the named bundle has no semantic version metadata. + Bundles []string +} + +// Run adds all packages and channels in DiffIncluder with matching names +// directly, and all versions plus their upgrade graphs to channel heads, +// from newModel to outputModel. +func (i DiffIncluder) Run(newModel, outputModel model.Model) error { + var includeErrs []error + for _, ipkg := range i.Packages { + pkgLog := i.Logger.WithField("package", ipkg.Name) + includeErrs = append(includeErrs, ipkg.includeNewInOutputModel(newModel, outputModel, pkgLog)...) + } + if len(includeErrs) != 0 { + return fmt.Errorf("error including items:\n%v", utilerrors.NewAggregate(includeErrs)) + } + return nil +} + +// includeNewInOutputModel adds all packages, channels, and versions (bundles) +// specified by ipkg that exist in newModel to outputModel. Any package, channel, +// or version in ipkg not satisfied by newModel is an error. +func (ipkg DiffIncludePackage) includeNewInOutputModel(newModel, outputModel model.Model, logger *logrus.Entry) (ierrs []error) { + + newPkg, newHasPkg := newModel[ipkg.Name] + if !newHasPkg { + ierrs = append(ierrs, fmt.Errorf("[package=%q] package does not exist in new model", ipkg.Name)) + return ierrs + } + pkgLog := logger.WithField("package", newPkg.Name) + + // No channels or versions were specified, meaning "include the full package". + if len(ipkg.Channels) == 0 && len(ipkg.AllChannels.Versions) == 0 && len(ipkg.AllChannels.Bundles) == 0 { + outputModel[ipkg.Name] = newPkg + return nil + } + + outputPkg := copyPackageNoChannels(newPkg) + outputModel[outputPkg.Name] = outputPkg + + // Add all channels to ipkg.Channels if bundles or versions were specified to include across all channels. + // skipMissingBundleForChannels's value for a channel will be true IFF at least one version is specified, + // since some other channel may contain that version. + skipMissingBundleForChannels := map[string]bool{} + if len(ipkg.AllChannels.Versions) != 0 || len(ipkg.AllChannels.Bundles) != 0 { + for newChName := range newPkg.Channels { + ipkg.Channels = append(ipkg.Channels, DiffIncludeChannel{ + Name: newChName, + Versions: ipkg.AllChannels.Versions, + Bundles: ipkg.AllChannels.Bundles, + }) + skipMissingBundleForChannels[newChName] = true + } + } + + for _, ich := range ipkg.Channels { + newCh, pkgHasCh := newPkg.Channels[ich.Name] + if !pkgHasCh { + ierrs = append(ierrs, fmt.Errorf("[package=%q channel=%q] channel does not exist in new model", newPkg.Name, ich.Name)) + continue + } + chLog := pkgLog.WithField("channel", newCh.Name) + + bundles, err := getBundlesForVersions(newCh, ich.Versions, ich.Bundles, chLog, skipMissingBundleForChannels[newCh.Name]) + if err != nil { + ierrs = append(ierrs, fmt.Errorf("[package=%q channel=%q] %v", newPkg.Name, newCh.Name, err)) + continue + } + outputCh := copyChannelNoBundles(newCh, outputPkg) + outputPkg.Channels[outputCh.Name] = outputCh + for _, b := range bundles { + tb := copyBundle(b, outputCh, outputPkg) + outputCh.Bundles[tb.Name] = tb + } + } + + return ierrs +} + +// getBundlesForVersions returns all bundles matching a version in vers +// and their upgrade graph(s) to ch.Head(). +// If skipMissingBundles is true, bundle names and versions not satisfied by bundles in ch +// will not result in errors. +func getBundlesForVersions(ch *model.Channel, vers []semver.Version, names []string, logger *logrus.Entry, skipMissingBundles bool) (bundles []*model.Bundle, err error) { + + // Short circuit when no versions were specified, meaning "include the whole channel". + if len(vers) == 0 { + for _, b := range ch.Bundles { + bundles = append(bundles, b) + } + return bundles, nil + } + + // Add every bundle with a specified bundle name or directly satisfying a bundle version to bundles. + versionsToInclude := make(map[string]struct{}, len(vers)) + for _, ver := range vers { + versionsToInclude[ver.String()] = struct{}{} + } + namesToInclude := make(map[string]struct{}, len(vers)) + for _, name := range names { + namesToInclude[name] = struct{}{} + } + for _, b := range ch.Bundles { + _, includeVersionedBundle := versionsToInclude[b.Version.String()] + _, includeNamedBundle := namesToInclude[b.Name] + if includeVersionedBundle || includeNamedBundle { + bundles = append(bundles, b) + } + } + + // Some version was not satisfied by this channel. + if len(bundles) != len(versionsToInclude)+len(namesToInclude) && !skipMissingBundles { + for _, b := range bundles { + delete(versionsToInclude, b.Version.String()) + delete(namesToInclude, b.Name) + } + var verStrs, nameStrs []string + for verStr := range versionsToInclude { + verStrs = append(verStrs, verStr) + } + for nameStr := range namesToInclude { + nameStrs = append(nameStrs, nameStr) + } + sb := strings.Builder{} + if len(verStrs) != 0 { + sb.WriteString(fmt.Sprintf("versions=%+q ", verStrs)) + } + if len(nameStrs) != 0 { + sb.WriteString(fmt.Sprintf("names=%+q", nameStrs)) + } + return nil, fmt.Errorf("bundles do not exist in channel: %s", strings.TrimSpace(sb.String())) + } + + // Fill in the upgrade graph between each bundle and head. + // Regardless of semver order, this step needs to be performed + // for each included bundle because there might be leaf nodes + // in the upgrade graph for a particular version not captured + // by any other fill due to skips not being honored here. + head, err := ch.Head() + if err != nil { + return nil, err + } + graph := makeUpgradeGraph(ch) + bundleSet := map[string]*model.Bundle{} + for _, ib := range bundles { + if _, addedBundle := bundleSet[ib.Name]; addedBundle { + // A prior graph traverse already included this bundle. + continue + } + intersectingBundles, intersectionFound := findIntersectingBundles(ch, ib, head, graph) + if !intersectionFound { + logger.Debugf("channel head %q not reachable from bundle %q, adding without upgrade graph", head.Name, ib.Name) + bundleSet[ib.Name] = ib + } + + for _, rb := range intersectingBundles { + bundleSet[rb.Name] = rb + } + } + for _, b := range bundleSet { + bundles = append(bundles, b) + } + + return bundles, nil +} + // makeUpgradeGraph creates a DAG of bundles with map key Bundle.Replaces. func makeUpgradeGraph(ch *model.Channel) map[string][]*model.Bundle { graph := map[string][]*model.Bundle{} for _, b := range ch.Bundles { - b := b if b.Replaces != "" { graph[b.Replaces] = append(graph[b.Replaces], b) } diff --git a/staging/operator-registry/alpha/declcfg/diff_test.go b/staging/operator-registry/alpha/declcfg/diff_test.go index 51f92a9083..18f7dc657b 100644 --- a/staging/operator-registry/alpha/declcfg/diff_test.go +++ b/staging/operator-registry/alpha/declcfg/diff_test.go @@ -3,6 +3,7 @@ package declcfg import ( "testing" + "github.com/blang/semver/v4" "github.com/stretchr/testify/require" "github.com/operator-framework/operator-registry/alpha/model" @@ -991,6 +992,246 @@ func TestDiffLatest(t *testing.T) { }, }, }, + { + name: "HasDiff/IncludePackage", + oldCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + {Schema: schemaPackage, Name: "bar", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{{Name: "foo.v0.1.0"}}}, + {Schema: schemaChannel, Name: "stable", Package: "bar", Entries: []ChannelEntry{{Name: "bar.v0.1.0"}}}, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "bar.v0.1.0", Package: "bar", Image: "reg/bar:latest", + Properties: []property.Property{property.MustBuildPackage("bar", "0.1.0")}, + }, + }, + }, + newCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + {Schema: schemaPackage, Name: "bar", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{{Name: "foo.v0.1.0"}}}, + {Schema: schemaChannel, Name: "stable", Package: "bar", Entries: []ChannelEntry{ + {Name: "bar.v0.1.0"}, {Name: "bar.v0.2.0", Replaces: "bar.v0.1.0"}, + }}, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "bar.v0.1.0", Package: "bar", Image: "reg/bar:latest", + Properties: []property.Property{property.MustBuildPackage("bar", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "bar.v0.2.0", Package: "bar", Image: "reg/bar:latest", + Properties: []property.Property{property.MustBuildPackage("bar", "0.2.0")}, + }, + }, + }, + g: &DiffGenerator{ + Includer: DiffIncluder{ + Packages: []DiffIncludePackage{{Name: "bar"}}, + }, + }, + expCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "bar", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "bar", Entries: []ChannelEntry{ + {Name: "bar.v0.2.0", Replaces: "bar.v0.1.0"}, + }}, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "bar.v0.2.0", Package: "bar", Image: "reg/bar:latest", + Properties: []property.Property{property.MustBuildPackage("bar", "0.2.0")}, + }, + }, + }, + }, + { + name: "HasDiff/IncludeChannel", + oldCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{{Name: "foo.v0.1.0"}}}, + {Schema: schemaChannel, Name: "alpha", Package: "foo", Entries: []ChannelEntry{{Name: "foo.v0.1.0-alpha.0"}}}, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.1.0-alpha.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0-alpha.0")}, + }, + }, + }, + newCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "alpha"}, // Make sure the default channel is still updated. + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0"}, {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0"}}, + }, + {Schema: schemaChannel, Name: "alpha", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0-alpha.0"}, {Name: "foo.v0.2.0-alpha.0", Replaces: "foo.v0.1.0-alpha.0"}}, + }, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.2.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.2.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.1.0-alpha.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0-alpha.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.2.0-alpha.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.2.0-alpha.0")}, + }, + }, + }, + g: &DiffGenerator{ + Includer: DiffIncluder{ + Packages: []DiffIncludePackage{{Name: "foo", Channels: []DiffIncludeChannel{{Name: "stable"}}}}, + }, + }, + expCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "alpha"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0"}}, + }, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.2.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.2.0")}, + }, + }, + }, + }, + { + name: "HasDiff/IncludeVersion", + oldCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0"}, {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0"}}, + }, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.2.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.2.0")}, + }, + }, + }, + newCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0"}, {Name: "foo.v0.1.1", Replaces: "foo.v0.1.0"}, + {Name: "foo.v0.2.0", Replaces: "foo.v0.1.1"}, {Name: "foo.v0.3.0", Replaces: "foo.v0.2.0"}}, + }, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.1.1", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.1")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.2.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.2.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.3.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.3.0")}, + }, + }, + }, + g: &DiffGenerator{ + Includer: DiffIncluder{ + Packages: []DiffIncludePackage{ + {Name: "foo", Channels: []DiffIncludeChannel{ + {Name: "stable", Versions: []semver.Version{{Major: 0, Minor: 2, Patch: 0}}}}, + }}, + }, + }, + expCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.3.0", Replaces: "foo.v0.2.0"}}, + }, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.3.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.3.0")}, + }, + }, + }, + }, } for _, s := range specs { @@ -1494,6 +1735,561 @@ func TestDiffHeadsOnly(t *testing.T) { }, }, }, + { + name: "HasDiff/IncludeAdditive", + newCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "etcd", DefaultChannel: "stable"}, + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + {Schema: schemaPackage, Name: "bar", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "etcd", Entries: []ChannelEntry{ + {Name: "etcd.v0.9.0"}, + {Name: "etcd.v0.9.1", Replaces: "etcd.v0.9.0"}, + {Name: "etcd.v0.9.2", Replaces: "etcd.v0.9.1"}, + {Name: "etcd.v0.9.3", Replaces: "etcd.v0.9.2"}, + {Name: "etcd.v1.0.0", Replaces: "etcd.v0.9.3", Skips: []string{"etcd.v0.9.1", "etcd.v0.9.2", "etcd.v0.9.3"}}, + }}, + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0"}, + }}, + {Schema: schemaChannel, Name: "stable", Package: "bar", Entries: []ChannelEntry{ + {Name: "bar.v0.1.0"}, + }}, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", + Package: "foo", + Image: "reg/foo:latest", + Properties: []property.Property{ + property.MustBuildPackageRequired("etcd", "<0.9.2"), + property.MustBuildPackage("foo", "0.1.0"), + }, + }, + { + Schema: schemaBundle, + Name: "bar.v0.1.0", + Package: "bar", + Image: "reg/bar:latest", + Properties: []property.Property{ + property.MustBuildGVKRequired("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildPackage("bar", "0.1.0"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.0", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.0"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.1", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.1"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.2", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.2"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.3", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildGVK("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.3"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v1.0.0", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildGVK("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildPackage("etcd", "1.0.0"), + }, + }, + }, + }, + g: &DiffGenerator{ + IncludeAdditively: true, + Includer: DiffIncluder{ + Packages: []DiffIncludePackage{ + { + Name: "etcd", + Channels: []DiffIncludeChannel{{ + Name: "stable", + Versions: []semver.Version{{Major: 0, Minor: 9, Patch: 2}}}, + }}, + { + Name: "bar", + Channels: []DiffIncludeChannel{{Name: "stable"}}, + }, + }, + }, + }, + expCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "bar", DefaultChannel: "stable"}, + {Schema: schemaPackage, Name: "etcd", DefaultChannel: "stable"}, + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "bar", Entries: []ChannelEntry{ + {Name: "bar.v0.1.0"}, + }}, + {Schema: schemaChannel, Name: "stable", Package: "etcd", Entries: []ChannelEntry{ + {Name: "etcd.v0.9.1", Replaces: "etcd.v0.9.0"}, + {Name: "etcd.v0.9.2", Replaces: "etcd.v0.9.1"}, + {Name: "etcd.v0.9.3", Replaces: "etcd.v0.9.2"}, + {Name: "etcd.v1.0.0", Replaces: "etcd.v0.9.3", Skips: []string{"etcd.v0.9.1", "etcd.v0.9.2", "etcd.v0.9.3"}}, + }}, + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0"}, + }}, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "bar.v0.1.0", + Package: "bar", + Image: "reg/bar:latest", + Properties: []property.Property{ + property.MustBuildGVKRequired("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildPackage("bar", "0.1.0"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.1", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.1"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.2", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.2"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.3", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.3"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v1.0.0", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "1.0.0"), + }, + }, + { + Schema: schemaBundle, + Name: "foo.v0.1.0", + Package: "foo", + Image: "reg/foo:latest", + Properties: []property.Property{ + property.MustBuildPackage("foo", "0.1.0"), + property.MustBuildPackageRequired("etcd", "<0.9.2"), + }, + }, + }, + }, + }, + { + name: "HasDiff/IncludePackage", + newCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + {Schema: schemaPackage, Name: "bar", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{{Name: "foo.v0.1.0"}}}, + {Schema: schemaChannel, Name: "stable", Package: "bar", Entries: []ChannelEntry{ + {Name: "bar.v0.1.0"}, {Name: "bar.v0.2.0", Replaces: "bar.v0.1.0"}, + }}, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "bar.v0.1.0", Package: "bar", Image: "reg/bar:latest", + Properties: []property.Property{property.MustBuildPackage("bar", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "bar.v0.2.0", Package: "bar", Image: "reg/bar:latest", + Properties: []property.Property{property.MustBuildPackage("bar", "0.2.0")}, + }, + }, + }, + g: &DiffGenerator{ + Includer: DiffIncluder{ + Packages: []DiffIncludePackage{{Name: "bar"}}, + }, + }, + expCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "bar", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "bar", Entries: []ChannelEntry{ + {Name: "bar.v0.1.0"}, {Name: "bar.v0.2.0", Replaces: "bar.v0.1.0"}, + }}, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "bar.v0.1.0", Package: "bar", Image: "reg/bar:latest", + Properties: []property.Property{property.MustBuildPackage("bar", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "bar.v0.2.0", Package: "bar", Image: "reg/bar:latest", + Properties: []property.Property{property.MustBuildPackage("bar", "0.2.0")}, + }, + }, + }, + }, + { + name: "HasDiff/IncludeChannel", + newCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "alpha"}, // Make sure the default channel is still updated. + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0"}, {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0"}}, + }, + {Schema: schemaChannel, Name: "alpha", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0-alpha.0"}, {Name: "foo.v0.2.0-alpha.0", Replaces: "foo.v0.1.0-alpha.0"}}, + }, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.2.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.2.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.1.0-alpha.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0-alpha.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.2.0-alpha.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.2.0-alpha.0")}, + }, + }, + }, + g: &DiffGenerator{ + Includer: DiffIncluder{ + Packages: []DiffIncludePackage{{Name: "foo", Channels: []DiffIncludeChannel{{Name: "stable"}}}}, + }, + }, + expCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "alpha"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0"}, {Name: "foo.v0.2.0", Replaces: "foo.v0.1.0"}}, + }, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.2.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.2.0")}, + }, + }, + }, + }, + { + name: "HasDiff/IncludeVersion", + newCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0"}, {Name: "foo.v0.1.1", Replaces: "foo.v0.1.0"}, + {Name: "foo.v0.2.0", Replaces: "foo.v0.1.1"}, {Name: "foo.v0.3.0", Replaces: "foo.v0.2.0"}}, + }, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.1.1", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.1.1")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.2.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.2.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.3.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.3.0")}, + }, + }, + }, + g: &DiffGenerator{ + Includer: DiffIncluder{ + Packages: []DiffIncludePackage{ + {Name: "foo", Channels: []DiffIncludeChannel{ + {Name: "stable", Versions: []semver.Version{{Major: 0, Minor: 2, Patch: 0}}}}, + }}, + }, + }, + expCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.2.0", Replaces: "foo.v0.1.1"}, {Name: "foo.v0.3.0", Replaces: "foo.v0.2.0"}}, + }, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.2.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.2.0")}, + }, + { + Schema: schemaBundle, + Name: "foo.v0.3.0", Package: "foo", Image: "reg/foo:latest", + Properties: []property.Property{property.MustBuildPackage("foo", "0.3.0")}, + }, + }, + }, + }, + { + name: "HasDiff/IncludeNonAdditive", + newCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "etcd", DefaultChannel: "stable"}, + {Schema: schemaPackage, Name: "foo", DefaultChannel: "stable"}, + {Schema: schemaPackage, Name: "bar", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "etcd", Entries: []ChannelEntry{ + {Name: "etcd.v0.9.0"}, + {Name: "etcd.v0.9.1", Replaces: "etcd.v0.9.0"}, + {Name: "etcd.v0.9.2", Replaces: "etcd.v0.9.1"}, + {Name: "etcd.v0.9.3", Replaces: "etcd.v0.9.2"}, + {Name: "etcd.v1.0.0", Replaces: "etcd.v0.9.3", Skips: []string{"etcd.v0.9.1", "etcd.v0.9.2", "etcd.v0.9.3"}}, + }}, + {Schema: schemaChannel, Name: "stable", Package: "foo", Entries: []ChannelEntry{ + {Name: "foo.v0.1.0"}, + }}, + {Schema: schemaChannel, Name: "stable", Package: "bar", Entries: []ChannelEntry{ + {Name: "bar.v0.1.0"}, + }}, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "foo.v0.1.0", + Package: "foo", + Image: "reg/foo:latest", + Properties: []property.Property{ + property.MustBuildPackageRequired("etcd", "<0.9.2"), + property.MustBuildPackage("foo", "0.1.0"), + }, + }, + { + Schema: schemaBundle, + Name: "bar.v0.1.0", + Package: "bar", + Image: "reg/bar:latest", + Properties: []property.Property{ + property.MustBuildGVKRequired("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildPackage("bar", "0.1.0"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.0", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.0"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.1", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.1"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.2", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.2"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.3", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildGVK("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.3"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v1.0.0", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildGVK("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildPackage("etcd", "1.0.0"), + }, + }, + }, + }, + g: &DiffGenerator{ + Includer: DiffIncluder{ + Packages: []DiffIncludePackage{ + { + Name: "etcd", + Channels: []DiffIncludeChannel{{ + Name: "stable", + Versions: []semver.Version{{Major: 0, Minor: 9, Patch: 3}}}, + }}, + { + Name: "bar", + Channels: []DiffIncludeChannel{{Name: "stable"}}, + }, + }, + }, + }, + expCfg: DeclarativeConfig{ + Packages: []Package{ + {Schema: schemaPackage, Name: "bar", DefaultChannel: "stable"}, + {Schema: schemaPackage, Name: "etcd", DefaultChannel: "stable"}, + }, + Channels: []Channel{ + {Schema: schemaChannel, Name: "stable", Package: "bar", Entries: []ChannelEntry{ + {Name: "bar.v0.1.0"}, + }}, + {Schema: schemaChannel, Name: "stable", Package: "etcd", Entries: []ChannelEntry{ + {Name: "etcd.v0.9.3", Replaces: "etcd.v0.9.2"}, + {Name: "etcd.v1.0.0", Replaces: "etcd.v0.9.3", Skips: []string{"etcd.v0.9.1", "etcd.v0.9.2", "etcd.v0.9.3"}}, + }}, + }, + Bundles: []Bundle{ + { + Schema: schemaBundle, + Name: "bar.v0.1.0", + Package: "bar", + Image: "reg/bar:latest", + Properties: []property.Property{ + property.MustBuildGVKRequired("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildPackage("bar", "0.1.0"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v0.9.3", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "0.9.3"), + }, + }, + { + Schema: schemaBundle, + Name: "etcd.v1.0.0", + Package: "etcd", + Image: "reg/etcd:latest", + Properties: []property.Property{ + property.MustBuildGVK("etcd.database.coreos.com", "v1", "EtcdBackup"), + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildPackage("etcd", "1.0.0"), + }, + }, + }, + }, + }, } for _, s := range specs { diff --git a/staging/operator-registry/alpha/model/model.go b/staging/operator-registry/alpha/model/model.go index babcd08cc7..75042469a2 100644 --- a/staging/operator-registry/alpha/model/model.go +++ b/staging/operator-registry/alpha/model/model.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/h2non/filetype" "github.com/h2non/filetype/matchers" "github.com/h2non/filetype/types" diff --git a/staging/operator-registry/bundles/apicurio-registry-operator.v1.0.0-v2.0.0.final/manifests/apicurio-registry-operator.clusterserviceversion.yaml b/staging/operator-registry/bundles/apicurio-registry-operator.v1.0.0-v2.0.0.final/manifests/apicurio-registry-operator.clusterserviceversion.yaml new file mode 100644 index 0000000000..fab60c275f --- /dev/null +++ b/staging/operator-registry/bundles/apicurio-registry-operator.v1.0.0-v2.0.0.final/manifests/apicurio-registry-operator.clusterserviceversion.yaml @@ -0,0 +1,327 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "registry.apicur.io/v1", + "kind": "ApicurioRegistry", + "metadata": { + "name": "example-apicurioregistry-kafkasql" + }, + "spec": { + "configuration": { + "kafkasql": { + "bootstrapServers": "\u003cservice name\u003e.\u003cnamespace\u003e.svc:9092" + }, + "persistence": "kafkasql" + } + } + }, + { + "apiVersion": "registry.apicur.io/v1", + "kind": "ApicurioRegistry", + "metadata": { + "name": "example-apicurioregistry-mem" + }, + "spec": { + "configuration": { + "persistence": "mem" + } + } + }, + { + "apiVersion": "registry.apicur.io/v1", + "kind": "ApicurioRegistry", + "metadata": { + "name": "example-apicurioregistry-sql" + }, + "spec": { + "configuration": { + "persistence": "sql", + "sql": { + "dataSource": { + "password": "\u003cpassword\u003e", + "url": "jdbc:postgresql://\u003cservice name\u003e.\u003cnamespace\u003e.svc:5432/\u003cdatabase name\u003e", + "userName": "postgres" + } + } + } + } + } + ] + capabilities: Basic Install + categories: Streaming & Messaging + certified: "false" + containerImage: quay.io/apicurio/apicurio-registry-operator:1.0.0 + createdAt: 2021-05-19 + description: Deploy and manage Apicurio Registry on Kubernetes. + operators.operatorframework.io/builder: operator-sdk-v1.4.2 + operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 + repository: https://github.com/Apicurio/apicurio-registry-operator + support: Apicurio + name: apicurio-registry-operator.v1.0.0-v2.0.0.final + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: ApicurioRegistry represents an Apicurio Registry instance + displayName: Apicurio Registry + kind: ApicurioRegistry + name: apicurioregistries.registry.apicur.io + version: v1 + description: | + ## Apicurio Registry + + Apicurio Registry stores and retrieves API designs and event schemas, + and gives you control of their evolution. + + **Features** + - Supports: Apache Avro, AsyncAPI, GraphQL, JSON Schema, Kafka Connect Schema, OpenAPI, Protobuf + - Provides a REST API to manage the artifacts and artifact meta-data + - Includes Serializers and Deserializers for Kafka client integration + - Configurable rules to control schema validity and evolution (compatibility) + - Storage options: Kafka Streams, PostgreSQL, in-memory + - Compatible with Confluent and IBM APIs + - Runs on a lightweight Quarkus platform + - Includes Maven plugin to integrate with Maven based builds + + ## Apicurio Registry Operator + + Provides a quick and easy way to deploy and manage an Apicurio Registry on Kubernetes. + + **Features** + - Supports basic Install and configuration of the Registry + - Can optionally create an Ingress to access the API and UI on HTTP port 80 + - Manual horizontal scaling + - Easily perform a rolling upgrade of the Registry + + ## Prerequisites + + This operator does not deploy storage for the Registry. Therefore, some storage options require that the chosen persistence service is already set up. You can do this using an operator for the specified service, such as Strimzi for Kafka Streams. + displayName: Apicurio Registry Operator + icon: + - base64data: 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 + mediatype: image/jpeg + install: + spec: + clusterPermissions: + - rules: + - apiGroups: + - apps + resources: + - daemonsets + - deployments + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - apps.openshift.io + resources: + - deploymentconfigs + verbs: + - '*' + - apiGroups: + - config.openshift.io + resources: + - clusterversions + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - persistentvolumeclaims + - pods + - secrets + - services + - services/finalizers + verbs: + - '*' + - apiGroups: + - events + resources: + - events + verbs: + - '*' + - apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - '*' + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - '*' + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - '*' + - apiGroups: + - registry.apicur.io + resources: + - apicurioregistries + verbs: + - '*' + - apiGroups: + - registry.apicur.io + resources: + - apicurioregistries/finalizers + verbs: + - update + - apiGroups: + - registry.apicur.io + resources: + - apicurioregistries/status + verbs: + - get + - patch + - update + - apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: + - '*' + serviceAccountName: apicurio-registry-operator + deployments: + - name: apicurio-registry-operator + spec: + replicas: 1 + selector: + matchLabels: + apicur.io/name: apicurio-registry-operator + apicur.io/type: operator + apicur.io/version: 1.0.0 + name: apicurio-registry-operator + strategy: {} + template: + metadata: + labels: + apicur.io/name: apicurio-registry-operator + apicur.io/type: operator + apicur.io/version: 1.0.0 + name: apicurio-registry-operator + spec: + containers: + - args: + - --leader-elect + command: + - /manager + env: + - name: REGISTRY_VERSION + value: 2.0.0.Final + - name: REGISTRY_IMAGE_MEM + value: quay.io/apicurio/apicurio-registry-mem:2.0.0.Final + - name: REGISTRY_IMAGE_KAFKASQL + value: quay.io/apicurio/apicurio-registry-kafkasql:2.0.0.Final + - name: REGISTRY_IMAGE_SQL + value: quay.io/apicurio/apicurio-registry-sql:2.0.0.Final + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: apicurio-registry-operator + image: quay.io/apicurio/apicurio-registry-operator:1.0.0 + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: apicurio-registry-operator + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 100m + memory: 100Mi + requests: + cpu: 100m + memory: 50Mi + serviceAccountName: apicurio-registry-operator + terminationGracePeriodSeconds: 10 + permissions: + - rules: + - apiGroups: + - "" + - coordination.k8s.io + resources: + - configmaps + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + serviceAccountName: apicurio-registry-operator + strategy: deployment + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: true + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - integration + - streaming + - messaging + - api + - schemas + - registry + - apicurio + - apicurio-registry + links: + - name: Website + url: https://www.apicur.io/ + - name: GitHub + url: https://github.com/Apicurio/apicurio-registry/ + - name: Issues + url: https://github.com/Apicurio/apicurio-registry/issues + - name: Twitter + url: https://twitter.com/Apicurio + maintainers: + - email: apicurio@lists.jboss.org + name: Apicurio + maturity: alpha + provider: + name: Apicurio + selector: {} + version: 1.0.0-v2.0.0.final diff --git a/staging/operator-registry/bundles/apicurio-registry.v0.0.1/manifests/apicurio-registry-operator.v0.0.1.clusterserviceversion.yaml b/staging/operator-registry/bundles/apicurio-registry.v0.0.1/manifests/apicurio-registry-operator.v0.0.1.clusterserviceversion.yaml new file mode 100644 index 0000000000..9390151d73 --- /dev/null +++ b/staging/operator-registry/bundles/apicurio-registry.v0.0.1/manifests/apicurio-registry-operator.v0.0.1.clusterserviceversion.yaml @@ -0,0 +1,277 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: apicurio-registry.v0.0.1 + namespace: placeholder + annotations: + # Setting olm.maxOpenShiftVersion automatically + # This property was added via an automatic process since it was possible to identify that this distribution uses API(s), + # which will be removed in the k8s version 1.22 and OpenShift version OCP 4.9. Then, it will prevent OCP users to + # upgrade their cluster to 4.9 before they have installed in their current clusters a version of your operator that + # is compatible with it. Please, ensure that your project is no longer using these API(s) and that you start to + # distribute solutions which is compatible with Openshift 4.9. + # For further information, check the README of this repository. + olm.properties: '[{"type": "olm.maxOpenShiftVersion", "value": "4.8"}]' + alm-examples: >- + [{"apiVersion":"apicur.io/v1alpha1","kind":"ApicurioRegistry","metadata":{"name":"example-apicurioregistry"},"spec":{"image":{"registry":"docker.io/apicurio","version":"latest-release"},"configuration":{"persistence":"mem"},"deployment":{"route":"registry.example.com"}}}] + categories: ' Streaming & Messaging' + certified: 'false' + createdAt: '2020-05-27' + description: Deploy and manage Apicurio Registry on Kubernetes. + containerImage: 'apicurio/apicurio-registry-operator:0.0.1' + support: 'Apicurio Project' + capabilities: Basic Install + repository: 'https://github.com/Apicurio/apicurio-registry-operator' +spec: + displayName: Apicurio Registry Operator + description: > + ## Apicurio Registry + + + Apicurio Registry stores and retrieves API designs and event schemas, and + gives you control of their evolution. + + + **Features** + + - Supports: Apache Avro, AsyncAPI, GraphQL, JSON Schema, Kafka Connect + Schema, OpenAPI, Protobuf + + - Provides a REST API to manage the artifacts and artifact meta-data + + - Includes Serializers and Deserializers for Kafka client integration + + - Configurable rules to control schema validity and evolution + (compatibility) + + - Storage options: Kafka Streams, Kafka, PostgreSQL, in-memory + + - Compatible with Confluent and IBM APIs + + - Runs on a lightweight Quarkus platform + + - Includes Maven plugin to integrate with Maven based builds + + ## Apicurio Registry Operator + + + Provides a quick and easy way to deploy and manage an Apicurio Registry on + Kubernetes. + + + **Features** + + - Supports basic Install and configuration of the Registry + + - Can optionally create an Ingress to access the API and UI on HTTP port 80 + + - Manual horizontal scaling + + - Easily perform a rolling upgrade of the Registry + + ## Prerequisites + + This operator does not deploy storage for the Registry. Therefore, some storage options require that the chosen persistence service is already set up. You can do this using an operator for the specified service, such as Strimzi for Kafka Streams. + maturity: alpha + version: 0.0.1 + skips: [] + minKubeVersion: '' + keywords: [] + maintainers: + - name: Apicurio + email: 'apicurio@lists.jboss.org' + provider: + name: Apicurio + labels: {} + selector: + matchLabels: {} + links: + - name: Website + url: 'https://www.apicur.io/' + - name: GitHub + url: 'https://github.com/Apicurio/apicurio-registry/' + - name: Issues + url: 'https://github.com/Apicurio/apicurio-registry/issues' + - name: Twitter + url: 'https://twitter.com/Apicurio' + icon: + - base64data: >- + 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 + mediatype: image/jpeg + customresourcedefinitions: + owned: + - name: apicurioregistries.apicur.io + displayName: ApicurioRegistry + kind: ApicurioRegistry + version: v1alpha1 + description: Apicurio Registry + resources: + - version: v1 + kind: Deployment + - version: v1 + kind: Service + - version: v1 + kind: ReplicaSet + - version: v1 + kind: Pod + - version: v1 + kind: Secret + - version: v1 + kind: ConfigMap + specDescriptors: + - path: configuration + description: Configuration + displayName: Configuration + x-descriptors: [] + - path: deployment + description: Deployment + displayName: Deployment + x-descriptors: [] + - path: image + description: Image + displayName: Image + x-descriptors: [] + statusDescriptors: + - path: deploymentName + description: Deployment Name + displayName: Deployment Name + x-descriptors: [] + - path: image + description: Image + displayName: Image + x-descriptors: [] + - path: ingressName + description: Ingress Name + displayName: Ingress Name + x-descriptors: [] + - path: replicaCount + description: Replica Count + displayName: Replica Count + x-descriptors: [] + - path: route + description: Route + displayName: Route + x-descriptors: [] + - path: serviceName + description: Service Name + displayName: Service Name + x-descriptors: [] + required: [] + install: + strategy: deployment + spec: + permissions: + - serviceAccountName: apicurio-registry + rules: + - apiGroups: + - route.openshift.io + - apps.openshift.io + resources: + - routes/custom-host + - deploymentconfigs + verbs: + - '*' + - apiGroups: + - '' + - extensions + - route.openshift.io + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + - ingresses + - routes/custom-host + verbs: + - '*' + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - get + - create + - apiGroups: + - apps + resourceNames: + - apicurio-registry + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - '' + resources: + - pods + verbs: + - get + - apiGroups: + - apps + resources: + - replicasets + verbs: + - get + - apiGroups: + - apicur.io + resources: + - '*' + verbs: + - '*' + clusterPermissions: + - serviceAccountName: apicurio-registry + rules: + - apiGroups: + - config.openshift.io + resources: + - clusterversions + verbs: + - get + deployments: + - name: apicurio-registry + spec: + replicas: 1 + selector: + matchLabels: + name: apicurio-registry + template: + metadata: + labels: + name: apicurio-registry + spec: + serviceAccountName: apicurio-registry + containers: + - name: apicurio-registry + image: 'apicurio/apicurio-registry-operator:0.0.1' + imagePullPolicy: Always + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: apicurio-registry + installModes: + - type: OwnNamespace + supported: true + - type: SingleNamespace + supported: true + - type: MultiNamespace + supported: false + - type: AllNamespaces + supported: false diff --git a/staging/operator-registry/bundles/apicurio-registry.v0.0.3-v1.2.3.final/manifests/apicurio-registry.v0.0.3-v1.2.3.final.clusterserviceversion.yaml b/staging/operator-registry/bundles/apicurio-registry.v0.0.3-v1.2.3.final/manifests/apicurio-registry.v0.0.3-v1.2.3.final.clusterserviceversion.yaml new file mode 100644 index 0000000000..5fa6028911 --- /dev/null +++ b/staging/operator-registry/bundles/apicurio-registry.v0.0.3-v1.2.3.final/manifests/apicurio-registry.v0.0.3-v1.2.3.final.clusterserviceversion.yaml @@ -0,0 +1,266 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + # Setting olm.maxOpenShiftVersion automatically + # This property was added via an automatic process since it was possible to identify that this distribution uses API(s), + # which will be removed in the k8s version 1.22 and OpenShift version OCP 4.9. Then, it will prevent OCP users to + # upgrade their cluster to 4.9 before they have installed in their current clusters a version of your operator that + # is compatible with it. Please, ensure that your project is no longer using these API(s) and that you start to + # distribute solutions which is compatible with Openshift 4.9. + # For further information, check the README of this repository. + olm.properties: '[{"type": "olm.maxOpenShiftVersion", "value": "4.8"}]' + alm-examples: |- + [ + { + "apiVersion": "apicur.io/v1alpha1", + "kind": "ApicurioRegistry", + "metadata": { + "name": "example-apicurioregistry" + } + } + ] + capabilities: Basic Install + categories: Streaming & Messaging + certified: 'false' + containerImage: docker.io/apicurio/apicurio-registry-operator:0.0.3 + createdAt: 2020-07-08 + description: Deploy and manage Apicurio Registry on Kubernetes. + repository: https://github.com/Apicurio/apicurio-registry-operator + support: Apicurio + name: apicurio-registry.v0.0.3-v1.2.3.final + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: ApicurioRegistry is the Schema for the apicurioregistries API + kind: ApicurioRegistry + name: apicurioregistries.apicur.io + version: v1alpha1 + description: | + ## Apicurio Registry + + Apicurio Registry stores and retrieves API designs and event schemas, + and gives you control of their evolution. + + **Features** + - Supports: Apache Avro, AsyncAPI, GraphQL, JSON Schema, Kafka Connect Schema, OpenAPI, Protobuf + - Provides a REST API to manage the artifacts and artifact meta-data + - Includes Serializers and Deserializers for Kafka client integration + - Configurable rules to control schema validity and evolution (compatibility) + - Storage options: Kafka Streams, Kafka, PostgreSQL, in-memory + - Compatible with Confluent and IBM APIs + - Runs on a lightweight Quarkus platform + - Includes Maven plugin to integrate with Maven based builds + + ## Apicurio Registry Operator + + Provides a quick and easy way to deploy and manage an Apicurio Registry on Kubernetes. + + **Features** + - Supports basic Install and configuration of the Registry + - Can optionally create an Ingress to access the API and UI on HTTP port 80 + - Manual horizontal scaling + - Easily perform a rolling upgrade of the Registry + + ## Prerequisites + + This operator does not deploy storage for the Registry. Therefore, some storage options require that the chosen persistence service is already set up. You can do this using an operator for the specified service, such as Strimzi for Kafka Streams. + displayName: Apicurio Registry Operator + icon: + - base64data: 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 + mediatype: image/jpeg + install: + spec: + clusterPermissions: + - rules: + - apiGroups: + - config.openshift.io + resources: + - clusterversions + verbs: + - get + - apiGroups: + - apicur.io + resources: + - apicurioregistries + verbs: + - '*' + serviceAccountName: apicurio-registry-operator + deployments: + - name: apicurio-registry-operator + spec: + replicas: 1 + selector: + matchLabels: + name: apicurio-registry-operator + strategy: {} + template: + metadata: + labels: + name: apicurio-registry-operator + spec: + containers: + - env: + - name: REGISTRY_IMAGE_MEM + value: docker.io/apicurio/apicurio-registry-mem@sha256:e4712e66edfc0a217531c282b1e2513172c993b5eb37f5c1024b2c14af6d7874 + - name: REGISTRY_IMAGE_KAFKA + value: docker.io/apicurio/apicurio-registry-kafka@sha256:d4748be356fe135be9cd58e679182d3e2f9023ab7421cf3923fb54cd8666f2c9 + - name: REGISTRY_IMAGE_STREAMS + value: docker.io/apicurio/apicurio-registry-streams@sha256:ed272fb0c50f828e67d9c9ff885786043de356da389b169cc790c6169f084174 + - name: REGISTRY_IMAGE_JPA + value: docker.io/apicurio/apicurio-registry-jpa@sha256:f32d41977224d99eea6769b4b4ce3de3c2690be4af107f1eae6d42f8ae536a94 + - name: REGISTRY_IMAGE_INFINISPAN + value: docker.io/apicurio/apicurio-registry-infinispan@sha256:7a7936fde7058c360e98406019f1291e57b1895884f5563e0e71cc5e3c6a04fb + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: apicurio-registry-operator + image: docker.io/apicurio/apicurio-registry-operator@sha256:7d5164d09818a9605fbf8d1f8695206cf929bf17b267fc32d0ee3ca9d6ab5d4b + imagePullPolicy: Always + name: apicurio-registry-operator + resources: + limits: + cpu: 4m + memory: 64Mi + requests: + cpu: 2m + memory: 32Mi + serviceAccountName: apicurio-registry-operator + permissions: + - rules: + - apiGroups: + - route.openshift.io + - apps.openshift.io + resources: + - routes + - routes/custom-host + - deploymentconfigs + verbs: + - '*' + - apiGroups: + - "" + - extensions + - route.openshift.io + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + - ingresses + - routes/custom-host + verbs: + - '*' + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - '*' + - apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - get + - create + - apiGroups: + - apps + resourceNames: + - apicurio-registry-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - apiGroups: + - apps + resources: + - replicasets + verbs: + - get + - apiGroups: + - apicur.io + resources: + - '*' + verbs: + - '*' + - apiGroups: + - "" + resources: + - services/finalizers + verbs: + - update + serviceAccountName: apicurio-registry-operator + strategy: deployment + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: false + type: AllNamespaces + keywords: + - integration + - streaming + - messaging + - api + - schemas + - registry + - apicurio + - apicurio-registry + links: + - name: Website + url: https://www.apicur.io/ + - name: GitHub + url: https://github.com/Apicurio/apicurio-registry/ + - name: Issues + url: https://github.com/Apicurio/apicurio-registry/issues + - name: Twitter + url: https://twitter.com/Apicurio + maintainers: + - email: apicurio@lists.jboss.org + name: Apicurio + maturity: alpha + provider: + name: Apicurio + relatedImages: + - name: apicurio-registry-mem + image: docker.io/apicurio/apicurio-registry-mem@sha256:e4712e66edfc0a217531c282b1e2513172c993b5eb37f5c1024b2c14af6d7874 + - name: apicurio-registry-kafka + image: docker.io/apicurio/apicurio-registry-kafka@sha256:d4748be356fe135be9cd58e679182d3e2f9023ab7421cf3923fb54cd8666f2c9 + - name: apicurio-registry-streams + image: docker.io/apicurio/apicurio-registry-streams@sha256:ed272fb0c50f828e67d9c9ff885786043de356da389b169cc790c6169f084174 + - name: apicurio-registry-jpa + image: docker.io/apicurio/apicurio-registry-jpa@sha256:f32d41977224d99eea6769b4b4ce3de3c2690be4af107f1eae6d42f8ae536a94 + - name: apicurio-registry-infinispan + image: docker.io/apicurio/apicurio-registry-infinispan@sha256:7a7936fde7058c360e98406019f1291e57b1895884f5563e0e71cc5e3c6a04fb + replaces: apicurio-registry.v0.0.1 + selector: {} + version: 0.0.3-v1.2.3.final diff --git a/staging/operator-registry/bundles/apicurio-registry.v0.0.4-v1.3.2.final/manifests/apicurio-registry.v0.0.4-v1.3.2.final.clusterserviceversion.yaml b/staging/operator-registry/bundles/apicurio-registry.v0.0.4-v1.3.2.final/manifests/apicurio-registry.v0.0.4-v1.3.2.final.clusterserviceversion.yaml new file mode 100644 index 0000000000..eec0686f1e --- /dev/null +++ b/staging/operator-registry/bundles/apicurio-registry.v0.0.4-v1.3.2.final/manifests/apicurio-registry.v0.0.4-v1.3.2.final.clusterserviceversion.yaml @@ -0,0 +1,391 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + # Setting olm.maxOpenShiftVersion automatically + # This property was added via an automatic process since it was possible to identify that this distribution uses API(s), + # which will be removed in the k8s version 1.22 and OpenShift version OCP 4.9. Then, it will prevent OCP users to + # upgrade their cluster to 4.9 before they have installed in their current clusters a version of your operator that + # is compatible with it. Please, ensure that your project is no longer using these API(s) and that you start to + # distribute solutions which is compatible with Openshift 4.9. + # For further information, check the README of this repository. + olm.properties: '[{"type": "olm.maxOpenShiftVersion", "value": "4.8"}]' + alm-examples: |- + [ + { + "apiVersion": "apicur.io/v1alpha1", + "kind": "ApicurioRegistry", + "metadata": { + "name": "example-apicurioregistry" + } + } + ] + capabilities: Basic Install + categories: Streaming & Messaging + certified: "false" + containerImage: docker.io/apicurio/apicurio-registry-operator:0.0.4 + createdAt: 2020-11-18 + description: Deploy and manage Apicurio Registry on Kubernetes. + repository: https://github.com/Apicurio/apicurio-registry-operator + support: Apicurio + name: apicurio-registry.v0.0.4-v1.3.2.final + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: ApicurioRegistry is the Schema for the apicurioregistries API + kind: ApicurioRegistry + name: apicurioregistries.apicur.io + version: v1alpha1 + specDescriptors: + - description: The DataSource user name + displayName: User Name + path: configuration.dataSource.userName + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:dataSource + - urn:alm:descriptor:com.tectonic.ui:text + - description: The DataSource Password + displayName: Password + path: configuration.dataSource.password + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:dataSource + - urn:alm:descriptor:com.tectonic.ui:password + - description: The DataSource URL + displayName: URL + path: configuration.dataSource.url + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:dataSource + - urn:alm:descriptor:com.tectonic.ui:text + - description: The Infinispan Cluster Name + displayName: Cluster Name + path: configuration.infinispan.clusterName + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:infinispan + - urn:alm:descriptor:com.tectonic.ui:text + - description: The kafka Bootstrap Servers + displayName: Bootstrap Servers + path: configuration.kafka.bootstrapServers + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:kafka + - urn:alm:descriptor:com.tectonic.ui:text + - description: Log Level + displayName: Log Level + path: configuration.logLevel + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:logLevel + - urn:alm:descriptor:com.tectonic.ui:text + - description: Select the Persistence Type + displayName: Persistence Type + path: configuration.persistence + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:persistence + - urn:alm:descriptor:com.tectonic.ui:select + - description: Application Id + displayName: Application Id + path: configuration.streams.applicationId + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:streams + - urn:alm:descriptor:com.tectonic.ui:text + - description: Application Server Port + displayName: Application Server Port + path: configuration.streams.applicationServerPort + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:streams + - urn:alm:descriptor:com.tectonic.ui:text + - description: Bootstrap Servers + displayName: Bootstrap Servers + path: configuration.streams.bootstrapServers + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:streams + - urn:alm:descriptor:com.tectonic.ui:text + - description: Mechanism + displayName: Mechanism + path: configuration.streams.security.scram.mechanism + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:scram + - urn:alm:descriptor:com.tectonic.ui:text + - description: Password Secret Name + displayName: Password Secret Name + path: configuration.streams.security.scram.passwordSecretName + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:scram + - urn:alm:descriptor:com.tectonic.ui:text + - description: Truststore Secret Name + displayName: Truststore Secret Name + path: configuration.streams.security.scram.truststoreSecretName + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:scram + - urn:alm:descriptor:com.tectonic.ui:text + - description: User + displayName: User + path: configuration.streams.security.scram.user + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:scram + - urn:alm:descriptor:com.tectonic.ui:text + - description: Keystore Secret Name + displayName: Keystore Secret Name + path: configuration.streams.security.tls.keystoreSecretName + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:tls + - urn:alm:descriptor:com.tectonic.ui:text + - description: Truststore Secret Name + displayName: Truststore Secret Name + path: configuration.streams.security.tls.truststoreSecretName + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:tls + - urn:alm:descriptor:com.tectonic.ui:text + - description: Read Only + displayName: Read Only + path: configuration.ui.readOnly + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:ui + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Override the image name according to selected Persistence Type for example, MEM="docker.io/apicurio/apicurio-registry-mem:1.3.2.Final" KAFKA="docker.io/apicurio/apicurio-registry-kafka:1.3.2.Final" STREAMS="docker.io/apicurio/apicurio-registry-streams:1.3.2.Final" JPA="docker.io/apicurio/apicurio-registry-jpa:1.3.2.Final" INFINISPAN="docker.io/apicurio/apicurio-registry-infinispan:1.3.2.Final" + displayName: Image Name + path: image.name + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:image + - urn:alm:descriptor:com.tectonic.ui:text + - description: The number of replicas + displayName: Replicas + path: deployment.replicas + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:deployment + - urn:alm:descriptor:com.tectonic.ui:podCount + description: | + ## Apicurio Registry + + Apicurio Registry stores and retrieves API designs and event schemas, + and gives you control of their evolution. + + **Features** + - Supports: Apache Avro, AsyncAPI, GraphQL, JSON Schema, Kafka Connect Schema, OpenAPI, Protobuf + - Provides a REST API to manage the artifacts and artifact meta-data + - Includes Serializers and Deserializers for Kafka client integration + - Configurable rules to control schema validity and evolution (compatibility) + - Storage options: Kafka Streams, Kafka, PostgreSQL, in-memory + - Compatible with Confluent and IBM APIs + - Runs on a lightweight Quarkus platform + - Includes Maven plugin to integrate with Maven based builds + + ## Apicurio Registry Operator + + Provides a quick and easy way to deploy and manage an Apicurio Registry on Kubernetes. + + **Features** + - Supports basic Install and configuration of the Registry + - Can optionally create an Ingress to access the API and UI on HTTP port 80 + - Manual horizontal scaling + - Easily perform a rolling upgrade of the Registry + + ## Prerequisites + + This operator does not deploy storage for the Registry. Therefore, some storage options require that the chosen persistence service is already set up. You can do this using an operator for the specified service, such as Strimzi for Kafka Streams. + displayName: Apicurio Registry Operator + icon: + - base64data: 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 + mediatype: image/jpeg + install: + spec: + clusterPermissions: + - rules: + - apiGroups: + - config.openshift.io + resources: + - clusterversions + verbs: + - get + - apiGroups: + - apicur.io + resources: + - apicurioregistries + verbs: + - '*' + serviceAccountName: apicurio-registry-operator + deployments: + - name: apicurio-registry-operator + spec: + replicas: 1 + selector: + matchLabels: + name: apicurio-registry-operator + strategy: {} + template: + metadata: + labels: + apicur.io/name: apicurio-registry-operator + apicur.io/type: operator + apicur.io/version: 0.0.4 + name: apicurio-registry-operator + spec: + containers: + - env: + - name: REGISTRY_VERSION + value: 1.3.2.Final + - name: REGISTRY_IMAGE_MEM + value: docker.io/apicurio/apicurio-registry-mem@sha256:9e3e35e16a72c6c065b4be6c1fabcb2e9f7ed73b36600e05b1e46b4cc394413f # 1.3.2.Final + - name: REGISTRY_IMAGE_KAFKA + value: docker.io/apicurio/apicurio-registry-kafka@sha256:cdac38135c67f3da808e55b16beaa4fc3cc67b846af1e64f491417d4107d60aa # 1.3.2.Final + - name: REGISTRY_IMAGE_STREAMS + value: docker.io/apicurio/apicurio-registry-streams@sha256:d3bfb0d13498dedd563d7b45dfa2461e560bf0fbf08ff3767254bb8a198f4845 # 1.3.2.Final + - name: REGISTRY_IMAGE_JPA + value: docker.io/apicurio/apicurio-registry-jpa@sha256:44eeddd3562ca3ac3f5fb440e5f68880ab9b4d6caa0b34541a9178c4a039b1f4 # 1.3.2.Final + - name: REGISTRY_IMAGE_INFINISPAN + value: docker.io/apicurio/apicurio-registry-infinispan@sha256:4dfe0cba302f8243c5c2312df845eed6690381e8d44072c5f5ab96241b5d2dde # 1.3.2.Final + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: apicurio-registry-operator + image: docker.io/apicurio/apicurio-registry-operator@sha256:d9477c73b855f9821f4a8aca491407e065d34b5bdc795e3ad49a0537fd6ceeaa # 0.0.4 + imagePullPolicy: Always + name: apicurio-registry-operator + resources: + limits: + cpu: 4m + memory: 64Mi + requests: + cpu: 2m + memory: 32Mi + serviceAccountName: apicurio-registry-operator + permissions: + - rules: + - apiGroups: + - route.openshift.io + - apps.openshift.io + resources: + - routes + - routes/custom-host + - deploymentconfigs + verbs: + - '*' + - apiGroups: + - "" + - extensions + - route.openshift.io + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + - ingresses + - routes/custom-host + verbs: + - '*' + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - '*' + - apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - '*' + - apiGroups: + - apps + resourceNames: + - apicurio-registry-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - apiGroups: + - apps + resources: + - replicasets + verbs: + - get + - apiGroups: + - apicur.io + resources: + - '*' + verbs: + - '*' + - apiGroups: + - "" + resources: + - services/finalizers + verbs: + - update + serviceAccountName: apicurio-registry-operator + strategy: deployment + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: false + type: AllNamespaces + keywords: + - integration + - streaming + - messaging + - api + - schemas + - registry + - apicurio + - apicurio-registry + links: + - name: Website + url: https://www.apicur.io/ + - name: GitHub + url: https://github.com/Apicurio/apicurio-registry/ + - name: Issues + url: https://github.com/Apicurio/apicurio-registry/issues + - name: Twitter + url: https://twitter.com/Apicurio + maintainers: + - email: apicurio@lists.jboss.org + name: Apicurio + maturity: alpha + provider: + name: Apicurio + replaces: apicurio-registry.v0.0.3-v1.2.3.final + selector: {} + version: 0.0.4-v1.3.2.final + relatedImages: + - name: apicurio-registry-operator + image: docker.io/apicurio/apicurio-registry-operator@sha256:d9477c73b855f9821f4a8aca491407e065d34b5bdc795e3ad49a0537fd6ceeaa + - name: apicurio-registry-mem + image: docker.io/apicurio/apicurio-registry-mem@sha256:9e3e35e16a72c6c065b4be6c1fabcb2e9f7ed73b36600e05b1e46b4cc394413f + - name: apicurio-registry-kafka + image: docker.io/apicurio/apicurio-registry-kafka@sha256:cdac38135c67f3da808e55b16beaa4fc3cc67b846af1e64f491417d4107d60aa + - name: apicurio-registry-streams + image: docker.io/apicurio/apicurio-registry-streams@sha256:d3bfb0d13498dedd563d7b45dfa2461e560bf0fbf08ff3767254bb8a198f4845 + - name: apicurio-registry-jpa + image: docker.io/apicurio/apicurio-registry-jpa@sha256:44eeddd3562ca3ac3f5fb440e5f68880ab9b4d6caa0b34541a9178c4a039b1f4 + - name: apicurio-registry-infinispan + image: docker.io/apicurio/apicurio-registry-infinispan@sha256:4dfe0cba302f8243c5c2312df845eed6690381e8d44072c5f5ab96241b5d2dde diff --git a/staging/operator-registry/cmd/opm/alpha/bundle/build.go b/staging/operator-registry/cmd/opm/alpha/bundle/build.go index 79901e1af7..c35ce80c92 100644 --- a/staging/operator-registry/cmd/opm/alpha/bundle/build.go +++ b/staging/operator-registry/cmd/opm/alpha/bundle/build.go @@ -23,25 +23,25 @@ func newBundleBuildCmd() *cobra.Command { Use: "build", Short: "Build operator bundle image", Long: `The "opm alpha bundle build" command will generate operator - bundle metadata if needed and build bundle image with operator manifest - and metadata for a specific version. +bundle metadata if needed and build bundle image with operator manifest +and metadata for a specific version. - For example: The command will generate annotations.yaml metadata plus - Dockerfile for bundle image and then build a container image from - provided operator bundle manifests generated metadata - e.g. "quay.io/example/operator:v0.0.1". +For example: The command will generate annotations.yaml metadata plus +Dockerfile for bundle image and then build a container image from +provided operator bundle manifests generated metadata +e.g. "quay.io/example/operator:v0.0.1". - After the build process is completed, a container image would be built - locally in docker and available to push to a container registry. +After the build process is completed, a container image would be built +locally in docker and available to push to a container registry. - $ opm alpha bundle build --directory /test/0.1.0/ --tag quay.io/example/operator:v0.1.0 \ - --package test-operator --channels stable,beta --default stable --overwrite +$ opm alpha bundle build --directory /test/0.1.0/ --tag quay.io/example/operator:v0.1.0 \ + --package test-operator --channels stable,beta --default stable --overwrite - Note: - * Bundle image is not runnable. - * All manifests yaml must be in the same directory. - `, +Note: +* Bundle image is not runnable. +* All manifests yaml must be in the same directory. `, RunE: buildFunc, + Args: cobra.NoArgs, } bundleBuildCmd.Flags().StringVarP(&buildDir, "directory", "d", "", @@ -79,7 +79,7 @@ func newBundleBuildCmd() *cobra.Command { return bundleBuildCmd } -func buildFunc(cmd *cobra.Command, args []string) error { +func buildFunc(cmd *cobra.Command, _ []string) error { return bundle.BuildFunc( buildDir, outputDir, diff --git a/staging/operator-registry/cmd/opm/alpha/bundle/cmd.go b/staging/operator-registry/cmd/opm/alpha/bundle/cmd.go index bdd95bf13a..c318db6beb 100644 --- a/staging/operator-registry/cmd/opm/alpha/bundle/cmd.go +++ b/staging/operator-registry/cmd/opm/alpha/bundle/cmd.go @@ -9,6 +9,7 @@ func NewCmd() *cobra.Command { Use: "bundle", Short: "Operator bundle commands", Long: `Generate operator bundle metadata and build bundle image.`, + Args: cobra.NoArgs, } runCmd.AddCommand(newBundleGenerateCmd()) diff --git a/staging/operator-registry/cmd/opm/alpha/bundle/extract.go b/staging/operator-registry/cmd/opm/alpha/bundle/extract.go index ba8b716266..d07273b3c4 100644 --- a/staging/operator-registry/cmd/opm/alpha/bundle/extract.go +++ b/staging/operator-registry/cmd/opm/alpha/bundle/extract.go @@ -14,7 +14,7 @@ var extractCmd = &cobra.Command{ Short: "Extracts the data in a bundle directory via ConfigMap", Long: "Extract takes as input a directory containing manifests and writes the per file contents to a ConfipMap", - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -22,6 +22,7 @@ var extractCmd = &cobra.Command{ }, RunE: runExtractCmd, + Args: cobra.NoArgs, } func init() { @@ -35,7 +36,7 @@ func init() { extractCmd.MarkPersistentFlagRequired("configmapname") } -func runExtractCmd(cmd *cobra.Command, args []string) error { +func runExtractCmd(cmd *cobra.Command, _ []string) error { manifestsDir, err := cmd.Flags().GetString("manifestsdir") if err != nil { return err diff --git a/staging/operator-registry/cmd/opm/alpha/bundle/generate.go b/staging/operator-registry/cmd/opm/alpha/bundle/generate.go index 1aaa092321..417a22c379 100644 --- a/staging/operator-registry/cmd/opm/alpha/bundle/generate.go +++ b/staging/operator-registry/cmd/opm/alpha/bundle/generate.go @@ -14,15 +14,15 @@ func newBundleGenerateCmd() *cobra.Command { Use: "generate", Short: "Generate operator bundle metadata and Dockerfile", Long: `The "opm alpha bundle generate" command will generate operator - bundle metadata if needed and a Dockerfile to build Operator bundle image. +bundle metadata if needed and a Dockerfile to build Operator bundle image. - $ opm alpha bundle generate --directory /test/0.1.0/ --package test-operator \ - --channels stable,beta --default stable +$ opm alpha bundle generate --directory /test/0.1.0/ --package test-operator \ + --channels stable,beta --default stable - Note: - * All manifests yaml must be in the same directory. - `, +Note: +* All manifests yaml must be in the same directory.`, RunE: generateFunc, + Args: cobra.NoArgs, } bundleGenerateCmd.Flags().StringVarP(&buildDir, "directory", "d", "", @@ -48,7 +48,7 @@ func newBundleGenerateCmd() *cobra.Command { return bundleGenerateCmd } -func generateFunc(cmd *cobra.Command, args []string) error { +func generateFunc(cmd *cobra.Command, _ []string) error { return bundle.GenerateFunc( buildDir, outputDir, diff --git a/staging/operator-registry/cmd/opm/alpha/bundle/validate.go b/staging/operator-registry/cmd/opm/alpha/bundle/validate.go index 4e32503bc9..9406e79a3f 100644 --- a/staging/operator-registry/cmd/opm/alpha/bundle/validate.go +++ b/staging/operator-registry/cmd/opm/alpha/bundle/validate.go @@ -38,6 +38,7 @@ Optional validators. These validators are disabled by default and can be enabled See https://olm.operatorframework.io/docs/tasks/validate-package/#validation for more info.`, Example: `$ opm alpha bundle validate --tag quay.io/test/test-operator:latest --image-builder docker`, RunE: validateFunc, + Args: cobra.NoArgs, } bundleValidateCmd.Flags().StringVarP(&tag, "tag", "t", "", @@ -52,7 +53,7 @@ See https://olm.operatorframework.io/docs/tasks/validate-package/#validation for return bundleValidateCmd } -func validateFunc(cmd *cobra.Command, args []string) error { +func validateFunc(cmd *cobra.Command, _ []string) error { logger := log.WithFields(log.Fields{"container-tool": containerTool}) log.SetLevel(log.DebugLevel) diff --git a/staging/operator-registry/cmd/opm/alpha/cmd.go b/staging/operator-registry/cmd/opm/alpha/cmd.go index 78335f76b7..7afed8b098 100644 --- a/staging/operator-registry/cmd/opm/alpha/cmd.go +++ b/staging/operator-registry/cmd/opm/alpha/cmd.go @@ -14,6 +14,7 @@ func NewCmd() *cobra.Command { Hidden: true, Use: "alpha", Short: "Run an alpha subcommand", + Args: cobra.NoArgs, } runCmd.AddCommand( diff --git a/staging/operator-registry/cmd/opm/alpha/diff/cmd.go b/staging/operator-registry/cmd/opm/alpha/diff/cmd.go index f1a83f759e..752e4d343a 100644 --- a/staging/operator-registry/cmd/opm/alpha/diff/cmd.go +++ b/staging/operator-registry/cmd/opm/alpha/diff/cmd.go @@ -24,9 +24,11 @@ const ( ) type diff struct { - oldRefs []string - newRefs []string - skipDeps bool + oldRefs []string + newRefs []string + skipDeps bool + includeAdditive bool + includeFile string output string caFile string @@ -35,6 +37,18 @@ type diff struct { logger *logrus.Entry } +// Example include file needs to be formatted separately so indentation is not messed up. +var includeFileExample = fmt.Sprintf(`packages: +%[1]s- name: foo +%[1]s- name: bar +%[1]s channels: +%[1]s - name: stable +%[1]s- name: baz +%[1]s channels: +%[1]s - name: alpha +%[1]s versions: +%[1]s - 0.2.0-alpha.0`, templates.Indentation) + func NewCmd() *cobra.Command { a := diff{ logger: logrus.NewEntry(logrus.New()), @@ -43,32 +57,28 @@ func NewCmd() *cobra.Command { Use: "diff [old-refs]... new-refs...", Short: "Diff old and new catalog references into a declarative config", Long: templates.LongDesc(` -Diff a set of old and new catalog references ("refs") to produce a -declarative config containing only packages channels, and versions not present -in the old set, and versions that differ between the old and new sets. This is known as "latest" mode. - -These references are passed through 'opm render' to produce a single declarative config. -Bundle image refs are not supported directly; a valid "olm.package" declarative config object -referring to the bundle's package must exist in all input refs. - -This command has special behavior when old-refs are omitted, called "heads-only" mode: -instead of the output being that of 'opm render refs...' -(which would be the case given the preceding behavior description), -only the channel heads of all channels in all packages are included in the output, -and dependencies. Dependencies are assumed to be provided by either an old ref, -in which case they are not included in the diff, or a new ref, in which -case they are included. Dependencies provided by some catalog unknown to -'opm alpha diff' will not cause the command to error, but an error will occur -if that catalog is not serving these dependencies at runtime. -Dependency inclusion can be turned off with --no-deps, although this is not recommended -unless you are certain some in-cluster catalog satisfies all dependencies. +'diff' returns a declarative config containing packages, channels, and versions +from new-refs, optionally removing those in old-refs or those omitted by an include config file. + +Each set of refs is passed to 'opm render ' to produce a single, normalized delcarative config. + +Depending on what arguments are provided to the command, a particular "mode" is invoked to produce a diff: -NOTE: for now, if any dependency exists, the entire dependency's package is added to the diff. -In the future, these packages will be pruned such that only the latest dependencies -satisfying a package version range or GVK, and their upgrade graph(s) to their latest -channel head(s), are included in the diff. +- If in heads-only mode (old-refs is not specified), then the heads of channels in new-refs are added to the output. +- If in latest mode (old-refs is specified), a diff between old-refs and new-refs is added to the output. +- If --include-file is set, items from that file will be added to the diff: + - If --include-additive is false (the default), a diff will be generated only on those objects, depending on the mode. + - If --include-additive is true, the diff will contain included objects, plus those added by the mode's invocation. + +Dependencies are added in all modes if --skip-deps is false (the default). +Dependencies are assumed to be provided by either an old-ref, in which case they are not included in the diff, +or a new-ref, in which case they are included. +Dependencies provided by some catalog unknown to 'diff' will not cause the command to error, +but an error will occur if that catalog is not serving these dependencies at runtime. +While dependency inclusion can be turned off with --skip-deps, doing so is not recommended +unless you are certain some in-cluster catalog satisfies all dependencies. `), - Example: templates.Examples(` + Example: fmt.Sprintf(templates.Examples(` # Create a directory for your declarative config diff. mkdir -p my-catalog-index @@ -81,13 +91,27 @@ opm alpha diff registry.org/my-catalog:abc123 registry.org/my-catalog:def456 -o # Create a new catalog from the heads of an existing catalog. opm alpha diff registry.org/my-catalog:def456 -o yaml > my-catalog-index/index.yaml +# OR: +# Only include all of package "foo", package "bar" channel "stable", +# and package "baz" channel "alpha" version "0.2.0-alpha.0" (and its upgrade graph) in the diff. +cat < include.yaml +%s +EOF +opm alpha diff registry.org/my-catalog:def456 -i include.yaml -o yaml > pruned-index/index.yaml + +# OR: +# Include all of package "foo", package "bar" channel "stable", +# and package "baz" channel "alpha" version "0.2.0-alpha.0" in the diff +# on top of heads of all other channels in all packages (using the above include.yaml). +opm alpha diff registry.org/my-catalog:def456 -i include.yaml --include-additive -o yaml > pruned-index/index.yaml + # FINALLY: # Build an index image containing the diff-ed declarative config, # then tag and push it. opm alpha generate dockerfile ./my-catalog-index docker build -t registry.org/my-catalog:diff-latest -f index.Dockerfile . docker push registry.org/my-catalog:diff-latest -`), +`), includeFileExample), Args: cobra.RangeArgs(1, 2), PreRunE: func(cmd *cobra.Command, args []string) error { if a.debug { @@ -102,7 +126,12 @@ docker push registry.org/my-catalog:diff-latest cmd.Flags().BoolVar(&a.skipDeps, "skip-deps", false, "do not include bundle dependencies in the output catalog") cmd.Flags().StringVarP(&a.output, "output", "o", "yaml", "Output format (json|yaml)") - cmd.Flags().StringVarP(&a.caFile, "ca-file", "", "", "the root Certificates to use with this command") + cmd.Flags().StringVar(&a.caFile, "ca-file", "", "the root Certificates to use with this command") + cmd.Flags().StringVarP(&a.includeFile, "include-file", "i", "", + "YAML defining packages, channels, and/or bundles/versions to extract from the new refs. "+ + "Upgrade graphs from individual bundles/versions to their channel's head are also included") + cmd.Flags().BoolVar(&a.includeAdditive, "include-additive", false, + "Ref objects from --include-file are returned on top of 'heads-only' or 'latest' output") cmd.Flags().BoolVar(&a.debug, "debug", false, "enable debug logging") return cmd @@ -111,9 +140,8 @@ docker push registry.org/my-catalog:diff-latest func (a *diff) addFunc(cmd *cobra.Command, args []string) error { a.parseArgs(args) - skipTLS, err := cmd.Flags().GetBool("skip-tls") - if err != nil { - logrus.Panic(err) + if cmd.Flags().Changed("include-additive") && a.includeFile == "" { + a.logger.Fatal("must set --include-file if --include-additive is set") } var write func(declcfg.DeclarativeConfig, io.Writer) error @@ -126,6 +154,10 @@ func (a *diff) addFunc(cmd *cobra.Command, args []string) error { return fmt.Errorf("invalid --output value: %q", a.output) } + skipTLS, err := cmd.Flags().GetBool("skip-tls") + if err != nil { + logrus.Panic(err) + } rootCAs, err := certs.RootCAs(a.caFile) if err != nil { a.logger.Fatalf("error getting root CAs: %v", err) @@ -140,16 +172,33 @@ func (a *diff) addFunc(cmd *cobra.Command, args []string) error { } }() + diff := action.Diff{ + Registry: reg, + OldRefs: a.oldRefs, + NewRefs: a.newRefs, + SkipDependencies: a.skipDeps, + IncludeAdditively: a.includeAdditive, + Logger: a.logger, + } + + if a.includeFile != "" { + f, err := os.Open(a.includeFile) + if err != nil { + a.logger.Fatalf("error opening include file: %v", err) + } + defer func() { + if cerr := f.Close(); cerr != nil { + a.logger.Error(cerr) + } + }() + if diff.IncludeConfig, err = action.LoadDiffIncludeConfig(f); err != nil { + a.logger.Fatalf("error loading include file: %v", err) + } + } + ctx, cancel := context.WithTimeout(context.TODO(), timeout) defer cancel() - diff := action.Diff{ - Registry: reg, - OldRefs: a.oldRefs, - NewRefs: a.newRefs, - SkipDependencies: a.skipDeps, - Logger: a.logger, - } cfg, err := diff.Run(ctx) if err != nil { a.logger.Fatalf("error generating diff: %v", err) diff --git a/staging/operator-registry/cmd/opm/alpha/generate/cmd.go b/staging/operator-registry/cmd/opm/alpha/generate/cmd.go index 092f2310af..7871a7f52f 100644 --- a/staging/operator-registry/cmd/opm/alpha/generate/cmd.go +++ b/staging/operator-registry/cmd/opm/alpha/generate/cmd.go @@ -44,7 +44,7 @@ When specifying extra labels, note that if duplicate keys exist, only the last value of each duplicate key will be added to the generated Dockerfile. `, RunE: func(_ *cobra.Command, args []string) error { - fromDir := args[0] + fromDir := filepath.Clean(args[0]) extraLabels, err := parseLabels(extraLabelStrs) if err != nil { diff --git a/staging/operator-registry/cmd/opm/index/add.go b/staging/operator-registry/cmd/opm/index/add.go index 480cd9acac..ea218d6e04 100644 --- a/staging/operator-registry/cmd/opm/index/add.go +++ b/staging/operator-registry/cmd/opm/index/add.go @@ -45,13 +45,14 @@ func addIndexAddCmd(parent *cobra.Command) { Use: "add", Short: "Add operator bundles to an index.", Long: addLong, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } return nil }, RunE: runIndexAddCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") @@ -89,7 +90,7 @@ func addIndexAddCmd(parent *cobra.Command) { } -func runIndexAddCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexAddCmdFunc(cmd *cobra.Command, _ []string) error { generate, err := cmd.Flags().GetBool("generate") if err != nil { return err diff --git a/staging/operator-registry/cmd/opm/index/cmd.go b/staging/operator-registry/cmd/opm/index/cmd.go index 44141415b9..b7cdd14d15 100644 --- a/staging/operator-registry/cmd/opm/index/cmd.go +++ b/staging/operator-registry/cmd/opm/index/cmd.go @@ -16,18 +16,19 @@ func AddCommand(parent *cobra.Command) { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } return nil }, - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRun: func(cmd *cobra.Command, _ []string) { sqlite.LogSqliteDeprecation() if skipTLS, err := cmd.Flags().GetBool("skip-tls"); err == nil && skipTLS { logrus.Warn("--skip-tls flag is set: this mode is insecure and meant for development purposes only.") } }, + Args: cobra.NoArgs, } parent.AddCommand(cmd) diff --git a/staging/operator-registry/cmd/opm/index/delete.go b/staging/operator-registry/cmd/opm/index/delete.go index 9b249e2021..c9472b8ed2 100644 --- a/staging/operator-registry/cmd/opm/index/delete.go +++ b/staging/operator-registry/cmd/opm/index/delete.go @@ -17,7 +17,7 @@ func newIndexDeleteCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -25,6 +25,7 @@ func newIndexDeleteCmd() *cobra.Command { }, RunE: runIndexDeleteCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") @@ -53,7 +54,7 @@ func newIndexDeleteCmd() *cobra.Command { } -func runIndexDeleteCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexDeleteCmdFunc(cmd *cobra.Command, _ []string) error { generate, err := cmd.Flags().GetBool("generate") if err != nil { return err diff --git a/staging/operator-registry/cmd/opm/index/deprecatetruncate.go b/staging/operator-registry/cmd/opm/index/deprecatetruncate.go index dc76e9ab9c..9324d35b8e 100644 --- a/staging/operator-registry/cmd/opm/index/deprecatetruncate.go +++ b/staging/operator-registry/cmd/opm/index/deprecatetruncate.go @@ -37,13 +37,14 @@ func newIndexDeprecateTruncateCmd() *cobra.Command { Use: "deprecatetruncate", Short: "Deprecate and truncate operator bundles from an index.", Long: deprecateLong, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } return nil }, RunE: runIndexDeprecateTruncateCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") @@ -68,7 +69,7 @@ func newIndexDeprecateTruncateCmd() *cobra.Command { return indexCmd } -func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, _ []string) error { generate, err := cmd.Flags().GetBool("generate") if err != nil { return err diff --git a/staging/operator-registry/cmd/opm/index/export.go b/staging/operator-registry/cmd/opm/index/export.go index 910b6d02e2..f4e32672e7 100644 --- a/staging/operator-registry/cmd/opm/index/export.go +++ b/staging/operator-registry/cmd/opm/index/export.go @@ -27,7 +27,7 @@ func newIndexExportCmd() *cobra.Command { Short: "Export an operator from an index into the appregistry format", Long: exportLong, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -35,6 +35,7 @@ func newIndexExportCmd() *cobra.Command { }, RunE: runIndexExportCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") indexCmd.Flags().StringP("index", "i", "", "index to get package from") @@ -58,7 +59,7 @@ func newIndexExportCmd() *cobra.Command { } -func runIndexExportCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexExportCmdFunc(cmd *cobra.Command, _ []string) error { index, err := cmd.Flags().GetString("index") if err != nil { return err diff --git a/staging/operator-registry/cmd/opm/index/prune.go b/staging/operator-registry/cmd/opm/index/prune.go index 5cee902794..c80ebf6198 100644 --- a/staging/operator-registry/cmd/opm/index/prune.go +++ b/staging/operator-registry/cmd/opm/index/prune.go @@ -19,7 +19,7 @@ func newIndexPruneCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -27,6 +27,7 @@ func newIndexPruneCmd() *cobra.Command { }, RunE: runIndexPruneCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") @@ -53,7 +54,7 @@ func newIndexPruneCmd() *cobra.Command { } -func runIndexPruneCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexPruneCmdFunc(cmd *cobra.Command, _ []string) error { generate, err := cmd.Flags().GetBool("generate") if err != nil { return err diff --git a/staging/operator-registry/cmd/opm/index/prunestranded.go b/staging/operator-registry/cmd/opm/index/prunestranded.go index c323c619a3..03e739cff5 100644 --- a/staging/operator-registry/cmd/opm/index/prunestranded.go +++ b/staging/operator-registry/cmd/opm/index/prunestranded.go @@ -19,7 +19,7 @@ func newIndexPruneStrandedCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -27,6 +27,7 @@ func newIndexPruneStrandedCmd() *cobra.Command { }, RunE: runIndexPruneStrandedCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") @@ -48,7 +49,7 @@ func newIndexPruneStrandedCmd() *cobra.Command { } -func runIndexPruneStrandedCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexPruneStrandedCmdFunc(cmd *cobra.Command, _ []string) error { generate, err := cmd.Flags().GetBool("generate") if err != nil { return err diff --git a/staging/operator-registry/cmd/opm/migrate/cmd.go b/staging/operator-registry/cmd/opm/migrate/cmd.go new file mode 100644 index 0000000000..6405060b0a --- /dev/null +++ b/staging/operator-registry/cmd/opm/migrate/cmd.go @@ -0,0 +1,54 @@ +package migrate + +import ( + "log" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/operator-framework/operator-registry/alpha/action" + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/pkg/sqlite" +) + +func NewCmd() *cobra.Command { + var ( + migrate action.Migrate + output string + ) + cmd := &cobra.Command{ + Use: "migrate ", + Short: "Migrate a sqlite-based index image or database file to a file-based catalog", + Long: `Migrate a sqlite-based index image or database file to a file-based catalog. + +` + sqlite.DeprecationMessage, + Args: cobra.ExactArgs(2), + PersistentPreRun: func(_ *cobra.Command, _ []string) { + sqlite.LogSqliteDeprecation() + }, + RunE: func(cmd *cobra.Command, args []string) error { + migrate.CatalogRef = args[0] + migrate.OutputDir = args[1] + + switch output { + case "yaml": + migrate.WriteFunc = declcfg.WriteYAML + migrate.FileExt = ".yaml" + case "json": + migrate.WriteFunc = declcfg.WriteJSON + migrate.FileExt = ".json" + default: + log.Fatalf("invalid --output value %q, expected (json|yaml)", output) + } + + logrus.Infof("rendering index %q as file-based catalog", migrate.CatalogRef) + if err := migrate.Run(cmd.Context()); err != nil { + logrus.New().Fatal(err) + } + logrus.Infof("wrote rendered file-based catalog to %q\n", migrate.OutputDir) + return nil + }, + } + cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)") + return cmd +} diff --git a/staging/operator-registry/cmd/opm/registry/add.go b/staging/operator-registry/cmd/opm/registry/add.go index 0e7a399ef0..ceb3fcd1ad 100644 --- a/staging/operator-registry/cmd/opm/registry/add.go +++ b/staging/operator-registry/cmd/opm/registry/add.go @@ -21,7 +21,7 @@ func newRegistryAddCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -29,6 +29,7 @@ func newRegistryAddCmd() *cobra.Command { }, RunE: addFunc, + Args: cobra.NoArgs, } rootCmd.Flags().Bool("debug", false, "enable debug logging") @@ -39,11 +40,18 @@ func newRegistryAddCmd() *cobra.Command { rootCmd.Flags().String("ca-file", "", "the root certificates to use when --container-tool=none; see docker/podman docs for certificate loading instructions") rootCmd.Flags().StringP("mode", "", "replaces", "graph update mode that defines how channel graphs are updated. One of: [replaces, semver, semver-skippatch]") rootCmd.Flags().StringP("container-tool", "c", "none", "tool to interact with container images (save, build, etc.). One of: [none, docker, podman]") - + rootCmd.Flags().Bool("overwrite-latest", false, "overwrite the latest bundles (channel heads) with those of the same csv name given by --bundles") + if err := rootCmd.Flags().MarkHidden("overwrite-latest"); err != nil { + logrus.Panic(err.Error()) + } + rootCmd.Flags().Bool("enable-alpha", false, "enable unsupported alpha features of the OPM CLI") + if err := rootCmd.Flags().MarkHidden("enable-alpha"); err != nil { + logrus.Panic(err.Error()) + } return rootCmd } -func addFunc(cmd *cobra.Command, args []string) error { +func addFunc(cmd *cobra.Command, _ []string) error { permissive, err := cmd.Flags().GetBool("permissive") if err != nil { return err @@ -77,6 +85,15 @@ func addFunc(cmd *cobra.Command, args []string) error { if err != nil { return err } + overwrite, err := cmd.Flags().GetBool("overwrite-latest") + if err != nil { + return err + } + + enableAlpha, err := cmd.Flags().GetBool("enable-alpha") + if err != nil { + return err + } if caFile != "" { if skipTLS { @@ -96,7 +113,8 @@ func addFunc(cmd *cobra.Command, args []string) error { Bundles: bundleImages, Mode: modeEnum, ContainerTool: containerTool, - Overwrite: false, + Overwrite: overwrite, + EnableAlpha: enableAlpha, } logger := logrus.WithFields(logrus.Fields{"bundles": bundleImages}) diff --git a/staging/operator-registry/cmd/opm/registry/cmd.go b/staging/operator-registry/cmd/opm/registry/cmd.go index 15019f9c09..f4b058bc8f 100644 --- a/staging/operator-registry/cmd/opm/registry/cmd.go +++ b/staging/operator-registry/cmd/opm/registry/cmd.go @@ -18,12 +18,13 @@ func NewOpmRegistryCmd() *cobra.Command { PersistentPreRun: func(_ *cobra.Command, _ []string) { sqlite.LogSqliteDeprecation() }, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } return nil }, + Args: cobra.NoArgs, } rootCmd.AddCommand(newRegistryServeCmd()) @@ -31,6 +32,7 @@ func NewOpmRegistryCmd() *cobra.Command { rootCmd.AddCommand(newRegistryRmCmd()) rootCmd.AddCommand(newRegistryPruneCmd()) rootCmd.AddCommand(newRegistryPruneStrandedCmd()) + rootCmd.AddCommand(newRegistryDeprecateCmd()) return rootCmd } diff --git a/staging/operator-registry/cmd/opm/registry/deprecatetruncate.go b/staging/operator-registry/cmd/opm/registry/deprecatetruncate.go new file mode 100644 index 0000000000..65eb66278c --- /dev/null +++ b/staging/operator-registry/cmd/opm/registry/deprecatetruncate.go @@ -0,0 +1,76 @@ +package registry + +import ( + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/operator-framework/operator-registry/pkg/lib/registry" + "github.com/operator-framework/operator-registry/pkg/sqlite" +) + +func newRegistryDeprecateCmd() *cobra.Command { + cmd := &cobra.Command{ + Hidden: true, + Use: "deprecatetruncate", + Short: "deprecate operator bundle from registry DB", + Long: `deprecate operator bundle from registry DB + +` + sqlite.DeprecationMessage, + + PreRunE: func(cmd *cobra.Command, _ []string) error { + if debug, _ := cmd.Flags().GetBool("debug"); debug { + logrus.SetLevel(logrus.DebugLevel) + } + return nil + }, + + RunE: deprecateFunc, + Args: cobra.NoArgs, + } + + cmd.Flags().Bool("debug", false, "enable debug logging") + cmd.Flags().StringP("database", "d", "index.db", "relative path to database file") + cmd.Flags().StringSliceP("bundle-images", "b", []string{}, "comma separated list of links to bundle image") + cmd.Flags().Bool("permissive", false, "allow registry load errors") + cmd.Flags().Bool("allow-package-removal", false, "removes the entire package if the heads of all channels in the package are deprecated") + + return cmd +} + +func deprecateFunc(cmd *cobra.Command, _ []string) error { + permissive, err := cmd.Flags().GetBool("permissive") + if err != nil { + return err + } + fromFilename, err := cmd.Flags().GetString("database") + if err != nil { + return err + } + bundleImages, err := cmd.Flags().GetStringSlice("bundle-images") + if err != nil { + return err + } + allowPackageRemoval, err := cmd.Flags().GetBool("allow-package-removal") + if err != nil { + return err + } + + request := registry.DeprecateFromRegistryRequest{ + Permissive: permissive, + InputDatabase: fromFilename, + Bundles: bundleImages, + AllowPackageRemoval: allowPackageRemoval, + } + + logger := logrus.WithFields(logrus.Fields{"bundles": bundleImages}) + + logger.Info("deprecating from registry") + + registryDeprecator := registry.NewRegistryDeprecator(logger) + + err = registryDeprecator.DeprecateFromRegistry(request) + if err != nil { + logger.Fatal(err) + } + return nil +} diff --git a/staging/operator-registry/cmd/opm/registry/mirror.go b/staging/operator-registry/cmd/opm/registry/mirror.go index cda5d030e4..5beae77925 100644 --- a/staging/operator-registry/cmd/opm/registry/mirror.go +++ b/staging/operator-registry/cmd/opm/registry/mirror.go @@ -40,6 +40,7 @@ func MirrorCmd() *cobra.Command { } return nil }, + Args: cobra.ExactArgs(2), } flags := cmd.Flags() diff --git a/staging/operator-registry/cmd/opm/registry/prune.go b/staging/operator-registry/cmd/opm/registry/prune.go index 05bbdd712c..0160e6bd20 100644 --- a/staging/operator-registry/cmd/opm/registry/prune.go +++ b/staging/operator-registry/cmd/opm/registry/prune.go @@ -16,7 +16,7 @@ func newRegistryPruneCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -24,6 +24,7 @@ func newRegistryPruneCmd() *cobra.Command { }, RunE: runRegistryPruneCmdFunc, + Args: cobra.NoArgs, } rootCmd.Flags().Bool("debug", false, "enable debug logging") @@ -37,7 +38,7 @@ func newRegistryPruneCmd() *cobra.Command { return rootCmd } -func runRegistryPruneCmdFunc(cmd *cobra.Command, args []string) error { +func runRegistryPruneCmdFunc(cmd *cobra.Command, _ []string) error { fromFilename, err := cmd.Flags().GetString("database") if err != nil { return err diff --git a/staging/operator-registry/cmd/opm/registry/prunestranded.go b/staging/operator-registry/cmd/opm/registry/prunestranded.go index a3ff44a0f3..800272a2f2 100644 --- a/staging/operator-registry/cmd/opm/registry/prunestranded.go +++ b/staging/operator-registry/cmd/opm/registry/prunestranded.go @@ -16,7 +16,7 @@ func newRegistryPruneStrandedCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -24,6 +24,7 @@ func newRegistryPruneStrandedCmd() *cobra.Command { }, RunE: runRegistryPruneStrandedCmdFunc, + Args: cobra.NoArgs, } rootCmd.Flags().Bool("debug", false, "enable debug logging") @@ -32,7 +33,7 @@ func newRegistryPruneStrandedCmd() *cobra.Command { return rootCmd } -func runRegistryPruneStrandedCmdFunc(cmd *cobra.Command, args []string) error { +func runRegistryPruneStrandedCmdFunc(cmd *cobra.Command, _ []string) error { fromFilename, err := cmd.Flags().GetString("database") if err != nil { return err diff --git a/staging/operator-registry/cmd/opm/registry/rm.go b/staging/operator-registry/cmd/opm/registry/rm.go index 17a4a51672..ba884ca8d9 100644 --- a/staging/operator-registry/cmd/opm/registry/rm.go +++ b/staging/operator-registry/cmd/opm/registry/rm.go @@ -16,7 +16,7 @@ func newRegistryRmCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -24,6 +24,7 @@ func newRegistryRmCmd() *cobra.Command { }, RunE: rmFunc, + Args: cobra.NoArgs, } rootCmd.Flags().Bool("debug", false, "enable debug logging") @@ -37,7 +38,7 @@ func newRegistryRmCmd() *cobra.Command { return rootCmd } -func rmFunc(cmd *cobra.Command, args []string) error { +func rmFunc(cmd *cobra.Command, _ []string) error { fromFilename, err := cmd.Flags().GetString("database") if err != nil { return err diff --git a/staging/operator-registry/cmd/opm/registry/serve.go b/staging/operator-registry/cmd/opm/registry/serve.go index 794553fa4d..b6defb75ee 100644 --- a/staging/operator-registry/cmd/opm/registry/serve.go +++ b/staging/operator-registry/cmd/opm/registry/serve.go @@ -32,7 +32,7 @@ func newRegistryServeCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -40,6 +40,7 @@ func newRegistryServeCmd() *cobra.Command { }, RunE: serveFunc, + Args: cobra.NoArgs, } rootCmd.Flags().Bool("debug", false, "enable debug logging") @@ -52,7 +53,7 @@ func newRegistryServeCmd() *cobra.Command { return rootCmd } -func serveFunc(cmd *cobra.Command, args []string) error { +func serveFunc(cmd *cobra.Command, _ []string) error { // Immediately set up termination log terminationLogPath, err := cmd.Flags().GetString("termination-log") if err != nil { diff --git a/staging/operator-registry/cmd/opm/root/cmd.go b/staging/operator-registry/cmd/opm/root/cmd.go index 7b07ccbc75..1b00fecc01 100644 --- a/staging/operator-registry/cmd/opm/root/cmd.go +++ b/staging/operator-registry/cmd/opm/root/cmd.go @@ -7,6 +7,7 @@ import ( "github.com/operator-framework/operator-registry/cmd/opm/alpha" "github.com/operator-framework/operator-registry/cmd/opm/index" initcmd "github.com/operator-framework/operator-registry/cmd/opm/init" + "github.com/operator-framework/operator-registry/cmd/opm/migrate" "github.com/operator-framework/operator-registry/cmd/opm/registry" "github.com/operator-framework/operator-registry/cmd/opm/render" "github.com/operator-framework/operator-registry/cmd/opm/serve" @@ -19,15 +20,16 @@ func NewCmd() *cobra.Command { Use: "opm", Short: "operator package manager", Long: "CLI to interact with operator-registry and build indexes of operator content", - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } return nil }, + Args: cobra.NoArgs, } - cmd.AddCommand(registry.NewOpmRegistryCmd(), alpha.NewCmd(), initcmd.NewCmd(), serve.NewCmd(), render.NewCmd(), validate.NewCmd()) + cmd.AddCommand(registry.NewOpmRegistryCmd(), alpha.NewCmd(), initcmd.NewCmd(), migrate.NewCmd(), serve.NewCmd(), render.NewCmd(), validate.NewCmd()) index.AddCommand(cmd) version.AddCommand(cmd) diff --git a/staging/operator-registry/cmd/opm/serve/serve.go b/staging/operator-registry/cmd/opm/serve/serve.go index 0549262221..6a075c07ef 100644 --- a/staging/operator-registry/cmd/opm/serve/serve.go +++ b/staging/operator-registry/cmd/opm/serve/serve.go @@ -87,7 +87,11 @@ func (s *serve) run(ctx context.Context) error { if err != nil { return fmt.Errorf("could not build index model from declarative config: %v", err) } - store := registry.NewQuerier(m) + store, err := registry.NewQuerier(m) + defer store.Close() + if err != nil { + return err + } lis, err := net.Listen("tcp", ":"+s.port) if err != nil { diff --git a/staging/operator-registry/docs/contributors/e2e_tests.md b/staging/operator-registry/docs/contributors/e2e_tests.md index 5b683a768f..a0eb50eedc 100644 --- a/staging/operator-registry/docs/contributors/e2e_tests.md +++ b/staging/operator-registry/docs/contributors/e2e_tests.md @@ -19,13 +19,13 @@ running even after the test suite has completed. 1. Start the e2e tests: ```bash - DOCKER_REGISTRY_HOST=localhost:5000 make build e2e SKIPTLS="true" CLUSTER=kind + DOCKER_REGISTRY_HOST=localhost:5000 GOENV='GOOS=linux' make build e2e SKIPTLS="true" CLUSTER=kind ``` 1. Run a specific BDD test using the `TEST` argument to make. Note that this argument uses regular expressions. ```bash - DOCKER_REGISTRY_HOST=localhost:5000 make build e2e TEST='builds and manipulates bundle and index images' SKIPTLS="true" CLUSTER=kind + DOCKER_REGISTRY_HOST=localhost:5000 GOENV='GOOS=linux' make build e2e TEST='builds and manipulates bundle and index images' SKIPTLS="true" CLUSTER=kind ``` 1. If you want a quick way to ensure that your TEST regex argument will work, you can bypass the @@ -46,13 +46,13 @@ make file and use `-dryRun` with `-focus` and see if the regex would trigger you 1. Start the e2e tests: ```bash - DOCKER_REGISTRY_HOST=localhost:443 make build e2e CLUSTER=kind + DOCKER_REGISTRY_HOST=localhost:443 GOENV='GOOS=linux' make build e2e CLUSTER=kind ``` 1. Run a specific BDD test using the `TEST` argument to make. Note that this argument uses regular expressions. ```bash - DOCKER_REGISTRY_HOST=localhost:443 make build e2e TEST='builds and manipulates bundle and index images' CLUSTER=kind + DOCKER_REGISTRY_HOST=localhost:443 GOENV='GOOS=linux' make build e2e TEST='builds and manipulates bundle and index images' CLUSTER=kind ``` 1. If you want a quick way to ensure that your TEST regex argument will work, you can bypass the diff --git a/staging/operator-registry/docs/contributors/releases.md b/staging/operator-registry/docs/contributors/releases.md index 5dcac082ed..152d3ea8f0 100644 --- a/staging/operator-registry/docs/contributors/releases.md +++ b/staging/operator-registry/docs/contributors/releases.md @@ -2,7 +2,7 @@ ## opm -Releases of opm are built by Github Actions, see the [release.yml](../../.github/workflows/release.yml) for details. +Binary releases of opm are built by Github Actions, see [release.yaml](../../.github/workflows/release.yaml) for details. amd64 builds are produced for linux, macos, and windows. opm follows semantic versioning, with the latest version derived from the newest semver tag. @@ -11,17 +11,35 @@ opm follows semantic versioning, with the latest version derived from the newest Releases are triggered via tags. Make a new release by tagging a commit with an appropriate semver tag. +```console +$ git tag -a -m "operator-registry vX.Y.Z" vX.Y.Z +$ git push upstream vX.Y.Z +``` + ## Checking the build Builds for a release can be found [on GitHub Actions](https://github.com/operator-framework/operator-registry/actions). After triggering a build, watch for logs. If the build is successful, a new [release](https://github.com/operator-framework/operator-registry/releases) should appear in GitHub. It will be a draft release, so once all the artifacts are available you need to edit the release to publish the draft. ## Docker images -Builds are also triggered for the following docker images. The tags in Quay.io will match the git tag: +The primary image produced from this repository is [quay.io/operator-framework/opm](https://quay.io/repository/operator-framework/opm). See [goreleaser.yaml](../../.github/workflows/goreleaser.yaml) for details. The following tagging system is used for this image: + - `:master` - tracks this repository's `master` branch. + - `:latest` - tracks the highest semver tag in the repository. + - `vX` - tracks the highest semver tag with major version `X`. + - `vX.Y` - tracks the highest semver tag with the major/minor version `X.Y`. + - `vX.Y.Z` - pushed on every non-prerelease semver tag. + +For each of the appropriate tags, the build configuration produces images and a manifest list for the following platforms: + - `linux/amd64` + - `linux/arm64` + - `linux/s390x` + - `linux/ppc64le` + +Other deprecated images are also built via Quay triggers. The tags in Quay.io will match the git tag, and for these images, `:latest` tracks the `master` branch: - [quay.io/operator-framework/operator-registry-server](https://quay.io/repository/operator-framework/operator-registry-server) - [quay.io/operator-framework/configmap-operator-registry](https://quay.io/repository/operator-framework/configmap-operator-registry) - [quay.io/operator-framework/upstream-registry-builder](https://quay.io/repository/operator-framework/upstream-registry-builder?tab=tags) - Images are also built to track master with `latest` tags. It is recommended that you always pull by digest, and only use images that are tagged with a version. - +It is recommended that you always pull by digest, and only use images that are tagged with a version. + diff --git a/staging/operator-registry/go.mod b/staging/operator-registry/go.mod index 94d1d1bbc6..5fcabb4633 100644 --- a/staging/operator-registry/go.mod +++ b/staging/operator-registry/go.mod @@ -4,10 +4,10 @@ go 1.16 require ( github.com/Microsoft/hcsshim v0.8.9 // indirect - github.com/blang/semver v3.5.1+incompatible + github.com/blang/semver/v4 v4.0.0 github.com/bugsnag/bugsnag-go v1.5.3 // indirect github.com/bugsnag/panicwrap v1.2.0 // indirect - github.com/containerd/containerd v1.4.8 + github.com/containerd/containerd v1.4.11 github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb // indirect github.com/containerd/ttrpc v1.0.1 // indirect github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492 diff --git a/staging/operator-registry/go.sum b/staging/operator-registry/go.sum index 79e8263309..943977893f 100644 --- a/staging/operator-registry/go.sum +++ b/staging/operator-registry/go.sum @@ -152,8 +152,8 @@ github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqh github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.8 h1:H0wkS4AbVKTg9vyvBdCBrxoax8AMObKbNz9Fl2N0i4Y= -github.com/containerd/containerd v1.4.8/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.11 h1:QCGOUN+i70jEEL/A6JVIbhy4f4fanzAzSR4kNG7SlcE= +github.com/containerd/containerd v1.4.11/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb h1:nXPkFq8X1a9ycY3GYQpFNxHh3j2JgY7zDZfq2EXMIzk= github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= diff --git a/staging/operator-registry/pkg/lib/bundle/supported_resources.go b/staging/operator-registry/pkg/lib/bundle/supported_resources.go index 20d7e5402e..94b5fd01df 100644 --- a/staging/operator-registry/pkg/lib/bundle/supported_resources.go +++ b/staging/operator-registry/pkg/lib/bundle/supported_resources.go @@ -16,7 +16,7 @@ const ( PodDisruptionBudgetKind = "PodDisruptionBudget" PriorityClassKind = "PriorityClass" VerticalPodAutoscalerKind = "VerticalPodAutoscaler" - ConsoleYamlSampleKind = "ConsoleYamlSample" + ConsoleYAMLSampleKind = "ConsoleYAMLSample" ConsoleQuickStartKind = "ConsoleQuickStart" ConsoleCLIDownloadKind = "ConsoleCLIDownload" ConsoleLinkKind = "ConsoleLink" @@ -43,7 +43,7 @@ var supportedResources = map[string]Namespaced{ PodDisruptionBudgetKind: true, PriorityClassKind: false, VerticalPodAutoscalerKind: false, - ConsoleYamlSampleKind: false, + ConsoleYAMLSampleKind: false, ConsoleQuickStartKind: false, ConsoleCLIDownloadKind: false, ConsoleLinkKind: false, diff --git a/staging/operator-registry/pkg/lib/registry/registry.go b/staging/operator-registry/pkg/lib/registry/registry.go index e8ec05cce2..9ed73940e1 100644 --- a/staging/operator-registry/pkg/lib/registry/registry.go +++ b/staging/operator-registry/pkg/lib/registry/registry.go @@ -128,6 +128,8 @@ func unpackImage(ctx context.Context, reg image.Registry, ref image.Reference) ( func populate(ctx context.Context, loader registry.Load, graphLoader registry.GraphLoader, querier registry.Query, reg image.Registry, refs []image.Reference, mode registry.Mode, overwrite bool) error { unpackedImageMap := make(map[image.Reference]string, 0) + overwrittenBundles := map[string][]string{} + var imagesToAdd []*registry.Bundle for _, ref := range refs { to, from, cleanup, err := unpackImage(ctx, reg, ref) if err != nil { @@ -135,16 +137,14 @@ func populate(ctx context.Context, loader registry.Load, graphLoader registry.Gr } unpackedImageMap[to] = from defer cleanup() - } - overwriteImageMap := make(map[string]map[image.Reference]string, 0) - if overwrite { - // find all bundles that are attempting to overwrite - for to, from := range unpackedImageMap { - img, err := registry.NewImageInput(to, from) - if err != nil { - return err - } + img, err := registry.NewImageInput(to, from) + if err != nil { + return err + } + imagesToAdd = append(imagesToAdd, img.Bundle) + + if overwrite { overwritten, err := querier.GetBundlePathIfExists(ctx, img.Bundle.Name) if err != nil { if err == registry.ErrBundleImageNotInDatabase { @@ -155,57 +155,18 @@ func populate(ctx context.Context, loader registry.Load, graphLoader registry.Gr if overwritten == "" { return fmt.Errorf("index add --overwrite-latest is only supported when using bundle images") } - // get all bundle paths for that package - we will re-add these to regenerate the graph - bundles, err := querier.GetBundlesForPackage(ctx, img.Bundle.Package) - if err != nil { - return err - } - type unpackedImage struct { - to image.Reference - from string - cleanup func() - err error - } - unpacked := make(chan unpackedImage) - for bundle := range bundles { - // parallelize image pulls - go func(bundle registry.BundleKey, img *registry.ImageInput) { - if bundle.CsvName != img.Bundle.Name { - to, from, cleanup, err := unpackImage(ctx, reg, image.SimpleReference(bundle.BundlePath)) - unpacked <- unpackedImage{to: to, from: from, cleanup: cleanup, err: err} - } else { - unpacked <- unpackedImage{to: to, from: from, cleanup: func() { return }, err: nil} - } - }(bundle, img) - } - if _, ok := overwriteImageMap[img.Bundle.Package]; !ok { - overwriteImageMap[img.Bundle.Package] = make(map[image.Reference]string, 0) - } - for i := 0; i < len(bundles); i++ { - unpack := <-unpacked - if unpack.err != nil { - return unpack.err - } - overwriteImageMap[img.Bundle.Package][unpack.to] = unpack.from - if _, ok := unpackedImageMap[unpack.to]; ok { - delete(unpackedImageMap, unpack.to) - } - defer unpack.cleanup() - } + overwrittenBundles[img.Bundle.Package] = append(overwrittenBundles[img.Bundle.Package], img.Bundle.Name) } } - populator := registry.NewDirectoryPopulator(loader, graphLoader, querier, unpackedImageMap, overwriteImageMap, overwrite) + populator := registry.NewDirectoryPopulator(loader, graphLoader, querier, unpackedImageMap, overwrittenBundles) + if err := populator.Populate(mode); err != nil { + return err - } - for _, imgMap := range overwriteImageMap { - for to, from := range imgMap { - unpackedImageMap[to] = from - } } - return checkForBundles(ctx, querier.(*sqlite.SQLQuerier), graphLoader, unpackedImageMap) + return checkForBundles(ctx, querier.(*sqlite.SQLQuerier), graphLoader, imagesToAdd) } type DeleteFromRegistryRequest struct { @@ -248,6 +209,9 @@ func (r RegistryUpdater) DeleteFromRegistry(request DeleteFromRegistryRequest) e return fmt.Errorf("error removing stranded packages from database: %s", err) } + if _, err := db.Exec("VACUUM"); err != nil { + return err + } return nil } @@ -275,6 +239,9 @@ func (r RegistryUpdater) PruneStrandedFromRegistry(request PruneStrandedFromRegi return fmt.Errorf("error removing stranded packages from database: %s", err) } + if _, err := db.Exec("VACUUM"); err != nil { + return err + } return nil } @@ -291,7 +258,7 @@ func (r RegistryUpdater) PruneFromRegistry(request PruneFromRegistryRequest) err } defer db.Close() - dbLoader, err := sqlite.NewSQLLiteLoader(db) + dbLoader, err := sqlite.NewDeprecationAwareLoader(db) if err != nil { return err } @@ -327,6 +294,9 @@ func (r RegistryUpdater) PruneFromRegistry(request PruneFromRegistryRequest) err } } + if _, err := db.Exec("VACUUM"); err != nil { + return err + } return nil } @@ -344,7 +314,7 @@ func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequ } defer db.Close() - dbLoader, err := sqlite.NewSQLLiteLoader(db) + dbLoader, err := sqlite.NewDeprecationAwareLoader(db) if err != nil { return err } @@ -393,6 +363,9 @@ func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequ r.Logger.WithError(err).Warn("permissive mode enabled") } + if _, err := db.Exec("VACUUM"); err != nil { + return err + } return nil } @@ -430,96 +403,40 @@ func checkForBundlePaths(querier registry.GRPCQuery, bundlePaths []string) ([]st return found, missing, nil } -// packagesFromUnpackedRefs creates packages from a set of unpacked ref dirs without their upgrade edges. -func packagesFromUnpackedRefs(bundles map[image.Reference]string) (map[string]registry.Package, error) { - graph := map[string]registry.Package{} - for to, from := range bundles { - b, err := registry.NewImageInput(to, from) - if err != nil { - return nil, fmt.Errorf("failed to parse unpacked bundle image %s: %v", to, err) - } - v, err := b.Bundle.Version() - if err != nil { - return nil, fmt.Errorf("failed to parse version for %s (%s): %v", b.Bundle.Name, b.Bundle.BundleImage, err) - } - key := registry.BundleKey{ - CsvName: b.Bundle.Name, - Version: v, - BundlePath: b.Bundle.BundleImage, - } - if _, ok := graph[b.Bundle.Package]; !ok { - graph[b.Bundle.Package] = registry.Package{ - Name: b.Bundle.Package, - Channels: map[string]registry.Channel{}, - } - } - for _, c := range b.Bundle.Channels { - if _, ok := graph[b.Bundle.Package].Channels[c]; !ok { - graph[b.Bundle.Package].Channels[c] = registry.Channel{ - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{}, - } - } - graph[b.Bundle.Package].Channels[c].Nodes[key] = nil - } - } - - return graph, nil -} - // replaces mode selects highest version as channel head and // prunes any bundles in the upgrade chain after the channel head. -// check for the presence of all bundles after a replaces-mode add. -func checkForBundles(ctx context.Context, q *sqlite.SQLQuerier, g registry.GraphLoader, bundles map[image.Reference]string) error { - if len(bundles) == 0 { - return nil - } - - required, err := packagesFromUnpackedRefs(bundles) - if err != nil { - return err - } - +// check for the presence of newly added bundles after a replaces-mode add. +func checkForBundles(ctx context.Context, q *sqlite.SQLQuerier, g registry.GraphLoader, required []*registry.Bundle) error { var errs []error - for _, pkg := range required { - graph, err := g.Generate(pkg.Name) + for _, bundle := range required { + graph, err := g.Generate(bundle.Package) if err != nil { - errs = append(errs, fmt.Errorf("unable to verify added bundles for package %s: %v", pkg.Name, err)) + errs = append(errs, fmt.Errorf("unable to verify added bundles for package %s: %v", bundle.Package, err)) continue } - for channel, missing := range pkg.Channels { - // trace replaces chain for reachable bundles + for _, channel := range bundle.Channels { + var foundImage bool for next := []registry.BundleKey{graph.Channels[channel].Head}; len(next) > 0; next = next[1:] { - delete(missing.Nodes, next[0]) + if next[0].BundlePath == bundle.BundleImage { + foundImage = true + break + } for edge := range graph.Channels[channel].Nodes[next[0]] { next = append(next, edge) } } - for bundle := range missing.Nodes { - // check if bundle is deprecated. Bundles readded after deprecation should not be present in index and can be ignored. - deprecated, err := isDeprecated(ctx, q, bundle) - if err != nil { - errs = append(errs, fmt.Errorf("could not validate pruned bundle %s (%s) as deprecated: %v", bundle.CsvName, bundle.BundlePath, err)) - } - if !deprecated { - errs = append(errs, fmt.Errorf("added bundle %s pruned from package %s, channel %s: this may be due to incorrect channel head (%s)", bundle.BundlePath, pkg.Name, channel, graph.Channels[channel].Head.CsvName)) - } + if foundImage { + continue } - } - } - return utilerrors.NewAggregate(errs) -} -func isDeprecated(ctx context.Context, q *sqlite.SQLQuerier, bundle registry.BundleKey) (bool, error) { - props, err := q.GetPropertiesForBundle(ctx, bundle.CsvName, bundle.Version, bundle.BundlePath) - if err != nil { - return false, err - } - for _, prop := range props { - if prop.Type == registry.DeprecatedType { - return true, nil + var headSkips []string + for b := range graph.Channels[channel].Nodes[graph.Channels[channel].Head] { + headSkips = append(headSkips, b.CsvName) + } + errs = append(errs, fmt.Errorf("add prunes bundle %s (%s) from package %s, channel %s: this may be due to incorrect channel head (%s, skips/replaces %v)", bundle.Name, bundle.BundleImage, bundle.Package, channel, graph.Channels[channel].Head.CsvName, headSkips)) } } - return false, nil + return utilerrors.NewAggregate(errs) } diff --git a/staging/operator-registry/pkg/lib/registry/registry_test.go b/staging/operator-registry/pkg/lib/registry/registry_test.go index ff3e7d58ec..4ff3b812e9 100644 --- a/staging/operator-registry/pkg/lib/registry/registry_test.go +++ b/staging/operator-registry/pkg/lib/registry/registry_test.go @@ -25,14 +25,14 @@ import ( "github.com/operator-framework/operator-registry/pkg/lib/bundle" "github.com/operator-framework/operator-registry/pkg/registry" "github.com/operator-framework/operator-registry/pkg/sqlite" - "github.com/operator-framework/operator-registry/pkg/sqlite/sqlitefakes" ) func fakeBundlePathFromName(name string) string { return fmt.Sprintf("%s-path", name) } -func newQuerier(bundles []*model.Bundle) *registry.Querier { +func newQuerier(t *testing.T, bundles []*model.Bundle) *registry.Querier { + t.Helper() pkgs := map[string]*model.Package{} channels := map[string]map[string]*model.Channel{} @@ -85,7 +85,9 @@ func newQuerier(bundles []*model.Bundle) *registry.Querier { }) } } - return registry.NewQuerier(pkgs) + reg, err := registry.NewQuerier(pkgs) + require.NoError(t, err) + return reg } func TestCheckForBundlePaths(t *testing.T) { @@ -103,7 +105,7 @@ func TestCheckForBundlePaths(t *testing.T) { }{ { description: "BundleListPresent", - querier: newQuerier([]*model.Bundle{ + querier: newQuerier(t, []*model.Bundle{ { Package: &model.Package{Name: "pkg-0"}, Channel: &model.Channel{Name: "stable"}, @@ -126,7 +128,7 @@ func TestCheckForBundlePaths(t *testing.T) { }, { description: "BundleListPartiallyMissing", - querier: newQuerier([]*model.Bundle{ + querier: newQuerier(t, []*model.Bundle{ { Package: &model.Package{Name: "pkg-0"}, Channel: &model.Channel{Name: "stable"}, @@ -150,7 +152,7 @@ func TestCheckForBundlePaths(t *testing.T) { }, { description: "EmptyRegistry", - querier: newQuerier(nil), + querier: newQuerier(t, nil), checkPaths: []string{ fakeBundlePathFromName("missing"), }, @@ -161,7 +163,7 @@ func TestCheckForBundlePaths(t *testing.T) { }, { description: "EmptyDeprecateList", - querier: newQuerier([]*model.Bundle{ + querier: newQuerier(t, []*model.Bundle{ { Package: &model.Package{Name: "pkg-0"}, Channel: &model.Channel{Name: "stable"}, @@ -189,6 +191,9 @@ func TestCheckForBundlePaths(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { found, missing, err := checkForBundlePaths(tt.querier, tt.checkPaths) + if qc, ok := tt.querier.(*registry.Querier); ok { + defer qc.Close() + } if tt.expected.err != nil { require.EqualError(t, err, tt.expected.err.Error()) return @@ -276,11 +281,15 @@ func CreateTestDb(t *testing.T) (*sql.DB, func()) { } } -func newUnpackedTestBundle(dir, name string, csvSpec json.RawMessage, annotations registry.Annotations) (string, func(), error) { +func newUnpackedTestBundle(dir, name string, csvSpec json.RawMessage, annotations registry.Annotations, overwrite bool) (string, func(), error) { bundleDir := filepath.Join(dir, fmt.Sprintf("%s-%s", annotations.PackageName, name)) cleanup := func() { os.RemoveAll(bundleDir) } + + if overwrite { + os.RemoveAll(bundleDir) + } if err := os.Mkdir(bundleDir, 0755); err != nil { return bundleDir, cleanup, err } @@ -333,214 +342,287 @@ func newUnpackedTestBundle(dir, name string, csvSpec json.RawMessage, annotation } type bundleDir struct { + version string csvSpec json.RawMessage annotations registry.Annotations + isOverwrite bool } -func TestPackagesFromUnpackedRefs(t *testing.T) { +func TestCheckForBundles(t *testing.T) { + type step struct { + bundles map[string]bundleDir + action int + expected []*registry.Bundle // For testing pruning after deprecation + wantErr error + } + const ( + actionAdd = iota + actionDeprecate + actionOverwrite + ) tests := []struct { description string - bundles map[string]bundleDir - expected map[string]registry.Package - wantErr bool + steps []step + init func() (*sql.DB, func()) }{ { - description: "InvalidBundle/Empty", - bundles: map[string]bundleDir{ - "bundle-empty": {}, - }, - wantErr: true, - }, - { - description: "LoadPartialGraph", - bundles: map[string]bundleDir{ - "testoperator-1": { - csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"1.0.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg-1", - Channels: "alpha", - DefaultChannelName: "stable", + // 1.1.0 -> 1.2.0 ok channel 1 + // \-> 1.2.0-1 pruned channel 2 + description: "ErrorOnNewPrunedBundle", + steps: []step{ + { + bundles: map[string]bundleDir{ + "newPruned-1.1.0": { + csvSpec: json.RawMessage(`{"version":"1.1.0"}`), + annotations: registry.Annotations{ + PackageName: "testpkg", + Channels: "stable,alpha", + DefaultChannelName: "stable", + }, + version: "1.1.0", + }, }, + action: actionAdd, }, - "testoperator-2": { - csvSpec: json.RawMessage(`{"version":"2.1.0"}`), - annotations: registry.Annotations{ - PackageName: "testpkg-2", - Channels: "stable,alpha", - DefaultChannelName: "stable", + { + bundles: map[string]bundleDir{ + "newPruned-1.2.0": { + csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"newPruned-1.1.0"}`), + annotations: registry.Annotations{ + PackageName: "testpkg", + Channels: "alpha", + DefaultChannelName: "stable", + }, + version: "1.2.0", + }, + "newPruned-1.2.0-1": { + csvSpec: json.RawMessage(`{"version":"1.2.0-1","replaces":"newPruned-1.2.0"}`), + annotations: registry.Annotations{ + PackageName: "testpkg", + Channels: "stable,alpha", + DefaultChannelName: "stable", + }, + version: "1.2.0-1", + }, }, + action: actionAdd, + wantErr: fmt.Errorf("add prunes bundle newPruned-1.2.0-1 (newPruned-1.2.0-1) from package testpkg, channel alpha: this may be due to incorrect channel head (newPruned-1.2.0, skips/replaces [newPruned-1.1.0])"), }, }, - expected: map[string]registry.Package{ - "testpkg-1": { - Name: "testpkg-1", - Channels: map[string]registry.Channel{ - "alpha": { - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - registry.BundleKey{ - BundlePath: fakeBundlePathFromName("testoperator-1"), - Version: "1.1.0", - CsvName: "testoperator-1", - }: nil, + }, + { + description: "silentPruneForExistingBundle", + steps: []step{ + { + bundles: map[string]bundleDir{ + "silentPrune-1.0.0": { + csvSpec: json.RawMessage(`{"version":"1.0.0"}`), + annotations: registry.Annotations{ + PackageName: "testpkg", + Channels: "stable,alpha", + DefaultChannelName: "stable", }, + version: "1.0.0", }, - }, - }, - "testpkg-2": { - Name: "testpkg-2", - Channels: map[string]registry.Channel{ - "alpha": { - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - registry.BundleKey{ - BundlePath: fakeBundlePathFromName("testoperator-2"), - Version: "2.1.0", - CsvName: "testoperator-2", - }: nil, + "silentPrune-1.1.0": { + csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"silentPrune-1.0.0"}`), + annotations: registry.Annotations{ + PackageName: "testpkg", + Channels: "stable,alpha", + DefaultChannelName: "stable", }, + version: "1.1.0", }, - "stable": { - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ - registry.BundleKey{ - BundlePath: fakeBundlePathFromName("testoperator-2"), - Version: "2.1.0", - CsvName: "testoperator-2", - }: nil, + }, + action: actionAdd, + }, + { + bundles: map[string]bundleDir{ + "silentPrune-1.2.0": { + csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"silentPrune-1.0.0"}`), + annotations: registry.Annotations{ + PackageName: "testpkg", + Channels: "alpha", + DefaultChannelName: "stable", }, + version: "1.2.0", }, }, + action: actionAdd, }, }, }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - tmpdir, err := os.MkdirTemp(".", "tmpdir-*") - defer os.RemoveAll(tmpdir) - require.NoError(t, err) - refs := map[image.Reference]string{} - for name, b := range tt.bundles { - dir, _, err := newUnpackedTestBundle(tmpdir, name, b.csvSpec, b.annotations) - require.NoError(t, err) - refs[image.SimpleReference(fakeBundlePathFromName(name))] = dir - } - pkg, err := packagesFromUnpackedRefs(refs) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - require.EqualValues(t, tt.expected, pkg) - }) - } -} - -func TestCheckForBundles(t *testing.T) { - type step struct { - bundles map[string]bundleDir - action int - } - const ( - actionAdd = iota - actionDeprecate - actionOverwrite - ) - tests := []struct { - description string - steps []step - wantErr error - init func() (*sql.DB, func()) - }{ { - // 1.1.0 -> 1.0.0 pruned channel 1 - // \-> 1.2.0 ok channel 2 - description: "partialPruning", + // 1.0.0 <- 1.0.1 <- 1.0.1-1 <- 1.0.2 (head) + // No pruning despite chain being out of order for 1.0.1 <- 1.0.1-1 + description: "allowUnorderedWithMaxChannelHead", steps: []step{ { bundles: map[string]bundleDir{ - "unorderedReplaces-1.1.0": { - csvSpec: json.RawMessage(`{"version":"1.1.0"}`), + "unorderedReplaces-1.0.0": { + csvSpec: json.RawMessage(`{"version":"1.0.0"}`), annotations: registry.Annotations{ PackageName: "testpkg", Channels: "stable,alpha", DefaultChannelName: "stable", }, + version: "1.0.0", }, - "unorderedReplaces-1.0.0": { - csvSpec: json.RawMessage(`{"version":"1.0.0","replaces":"unorderedReplaces-1.1.0"}`), + "unorderedReplaces-1.1.0": { + csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"unorderedReplaces-1.0.0"}`), annotations: registry.Annotations{ PackageName: "testpkg", Channels: "stable,alpha", DefaultChannelName: "stable", }, + version: "1.1.0", + }, + }, + action: actionAdd, + }, + { + bundles: map[string]bundleDir{ + "unorderedReplaces-1.1.0-1": { + csvSpec: json.RawMessage(`{"version":"1.1.0-1","replaces":"unorderedReplaces-1.1.0"}`), + annotations: registry.Annotations{ + PackageName: "testpkg", + Channels: "alpha", + DefaultChannelName: "stable", + }, + version: "1.1.0-1", }, "unorderedReplaces-1.2.0": { - csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"unorderedReplaces-1.0.0"}`), + csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"unorderedReplaces-1.1.0-1"}`), annotations: registry.Annotations{ PackageName: "testpkg", Channels: "alpha", DefaultChannelName: "stable", }, + version: "1.2.0", }, }, action: actionAdd, }, }, - wantErr: fmt.Errorf("added bundle unorderedReplaces-1.0.0 pruned from package testpkg, channel stable: this may be due to incorrect channel head (unorderedReplaces-1.1.0)"), }, { - description: "ignoreDeprecated", + // If a pruned bundle was deprecated, ignore + description: "withDeprecated", steps: []step{ { bundles: map[string]bundleDir{ - "ignoreDeprecated-1.0.0": { + "withDeprecated-1.0.0": { csvSpec: json.RawMessage(`{"version":"1.0.0"}`), annotations: registry.Annotations{ PackageName: "testpkg", Channels: "stable", }, + version: "1.0.0", }, - "ignoreDeprecated-1.1.0": { - csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"ignoreDeprecated-1.0.0"}`), + "withDeprecated-1.1.0": { + csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"withDeprecated-1.0.0"}`), annotations: registry.Annotations{ PackageName: "testpkg", Channels: "stable", }, + version: "1.1.0", }, - "ignoreDeprecated-1.2.0": { - csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"ignoreDeprecated-1.1.0"}`), + "withDeprecated-1.2.0": { + csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"withDeprecated-1.1.0"}`), annotations: registry.Annotations{ PackageName: "testpkg", Channels: "stable", }, + version: "1.2.0", }, }, action: actionAdd, }, { bundles: map[string]bundleDir{ - "ignoreDeprecated-1.1.0": {}, + "withDeprecated-1.1.0": {}, }, action: actionDeprecate, + expected: []*registry.Bundle{ + { + Name: "withDeprecated-1.1.0", + Package: "testpkg", + Channels: []string{"stable"}, + BundleImage: "withDeprecated-1.1.0", + }, + { + Name: "withDeprecated-1.2.0", + Package: "testpkg", + Channels: []string{"stable"}, + BundleImage: "withDeprecated-1.2.0", + }, + }, }, { bundles: map[string]bundleDir{ - "ignoreDeprecated-1.0.0": { + "withDeprecated-1.2.0": { + csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":""}`), + annotations: registry.Annotations{ + PackageName: "testpkg", + Channels: "alpha", + DefaultChannelName: "alpha", + }, + version: "1.2.0", + isOverwrite: true, + }, + }, + action: actionOverwrite, + }, + }, + }, + { + // bundle version should be immutable anyway, but only csv name is required to stay unchanged in overwrite + description: "overwritePruning", + steps: []step{ + { + bundles: map[string]bundleDir{ + "withOverwrite-1.0.0": { csvSpec: json.RawMessage(`{"version":"1.0.0"}`), annotations: registry.Annotations{ PackageName: "testpkg", Channels: "stable", }, + version: "1.0.0", }, - "ignoreDeprecated-1.1.0": { - csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"ignoreDeprecated-1.0.0"}`), + "withOverwrite-1.1.0": { + csvSpec: json.RawMessage(`{"version":"1.1.0","replaces":"withOverwrite-1.0.0"}`), annotations: registry.Annotations{ PackageName: "testpkg", Channels: "stable", }, + version: "1.1.0", }, }, - action: actionOverwrite, + action: actionAdd, + }, + { + bundles: map[string]bundleDir{ + "withOverwrite-1.1.0": { + csvSpec: json.RawMessage(`{"version":"1.0.0-1","replaces":"withOverwrite-1.0.0"}`), + annotations: registry.Annotations{ + PackageName: "testpkg", + Channels: "stable", + }, + version: "1.0.0-1", + isOverwrite: true, + }, + "withOverwrite-1.2.0": { + csvSpec: json.RawMessage(`{"version":"1.2.0","replaces":"withOverwrite-1.1.0"}`), + annotations: registry.Annotations{ + PackageName: "testpkg", + Channels: "alpha", + DefaultChannelName: "stable", + }, + version: "1.2.0", + }, + }, + action: actionOverwrite, + wantErr: fmt.Errorf("add prunes bundle withOverwrite-1.1.0 (withOverwrite-1.1.0-overwrite) from package testpkg, channel stable: this may be due to incorrect channel head (withOverwrite-1.0.0, skips/replaces [])"), }, }, }, @@ -560,112 +642,52 @@ func TestCheckForBundles(t *testing.T) { require.NoError(t, err) for _, step := range tt.steps { + expected := []*registry.Bundle{} switch step.action { case actionDeprecate: for deprecate := range step.bundles { require.NoError(t, load.DeprecateBundle(deprecate)) } - case actionAdd: + expected = step.expected + case actionAdd, actionOverwrite: + overwriteRefs := map[string][]string{} refs := map[image.Reference]string{} for name, b := range step.bundles { - dir, _, err := newUnpackedTestBundle(tmpdir, name, b.csvSpec, b.annotations) + dir, _, err := newUnpackedTestBundle(tmpdir, name, b.csvSpec, b.annotations, true) require.NoError(t, err) - refs[image.SimpleReference(name)] = dir - } - require.NoError(t, registry.NewDirectoryPopulator( - load, - graphLoader, - query, - refs, - nil, - false).Populate(registry.ReplacesMode)) - err = checkForBundles(context.TODO(), query, graphLoader, refs) - if tt.wantErr == nil { - require.NoError(t, err) - return - } - require.EqualError(t, err, tt.wantErr.Error()) + // refs to be added + bundleImage := name - case actionOverwrite: - overwriteRefs := map[string]map[image.Reference]string{} - refs := map[image.Reference]string{} - for name, b := range step.bundles { - dir, _, err := newUnpackedTestBundle(tmpdir, name, b.csvSpec, b.annotations) - require.NoError(t, err) - to := image.SimpleReference(name) - refs[image.SimpleReference(name)] = dir - refs[to] = dir - img, err := registry.NewImageInput(to, dir) + // bundles to remove for overwrite. Only one per package is permitted. + if step.action == actionOverwrite && b.isOverwrite { + bundleImage += "-overwrite" + } + + img, err := registry.NewImageInput(image.SimpleReference(bundleImage), dir) require.NoError(t, err) - if _, ok := overwriteRefs[img.Bundle.Package]; ok { - overwriteRefs[img.Bundle.Package] = map[image.Reference]string{} + expected = append(expected, img.Bundle) + + if step.action == actionOverwrite && b.isOverwrite { + overwriteRefs[img.Bundle.Package] = append(overwriteRefs[img.Bundle.Package], name) } - overwriteRefs[img.Bundle.Package][to] = dir + refs[image.SimpleReference(bundleImage)] = dir + } require.NoError(t, registry.NewDirectoryPopulator( load, graphLoader, query, - nil, - overwriteRefs, - true).Populate(registry.ReplacesMode)) - - err = checkForBundles(context.TODO(), query, graphLoader, refs) - if tt.wantErr == nil { - require.NoError(t, err) - return - } - require.EqualError(t, err, tt.wantErr.Error()) + refs, + overwriteRefs).Populate(registry.ReplacesMode)) } + err = checkForBundles(context.TODO(), query, graphLoader, expected) + if step.wantErr == nil { + require.NoError(t, err, fmt.Sprintf("%d", step.action)) + continue + } + require.EqualError(t, err, step.wantErr.Error()) } }) } } - -func TestDeprecated(t *testing.T) { - deprecated := map[string]bool{ - "deprecatedBundle": true, - "otherBundle": false, - } - q := &sqlitefakes.FakeQuerier{ - QueryContextStub: func(ctx context.Context, query string, args ...interface{}) (sqlite.RowScanner, error) { - bundleName := args[2].(string) - if len(bundleName) == 0 { - return nil, fmt.Errorf("empty bundle name") - } - hasNext := true - return &sqlitefakes.FakeRowScanner{ScanStub: func(args ...interface{}) error { - if deprecated[bundleName] { - *args[0].(*sql.NullString) = sql.NullString{ - String: registry.DeprecatedType, - Valid: true, - } - *args[1].(*sql.NullString) = sql.NullString{ - Valid: true, - } - } - return nil - }, - NextStub: func() bool { - if hasNext { - hasNext = false - return true - } - return false - }, - }, nil - }, - } - - querier := sqlite.NewSQLLiteQuerierFromDBQuerier(q) - - _, err := isDeprecated(context.TODO(), querier, registry.BundleKey{}) - require.Error(t, err) - - for b := range deprecated { - isDeprecated, err := isDeprecated(context.TODO(), querier, registry.BundleKey{BundlePath: b}) - require.NoError(t, err) - require.Equal(t, deprecated[b], isDeprecated) - } -} diff --git a/staging/operator-registry/pkg/lib/semver/semver.go b/staging/operator-registry/pkg/lib/semver/semver.go index 033bb5419e..6875566d08 100644 --- a/staging/operator-registry/pkg/lib/semver/semver.go +++ b/staging/operator-registry/pkg/lib/semver/semver.go @@ -3,7 +3,7 @@ package semver import ( "fmt" - "github.com/blang/semver" + "github.com/blang/semver/v4" ) // BuildIdCompare compares two versions and returns negative one if the first arg is less than the second arg, positive one if it is larger, and zero if they are equal. diff --git a/staging/operator-registry/pkg/lib/semver/semver_test.go b/staging/operator-registry/pkg/lib/semver/semver_test.go index 2291146783..2ee4a5385f 100644 --- a/staging/operator-registry/pkg/lib/semver/semver_test.go +++ b/staging/operator-registry/pkg/lib/semver/semver_test.go @@ -3,7 +3,7 @@ package semver import ( "testing" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/stretchr/testify/require" ) diff --git a/staging/operator-registry/pkg/registry/bundlegraphloader.go b/staging/operator-registry/pkg/registry/bundlegraphloader.go index 05d8abf064..e8664c4e84 100644 --- a/staging/operator-registry/pkg/registry/bundlegraphloader.go +++ b/staging/operator-registry/pkg/registry/bundlegraphloader.go @@ -3,7 +3,7 @@ package registry import ( "fmt" - "github.com/blang/semver" + "github.com/blang/semver/v4" ) // BundleGraphLoader generates updated graphs by adding bundles to them, updating diff --git a/staging/operator-registry/pkg/registry/bundlegraphloader_test.go b/staging/operator-registry/pkg/registry/bundlegraphloader_test.go index 8bc5d03c1a..5d6e3c2a14 100644 --- a/staging/operator-registry/pkg/registry/bundlegraphloader_test.go +++ b/staging/operator-registry/pkg/registry/bundlegraphloader_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/staging/operator-registry/pkg/registry/csv.go b/staging/operator-registry/pkg/registry/csv.go index a3b5ebc191..ec6f3e3239 100644 --- a/staging/operator-registry/pkg/registry/csv.go +++ b/staging/operator-registry/pkg/registry/csv.go @@ -50,7 +50,7 @@ const ( description = "description" // The yaml attribute that specifies the version of the ClusterServiceVersion - // expected to be semver and parseable by blang/semver + // expected to be semver and parseable by blang/semver/v4 version = "version" // The yaml attribute that specifies the related images of the ClusterServiceVersion diff --git a/staging/operator-registry/pkg/registry/decode.go b/staging/operator-registry/pkg/registry/decode.go index 392d5dd887..0a9587d092 100644 --- a/staging/operator-registry/pkg/registry/decode.go +++ b/staging/operator-registry/pkg/registry/decode.go @@ -6,6 +6,7 @@ import ( "io" "io/fs" + "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/yaml" ) @@ -44,7 +45,7 @@ func DecodePackageManifest(reader io.Reader) (manifest *PackageManifest, err err return } -func decodeFileFS(root fs.FS, path string, into interface{}) error { +func decodeFileFS(root fs.FS, path string, into interface{}, log *logrus.Entry) error { fileReader, err := root.Open(path) if err != nil { return fmt.Errorf("unable to read file %s: %s", path, err) @@ -53,5 +54,16 @@ func decodeFileFS(root fs.FS, path string, into interface{}) error { decoder := yaml.NewYAMLOrJSONDecoder(fileReader, 30) - return decoder.Decode(into) + errRet := decoder.Decode(into) + + if errRet == nil { + // Look for and warn about extra documents + extraDocument := &map[string]interface{}{} + err = decoder.Decode(extraDocument) + if err == nil && log != nil { + log.Warnf("found more than one document inside %s, using only the first one", path) + } + } + + return errRet } diff --git a/staging/operator-registry/pkg/registry/decode_test.go b/staging/operator-registry/pkg/registry/decode_test.go index 86a7f0d723..02b26de5a6 100644 --- a/staging/operator-registry/pkg/registry/decode_test.go +++ b/staging/operator-registry/pkg/registry/decode_test.go @@ -6,6 +6,8 @@ import ( "testing" "testing/fstest" + "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -97,17 +99,34 @@ func TestDecodeFileFS(t *testing.T) { } root := fstest.MapFS{ - "foo.yaml": &fstest.MapFile{Data: []byte("bar: baz")}, + "foo.yaml": &fstest.MapFile{Data: []byte("bar: baz")}, + "multi.yaml": &fstest.MapFile{Data: []byte("bar: baz\n---\nfoo: bar")}, } + logger, logHook := test.NewNullLogger() + entry := logger.WithFields(nil) + var nilPtr *foo - require.NoError(t, decodeFileFS(root, "foo.yaml", nilPtr)) + require.NoError(t, decodeFileFS(root, "foo.yaml", nilPtr, entry)) require.Nil(t, nilPtr) + require.Equal(t, 0, len(logHook.Entries)) + logHook.Reset() ptr := &foo{} - require.NoError(t, decodeFileFS(root, "foo.yaml", ptr)) + require.NoError(t, decodeFileFS(root, "foo.yaml", ptr, entry)) + require.NotNil(t, ptr) + require.Equal(t, "baz", ptr.Bar) + require.Equal(t, 0, len(logHook.Entries)) + logHook.Reset() + + ptr = &foo{} + require.NoError(t, decodeFileFS(root, "multi.yaml", ptr, entry)) require.NotNil(t, ptr) require.Equal(t, "baz", ptr.Bar) + require.Equal(t, 1, len(logHook.Entries)) + require.Equal(t, logrus.WarnLevel, logHook.LastEntry().Level) + require.Equal(t, "found more than one document inside multi.yaml, using only the first one", logHook.LastEntry().Message) + logHook.Reset() } func loadFile(t *testing.T, path string) io.Reader { diff --git a/staging/operator-registry/pkg/registry/directoryGraphLoader.go b/staging/operator-registry/pkg/registry/directoryGraphLoader.go index 03efa4d63f..b639c08b5e 100644 --- a/staging/operator-registry/pkg/registry/directoryGraphLoader.go +++ b/staging/operator-registry/pkg/registry/directoryGraphLoader.go @@ -8,7 +8,7 @@ import ( "path/filepath" "sort" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/onsi/gomega/gstruct/errors" ) diff --git a/staging/operator-registry/pkg/registry/helper_test.go b/staging/operator-registry/pkg/registry/helper_test.go index 3cd940089f..9121fa4506 100644 --- a/staging/operator-registry/pkg/registry/helper_test.go +++ b/staging/operator-registry/pkg/registry/helper_test.go @@ -3,7 +3,7 @@ package registry import ( "testing" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/stretchr/testify/require" ) diff --git a/staging/operator-registry/pkg/registry/interface.go b/staging/operator-registry/pkg/registry/interface.go index f6a5f843b7..93df40c61a 100644 --- a/staging/operator-registry/pkg/registry/interface.go +++ b/staging/operator-registry/pkg/registry/interface.go @@ -104,3 +104,7 @@ type GraphLoader interface { type RegistryPopulator interface { Populate() error } + +type HeadOverwriter interface { + RemoveOverwrittenChannelHead(pkg, bundle string) error +} diff --git a/staging/operator-registry/pkg/registry/parse.go b/staging/operator-registry/pkg/registry/parse.go index 984517cc04..4b13ef7673 100644 --- a/staging/operator-registry/pkg/registry/parse.go +++ b/staging/operator-registry/pkg/registry/parse.go @@ -6,10 +6,9 @@ import ( "io/fs" "strings" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" ) type bundleParser struct { @@ -74,7 +73,7 @@ func (b *bundleParser) addManifests(manifests fs.FS, bundle *Bundle) error { } obj := &unstructured.Unstructured{} - if err = decodeFileFS(manifests, name, obj); err != nil { + if err = decodeFileFS(manifests, name, obj, b.log); err != nil { b.log.Warnf("failed to decode: %s", err) continue } @@ -128,7 +127,7 @@ func (b *bundleParser) addMetadata(metadata fs.FS, bundle *Bundle) error { name := f.Name() if af == nil { decoded := AnnotationsFile{} - if err = decodeFileFS(metadata, name, &decoded); err == nil { + if err = decodeFileFS(metadata, name, &decoded, b.log); err == nil { if decoded != (AnnotationsFile{}) { af = &decoded } @@ -136,7 +135,7 @@ func (b *bundleParser) addMetadata(metadata fs.FS, bundle *Bundle) error { } if df == nil { decoded := DependenciesFile{} - if err = decodeFileFS(metadata, name, &decoded); err == nil { + if err = decodeFileFS(metadata, name, &decoded, b.log); err == nil { if len(decoded.Dependencies) > 0 { df = &decoded } @@ -144,7 +143,7 @@ func (b *bundleParser) addMetadata(metadata fs.FS, bundle *Bundle) error { } if pf == nil { decoded := PropertiesFile{} - if err = decodeFileFS(metadata, name, &decoded); err == nil { + if err = decodeFileFS(metadata, name, &decoded, b.log); err == nil { if len(decoded.Properties) > 0 { pf = &decoded } diff --git a/staging/operator-registry/pkg/registry/populator.go b/staging/operator-registry/pkg/registry/populator.go index ad65e78b2d..4a236da793 100644 --- a/staging/operator-registry/pkg/registry/populator.go +++ b/staging/operator-registry/pkg/registry/populator.go @@ -5,8 +5,9 @@ import ( "errors" "fmt" "os" + "sort" - "github.com/blang/semver" + "github.com/blang/semver/v4" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/yaml" @@ -20,22 +21,20 @@ type Dependencies struct { // DirectoryPopulator loads an unpacked operator bundle from a directory into the database. type DirectoryPopulator struct { - loader Load - graphLoader GraphLoader - querier Query - imageDirMap map[image.Reference]string - overwriteDirMap map[string]map[image.Reference]string - overwrite bool + loader Load + graphLoader GraphLoader + querier Query + imageDirMap map[image.Reference]string + overwrittenImages map[string][]string } -func NewDirectoryPopulator(loader Load, graphLoader GraphLoader, querier Query, imageDirMap map[image.Reference]string, overwriteDirMap map[string]map[image.Reference]string, overwrite bool) *DirectoryPopulator { +func NewDirectoryPopulator(loader Load, graphLoader GraphLoader, querier Query, imageDirMap map[image.Reference]string, overwrittenImages map[string][]string) *DirectoryPopulator { return &DirectoryPopulator{ - loader: loader, - graphLoader: graphLoader, - querier: querier, - imageDirMap: imageDirMap, - overwriteDirMap: overwriteDirMap, - overwrite: overwrite, + loader: loader, + graphLoader: graphLoader, + querier: querier, + imageDirMap: imageDirMap, + overwrittenImages: overwrittenImages, } } @@ -52,24 +51,11 @@ func (i *DirectoryPopulator) Populate(mode Mode) error { imagesToAdd = append(imagesToAdd, imageInput) } - imagesToReAdd := make([]*ImageInput, 0) - for pkg := range i.overwriteDirMap { - for to, from := range i.overwriteDirMap[pkg] { - imageInput, err := NewImageInput(to, from) - if err != nil { - errs = append(errs, err) - continue - } - - imagesToReAdd = append(imagesToReAdd, imageInput) - } - } - if len(errs) > 0 { return utilerrors.NewAggregate(errs) } - err := i.loadManifests(imagesToAdd, imagesToReAdd, mode) + err := i.loadManifests(imagesToAdd, mode) if err != nil { return err } @@ -78,6 +64,7 @@ func (i *DirectoryPopulator) Populate(mode Mode) error { } func (i *DirectoryPopulator) globalSanityCheck(imagesToAdd []*ImageInput) error { + overwrite := len(i.overwrittenImages) > 0 var errs []error images := make(map[string]struct{}) for _, image := range imagesToAdd { @@ -111,7 +98,7 @@ func (i *DirectoryPopulator) globalSanityCheck(imagesToAdd []*ImageInput) error continue } if bundle != nil { - if !i.overwrite { + if !overwrite { // raise error that this package + channel + csv combo is already in the db errs = append(errs, PackageVersionAlreadyAddedErr{ErrorString: "Bundle already added that provides package and csv"}) break @@ -142,10 +129,13 @@ func (i *DirectoryPopulator) globalSanityCheck(imagesToAdd []*ImageInput) error } } + if err := i.ValidateEdgeBundlePackage(imagesToAdd); err != nil { + errs = append(errs, err) + } return utilerrors.NewAggregate(errs) } -func (i *DirectoryPopulator) loadManifests(imagesToAdd []*ImageInput, imagesToReAdd []*ImageInput, mode Mode) error { +func (i *DirectoryPopulator) loadManifests(imagesToAdd []*ImageInput, mode Mode) error { // global sanity checks before insertion if err := i.globalSanityCheck(imagesToAdd); err != nil { return err @@ -153,17 +143,30 @@ func (i *DirectoryPopulator) loadManifests(imagesToAdd []*ImageInput, imagesToRe switch mode { case ReplacesMode: - for pkg := range i.overwriteDirMap { - // TODO: If this succeeds but the add fails there will be a disconnect between - // the registry and the index. Loading the bundles in a single transactions as - // described above would allow us to do the removable in that same transaction - // and ensure that rollback is possible. - if err := i.loader.RemovePackage(pkg); err != nil { - return err + // TODO: If this succeeds but the add fails there will be a disconnect between + // the registry and the index. Loading the bundles in a single transactions as + // described above would allow us to do the removable in that same transaction + // and ensure that rollback is possible. + + // globalSanityCheck should have verified this to be a head without anything replacing it + // and that we have a single overwrite per package + + if len(i.overwrittenImages) > 0 { + if overwriter, ok := i.loader.(HeadOverwriter); ok { + // Assume loader has some way to handle overwritten heads if HeadOverwriter isn't implemented explicitly + for pkg, imgToDelete := range i.overwrittenImages { + if len(imgToDelete) == 0 { + continue + } + // delete old head bundle and swap it with the previous real bundle in its replaces chain + if err := overwriter.RemoveOverwrittenChannelHead(pkg, imgToDelete[0]); err != nil { + return err + } + } } } - return i.loadManifestsReplaces(append(imagesToAdd, imagesToReAdd...)) + return i.loadManifestsReplaces(imagesToAdd) case SemVerMode: for _, image := range imagesToAdd { if err := i.loadManifestsSemver(image.Bundle, false); err != nil { @@ -408,3 +411,54 @@ func DecodeFile(path string, into interface{}) error { return decoder.Decode(into) } + +// ValidateEdgeBundlePackage ensures that all bundles in the input will only skip or replace bundles in the same package. +func (i *DirectoryPopulator) ValidateEdgeBundlePackage(images []*ImageInput) error { + // track packages for encountered bundles + expectedBundlePackages := map[string]string{} + for _, b := range images { + r, err := b.Bundle.Replaces() + if err != nil { + return fmt.Errorf("failed to validate replaces for bundle %s(%s): %v", b.Bundle.Name, b.Bundle.BundleImage, err) + } + + skipped, err := b.Bundle.Skips() + if err != nil { + return fmt.Errorf("failed to validate skipped entries for bundle %s(%s): %v", b.Bundle.Name, b.Bundle.BundleImage, err) + } + + for _, bndl := range append(skipped, r, b.Bundle.Name) { + if len(bndl) == 0 { + continue + } + + if pkg, ok := expectedBundlePackages[bndl]; ok && pkg != b.Bundle.Package { + pkgs := []string{pkg, b.Bundle.Package} + sort.Strings(pkgs) + return fmt.Errorf("bundle %s must belong to exactly one package, found on: %v", bndl, pkgs) + } + expectedBundlePackages[bndl] = b.Bundle.Package + } + } + if len(expectedBundlePackages) == 0 { + return nil + } + + pkgs, err := i.querier.ListPackages(context.TODO()) + if err != nil { + return fmt.Errorf("unable to verify bundle packages: %v", err) + } + for _, pkg := range pkgs { + entries, err := i.querier.GetChannelEntriesFromPackage(context.TODO(), pkg) + if err != nil { + return fmt.Errorf("unable to verify bundles for package %v", err) + } + for _, b := range entries { + if bundlePkg, ok := expectedBundlePackages[b.BundleName]; ok && bundlePkg != b.PackageName { + return fmt.Errorf("bundle %s belongs to package %s on index, cannot be added as an edge for package %s", b.BundleName, b.PackageName, bundlePkg) + } + } + } + + return nil +} diff --git a/staging/operator-registry/pkg/registry/populator_test.go b/staging/operator-registry/pkg/registry/populator_test.go index 280907964c..d6f7533749 100644 --- a/staging/operator-registry/pkg/registry/populator_test.go +++ b/staging/operator-registry/pkg/registry/populator_test.go @@ -5,20 +5,29 @@ import ( "database/sql" "encoding/json" "fmt" + "io/ioutil" "math/rand" "os" + "path/filepath" "reflect" "strings" "testing" "time" + "github.com/blang/semver/v4" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors" + "sigs.k8s.io/yaml" + "github.com/operator-framework/api/pkg/lib/version" + "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-registry/pkg/api" "github.com/operator-framework/operator-registry/pkg/image" + "github.com/operator-framework/operator-registry/pkg/lib/bundle" "github.com/operator-framework/operator-registry/pkg/registry" "github.com/operator-framework/operator-registry/pkg/sqlite" ) @@ -71,7 +80,7 @@ func createAndPopulateDB(db *sql.DB) (*sqlite.SQLQuerier, error) { graphLoader, query, refMap, - make(map[string]map[image.Reference]string, 0), false).Populate(registry.ReplacesMode) + nil).Populate(registry.ReplacesMode) } names := []string{"etcd.0.9.0", "etcd.0.9.2", "prometheus.0.22.2", "prometheus.0.14.0", "prometheus.0.15.0"} if err := populate(names); err != nil { @@ -497,7 +506,7 @@ func TestImageLoading(t *testing.T) { graphLoader, query, map[image.Reference]string{i.ref: i.dir}, - make(map[string]map[image.Reference]string, 0), false) + nil) require.NoError(t, p.Populate(registry.ReplacesMode)) } add := registry.NewDirectoryPopulator( @@ -505,7 +514,7 @@ func TestImageLoading(t *testing.T) { graphLoader, query, map[image.Reference]string{tt.addImage.ref: tt.addImage.dir}, - make(map[string]map[image.Reference]string, 0), false) + nil) err = add.Populate(registry.ReplacesMode) if tt.wantErr { require.True(t, checkAggErr(err, tt.err)) @@ -714,8 +723,7 @@ func TestDirectoryPopulator(t *testing.T) { graphLoader, query, bundles, - make(map[string]map[image.Reference]string), - false).Populate(registry.ReplacesMode) + nil).Populate(registry.ReplacesMode) } add := map[image.Reference]string{ image.SimpleReference("quay.io/test/etcd.0.9.2"): "../../bundles/etcd.0.9.2", @@ -1017,6 +1025,49 @@ func TestDeprecatePackage(t *testing.T) { "preview", "stable", }, + "apicurio-registry": []string{ + "2.x", + "alpha", + }, + }, + }, + }, + { + description: "RemoveHeadOfDefaultChannelWithoutAllChannelHeads/Success", + args: args{ + bundles: []string{ + "quay.io/test/apicurio-registry.v0.0.1", + "quay.io/test/apicurio-registry.v0.0.3-v1.2.3.final", + "quay.io/test/apicurio-registry.v0.0.4-v1.3.2.final", + }, + }, + expected: expected{ + remainingBundles: []string{ + "quay.io/test/etcd.0.9.0/alpha", + "quay.io/test/etcd.0.9.0/beta", + "quay.io/test/etcd.0.9.0/stable", + "quay.io/test/etcd.0.9.2/stable", + "quay.io/test/etcd.0.9.2/alpha", + "quay.io/test/prometheus.0.14.0/preview", + "quay.io/test/prometheus.0.14.0/stable", + "quay.io/test/prometheus.0.15.0/preview", + "quay.io/test/prometheus.0.15.0/stable", + "quay.io/test/prometheus.0.22.2/preview", + }, + deprecatedBundles: []string{}, + remainingPkgChannels: pkgChannel{ + "etcd": []string{ + "alpha", + "beta", + "stable", + }, + "prometheus": []string{ + "preview", + "stable", + }, + "apicurio-registry": []string{ + "2.x", + }, }, }, }, @@ -1117,7 +1168,111 @@ func TestDeprecatePackage(t *testing.T) { } func TestAddAfterDeprecate(t *testing.T) { + tmpdir, err := os.MkdirTemp(".", "add-after-deprecate-*") + require.NoError(t, err) + defer os.RemoveAll(tmpdir) + + /* + (0.1) 0.1.2 <- 0.1.1 <- 0.1.0 + | + (0.2) 0.2.2 <- 0.2.1 <- 0.2.0<| + | + (0.3) 0.3.2 <- 0.3.1 <- 0.3.0 <| + */ + testBundles := []struct { + suffix string + channels string + defaultChannel string + csvName string + csvSpec json.RawMessage + }{ + { + csvName: "testpkg.v0.1.0", + channels: "0.1", + csvSpec: json.RawMessage(`{"version":"0.1.0","replaces":""}`), + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.1.1", + csvSpec: json.RawMessage(`{"version":"0.1.1","replaces":"testpkg.v0.1.0"}`), + channels: "0.1", + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.1.2", + csvSpec: json.RawMessage(`{"version":"0.1.2","replaces":"testpkg.v0.1.1"}`), + channels: "0.1", + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.2.0", + csvSpec: json.RawMessage(`{"version":"0.2.0","replaces":"testpkg.v0.1.0"}`), + channels: "0.2", + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.2.1", + csvSpec: json.RawMessage(`{"version":"0.2.1","replaces":"testpkg.v0.2.0"}`), + channels: "0.2", + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.2.2", + csvSpec: json.RawMessage(`{"version":"0.2.2","replaces":"testpkg.v0.2.1"}`), + channels: "0.2", + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.3.0", + csvSpec: json.RawMessage(`{"version":"0.3.0","replaces":"testpkg.v0.2.1"}`), + channels: "0.3", + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.3.0", + suffix: "overwrite", + csvSpec: json.RawMessage(`{"version":"0.3.0","replaces":""}`), + channels: "0.3", + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.3.0", + csvSpec: json.RawMessage(`{"version":"0.3.0","replaces":"testpkg.v0.2.0"}`), + suffix: "overwrite-replaces-0.2.0", + channels: "0.3", + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.3.1", + csvSpec: json.RawMessage(`{"version":"0.3.1","replaces":"testpkg.v0.3.0"}`), + channels: "0.3", + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.3.1", + suffix: "overwrite", + csvSpec: json.RawMessage(`{"version":"0.3.1","replaces":"testpkg.v0.3.0"}`), + channels: "0.3", + defaultChannel: "0.1", + }, + { + csvName: "testpkg.v0.3.2", + csvSpec: json.RawMessage(`{"version":"0.3.2","replaces":"testpkg.v0.3.1"}`), + channels: "0.3", + defaultChannel: "0.1", + }, + } + for _, b := range testBundles { + dir := b.csvName + if len(b.suffix) > 0 { + dir += "-" + b.suffix + } + _, _, err := newUnpackedTestBundle(tmpdir, dir, b.csvName, b.csvSpec, registry.Annotations{PackageName: "testpkg", Channels: b.channels, DefaultChannelName: b.defaultChannel}) + require.NoError(t, err) + } + type args struct { + dir string //directory to find the bundles existing []string deprecate []string add []string @@ -1138,6 +1293,7 @@ func TestAddAfterDeprecate(t *testing.T) { { description: "SimpleAdd", args: args{ + dir: "../../bundles/", existing: []string{ "prometheus.0.14.0", "prometheus.0.15.0", @@ -1173,6 +1329,7 @@ func TestAddAfterDeprecate(t *testing.T) { { description: "OverwriteLatest", args: args{ + dir: "../../bundles/", existing: []string{ "prometheus.0.14.0", "prometheus.0.15.0", @@ -1204,6 +1361,256 @@ func TestAddAfterDeprecate(t *testing.T) { }, }, }, + { + description: "TruncateTillBranchPoint", + args: args{ + dir: tmpdir, + existing: []string{ + "testpkg.v0.1.0", + "testpkg.v0.1.1", + "testpkg.v0.2.0", + "testpkg.v0.2.1", + "testpkg.v0.2.2", + "testpkg.v0.3.0", + "testpkg.v0.3.1", + "testpkg.v0.3.2", + }, + deprecate: []string{ + "quay.io/test/testpkg.v0.3.1", + }, + add: []string{ + "testpkg.v0.1.2", + }, + overwrite: nil, + }, + expected: expected{ + err: nil, + remaining: []string{ + "quay.io/test/testpkg.v0.1.0/0.1", + "quay.io/test/testpkg.v0.1.0/0.2", + "quay.io/test/testpkg.v0.1.1/0.1", + "quay.io/test/testpkg.v0.1.2/0.1", + "quay.io/test/testpkg.v0.2.0/0.2", + "quay.io/test/testpkg.v0.2.1/0.2", + "quay.io/test/testpkg.v0.2.2/0.2", + "quay.io/test/testpkg.v0.3.1/0.3", + "quay.io/test/testpkg.v0.3.2/0.3", + }, + deprecated: []string{ + "quay.io/test/testpkg.v0.3.1/0.3", + }, + pkgChannels: pkgChannel{ + "testpkg": []string{"0.1", "0.2", "0.3"}, + }, + }, + }, + { + description: "DeprecateAboveBranchPoint", + args: args{ + dir: tmpdir, + existing: []string{ + "testpkg.v0.1.0", + "testpkg.v0.1.1", + "testpkg.v0.2.0", + "testpkg.v0.2.1", + "testpkg.v0.2.2", + "testpkg.v0.3.0", + "testpkg.v0.3.1", + }, + deprecate: []string{ + "quay.io/test/testpkg.v0.3.0", + }, + add: []string{ + "testpkg.v0.1.2", + }, + overwrite: nil, + }, + expected: expected{ + err: nil, + remaining: []string{ + "quay.io/test/testpkg.v0.1.0/0.1", + "quay.io/test/testpkg.v0.1.0/0.2", + "quay.io/test/testpkg.v0.1.1/0.1", + "quay.io/test/testpkg.v0.1.2/0.1", + "quay.io/test/testpkg.v0.2.0/0.2", + "quay.io/test/testpkg.v0.2.1/0.2", + "quay.io/test/testpkg.v0.2.2/0.2", + "quay.io/test/testpkg.v0.3.0/0.3", + "quay.io/test/testpkg.v0.3.1/0.3", + }, + deprecated: []string{ + "quay.io/test/testpkg.v0.3.0/0.3", + }, + pkgChannels: pkgChannel{ + "testpkg": []string{"0.1", "0.2", "0.3"}, + }, + }, + }, + { + description: "ReplaceDeprecatedChannelHead", + args: args{ + dir: tmpdir, + existing: []string{ + "testpkg.v0.1.0", + "testpkg.v0.1.1", + "testpkg.v0.2.0", + "testpkg.v0.2.1", + "testpkg.v0.2.2", + "testpkg.v0.3.0", + }, + deprecate: []string{ + "quay.io/test/testpkg.v0.3.0", + }, + add: []string{ + "testpkg.v0.3.1", + "testpkg.v0.3.2", + }, + overwrite: nil, + }, + expected: expected{ + err: nil, + remaining: []string{ + "quay.io/test/testpkg.v0.1.0/0.1", + "quay.io/test/testpkg.v0.1.0/0.2", + "quay.io/test/testpkg.v0.1.1/0.1", + "quay.io/test/testpkg.v0.2.0/0.2", + "quay.io/test/testpkg.v0.2.1/0.2", + "quay.io/test/testpkg.v0.2.2/0.2", + "quay.io/test/testpkg.v0.3.0/0.3", + "quay.io/test/testpkg.v0.3.1/0.3", + "quay.io/test/testpkg.v0.3.2/0.3", + }, + deprecated: []string{ + "quay.io/test/testpkg.v0.3.0/0.3", + }, + pkgChannels: pkgChannel{ + "testpkg": []string{"0.1", "0.2", "0.3"}, + }, + }, + }, + { + description: "OverwriteDeprecatedChannelHead", + args: args{ + dir: tmpdir, + existing: []string{ + "testpkg.v0.1.0", + "testpkg.v0.1.1", + "testpkg.v0.2.0", + "testpkg.v0.2.1", + "testpkg.v0.2.2", + "testpkg.v0.3.0", + }, + deprecate: []string{ + "quay.io/test/testpkg.v0.3.0", + }, + add: []string{ + "testpkg.v0.3.0-overwrite", + }, + overwrite: map[string][]string{ + "testpkg": []string{"testpkg.v0.3.0"}, + }, + }, + expected: expected{ + err: nil, + remaining: []string{ + "quay.io/test/testpkg.v0.1.0/0.1", + "quay.io/test/testpkg.v0.1.0/0.2", + "quay.io/test/testpkg.v0.1.1/0.1", + "quay.io/test/testpkg.v0.2.0/0.2", + "quay.io/test/testpkg.v0.2.1/0.2", + "quay.io/test/testpkg.v0.2.2/0.2", + "quay.io/test/testpkg.v0.3.0-overwrite/0.3", + }, + deprecated: []string{}, + pkgChannels: pkgChannel{ + "testpkg": []string{"0.1", "0.2", "0.3"}, + }, + }, + }, + { + description: "OverwriteDeprecatedChannelHeadWithReplaces", + args: args{ + dir: tmpdir, + existing: []string{ + "testpkg.v0.1.0", + "testpkg.v0.1.1", + "testpkg.v0.2.0", + "testpkg.v0.2.1", + "testpkg.v0.2.2", + "testpkg.v0.3.0", + }, + deprecate: []string{ + "quay.io/test/testpkg.v0.3.0", + }, + add: []string{ + "testpkg.v0.3.0-overwrite-replaces-0.2.0", + }, + overwrite: map[string][]string{ + "testpkg": []string{"testpkg.v0.3.0"}, + }, + }, + expected: expected{ + err: nil, + remaining: []string{ + "quay.io/test/testpkg.v0.1.0/0.1", + "quay.io/test/testpkg.v0.1.0/0.2", + "quay.io/test/testpkg.v0.1.0/0.3", + "quay.io/test/testpkg.v0.1.1/0.1", + "quay.io/test/testpkg.v0.2.0/0.2", + "quay.io/test/testpkg.v0.2.0/0.3", + "quay.io/test/testpkg.v0.2.1/0.2", + "quay.io/test/testpkg.v0.2.2/0.2", + "quay.io/test/testpkg.v0.3.0-overwrite-replaces-0.2.0/0.3", + }, + deprecated: []string{}, + pkgChannels: pkgChannel{ + "testpkg": []string{"0.1", "0.2", "0.3"}, + }, + }, + }, + { + description: "OverwriteAboveDeprecated", + args: args{ + dir: tmpdir, + existing: []string{ + "testpkg.v0.1.0", + "testpkg.v0.1.1", + "testpkg.v0.2.0", + "testpkg.v0.2.1", + "testpkg.v0.2.2", + "testpkg.v0.3.0", + "testpkg.v0.3.1", + }, + deprecate: []string{ + "quay.io/test/testpkg.v0.3.0", + }, + add: []string{ + "testpkg.v0.3.1-overwrite", + }, + overwrite: map[string][]string{ + "testpkg": []string{"testpkg.v0.3.1"}, + }, + }, + expected: expected{ + err: nil, + remaining: []string{ + "quay.io/test/testpkg.v0.1.0/0.1", + "quay.io/test/testpkg.v0.1.0/0.2", + "quay.io/test/testpkg.v0.1.1/0.1", + "quay.io/test/testpkg.v0.2.0/0.2", + "quay.io/test/testpkg.v0.2.1/0.2", + "quay.io/test/testpkg.v0.2.2/0.2", + "quay.io/test/testpkg.v0.3.0/0.3", + "quay.io/test/testpkg.v0.3.1-overwrite/0.3", + }, + deprecated: []string{ + "quay.io/test/testpkg.v0.3.0/0.3", + }, + pkgChannels: pkgChannel{ + "testpkg": []string{"0.1", "0.2", "0.3"}, + }, + }, + }, } for _, tt := range tests { @@ -1224,15 +1631,7 @@ func TestAddAfterDeprecate(t *testing.T) { populate := func(add []string, overwrite map[string][]string) error { addRefs := map[image.Reference]string{} for _, a := range add { - addRefs[image.SimpleReference("quay.io/test/"+a)] = "../../bundles/" + a - } - - overwriteRefs := map[string]map[image.Reference]string{} - for pkg, pkgOverwrite := range overwrite { - overwriteRefs[pkg] = map[image.Reference]string{} - for _, o := range pkgOverwrite { - overwriteRefs[pkg][image.SimpleReference("quay.io/test/"+o)] = "../../bundles/" + o - } + addRefs[image.SimpleReference("quay.io/test/"+a)] = filepath.Join(tt.args.dir, a) } return registry.NewDirectoryPopulator( @@ -1240,9 +1639,7 @@ func TestAddAfterDeprecate(t *testing.T) { graphLoader, query, addRefs, - overwriteRefs, - len(overwriteRefs) > 0, - ).Populate(registry.ReplacesMode) + overwrite).Populate(registry.ReplacesMode) } // Initialize index with some bundles @@ -1302,7 +1699,7 @@ func TestOverwrite(t *testing.T) { type args struct { firstAdd map[image.Reference]string secondAdd map[image.Reference]string - overwrites map[string]map[image.Reference]string + overwrites map[string][]string } type pkgChannel map[string][]string type expected struct { @@ -1360,7 +1757,7 @@ func TestOverwrite(t *testing.T) { image.SimpleReference("quay.io/test/new-etcd.0.9.0"): "testdata/overwrite/etcd.0.9.0", image.SimpleReference("quay.io/test/prometheus.0.22.2"): "../../bundles/prometheus.0.22.2", }, - overwrites: map[string]map[image.Reference]string{"etcd": {}}, + overwrites: map[string][]string{"etcd": {"etcdoperator.v0.9.0"}}, }, expected: expected{ errs: nil, @@ -1399,7 +1796,7 @@ func TestOverwrite(t *testing.T) { image.SimpleReference("quay.io/test/new-etcd.0.9.2"): "testdata/overwrite/etcd.0.9.2", image.SimpleReference("quay.io/test/prometheus.0.22.2"): "../../bundles/prometheus.0.22.2", }, - overwrites: map[string]map[image.Reference]string{"etcd": getBundleRefs([]string{"etcd.0.9.0"})}, + overwrites: map[string][]string{"etcd": []string{"etcdoperator.v0.9.2"}}, }, expected: expected{ errs: nil, @@ -1440,7 +1837,7 @@ func TestOverwrite(t *testing.T) { image.SimpleReference("quay.io/test/etcd.0.9.2"): "../../bundles/etcd.0.9.2", image.SimpleReference("quay.io/test/new-prometheus.0.22.2"): "testdata/overwrite/prometheus.0.22.2", }, - overwrites: map[string]map[image.Reference]string{"prometheus": getBundleRefs([]string{"prometheus.0.14.0", "prometheus.0.15.0"})}, + overwrites: map[string][]string{"prometheus": []string{"prometheusoperator.0.22.2"}}, }, expected: expected{ errs: nil, @@ -1485,7 +1882,7 @@ func TestOverwrite(t *testing.T) { image.SimpleReference("quay.io/test/etcd.0.9.2"): "../../bundles/etcd.0.9.2", image.SimpleReference("quay.io/test/new-prometheus.0.15.0"): "testdata/overwrite/prometheus.0.15.0", }, - overwrites: map[string]map[image.Reference]string{"prometheus": getBundleRefs([]string{"prometheus.0.14.0"})}, + overwrites: map[string][]string{"prometheus": []string{"prometheusoperator.0.15.0"}}, }, expected: expected{ errs: nil, @@ -1526,7 +1923,7 @@ func TestOverwrite(t *testing.T) { image.SimpleReference("quay.io/test/etcd.0.9.2"): "../../bundles/etcd.0.9.2", image.SimpleReference("quay.io/test/new-prometheus.0.15.0"): "testdata/overwrite/prometheus.0.15.0", }, - overwrites: map[string]map[image.Reference]string{"prometheus": getBundleRefs([]string{"prometheus.0.14.0"})}, + overwrites: map[string][]string{"prometheus": []string{"prometheus.0.14.0"}}, }, expected: expected{ errs: []error{registry.OverwriteErr{ErrorString: "Cannot overwrite a bundle that is not at the head of a channel using --overwrite-latest"}}, @@ -1556,9 +1953,9 @@ func TestOverwrite(t *testing.T) { image.SimpleReference("quay.io/test/new-etcd.0.9.2"): "testdata/overwrite/etcd.0.9.2", image.SimpleReference("quay.io/test/new-prometheus.0.22.2"): "testdata/overwrite/prometheus.0.22.2", }, - overwrites: map[string]map[image.Reference]string{ - "prometheus": getBundleRefs([]string{"prometheus.0.14.0", "prometheus.0.15.0"}), - "etcd": getBundleRefs([]string{"etcd.0.9.0"}), + overwrites: map[string][]string{ + "prometheus": []string{"prometheusoperator.0.22.2"}, + "etcd": []string{"etcdoperator.v0.9.2"}, }, }, expected: expected{ @@ -1602,9 +1999,7 @@ func TestOverwrite(t *testing.T) { image.SimpleReference("quay.io/test/new-etcd.0.9.2"): "testdata/overwrite/etcd.0.9.2", image.SimpleReference("quay.io/test/new-new-etcd.0.9.2"): "testdata/overwrite/etcd.0.9.2", }, - overwrites: map[string]map[image.Reference]string{ - "etcd": getBundleRefs([]string{"etcd.0.9.0"}), - }, + overwrites: map[string][]string{"etcd": []string{"etcd.0.9.0"}}, }, expected: expected{ errs: []error{registry.OverwriteErr{ErrorString: "Cannot overwrite more than one bundle at a time for a given package using --overwrite-latest"}}, @@ -1654,14 +2049,13 @@ func TestOverwrite(t *testing.T) { query := sqlite.NewSQLLiteQuerierFromDb(db) - populate := func(bundles map[image.Reference]string, overwrites map[string]map[image.Reference]string) error { + populate := func(bundles map[image.Reference]string, overwrites map[string][]string) error { return registry.NewDirectoryPopulator( store, graphLoader, query, bundles, - overwrites, - true).Populate(registry.ReplacesMode) + overwrites).Populate(registry.ReplacesMode) } require.NoError(t, populate(tt.args.firstAdd, nil)) @@ -2491,7 +2885,7 @@ func TestSubstitutesFor(t *testing.T) { graphLoader, query, refMap, - make(map[string]map[image.Reference]string, 0), false).Populate(registry.ReplacesMode) + nil).Populate(registry.ReplacesMode) } // Initialize index with some bundles require.NoError(t, populate(tt.args.bundles)) @@ -2615,9 +3009,148 @@ func TestEnableAlpha(t *testing.T) { graphLoader, query, refMap, - make(map[string]map[image.Reference]string, 0), false).Populate(registry.ReplacesMode) + nil).Populate(registry.ReplacesMode) } require.Equal(t, tt.expected.err, populate(tt.args.bundles)) }) } } + +func newUnpackedTestBundle(root, dir, name string, csvSpec json.RawMessage, annotations registry.Annotations) (string, func(), error) { + bundleDir := filepath.Join(root, dir) + cleanup := func() { + os.RemoveAll(bundleDir) + } + if err := os.Mkdir(bundleDir, 0755); err != nil { + return bundleDir, cleanup, err + } + if err := os.Mkdir(filepath.Join(bundleDir, bundle.ManifestsDir), 0755); err != nil { + return bundleDir, cleanup, err + } + if err := os.Mkdir(filepath.Join(bundleDir, bundle.MetadataDir), 0755); err != nil { + return bundleDir, cleanup, err + } + if len(csvSpec) == 0 { + csvSpec = json.RawMessage(`{}`) + } + + rawCSV, err := json.Marshal(registry.ClusterServiceVersion{ + TypeMeta: v1.TypeMeta{ + Kind: sqlite.ClusterServiceVersionKind, + }, + ObjectMeta: v1.ObjectMeta{ + Name: name, + }, + Spec: csvSpec, + }) + if err != nil { + return bundleDir, cleanup, err + } + + rawObj := unstructured.Unstructured{} + if err := json.Unmarshal(rawCSV, &rawObj); err != nil { + return bundleDir, cleanup, err + } + rawObj.SetCreationTimestamp(v1.Time{}) + + jsonout, err := rawObj.MarshalJSON() + out, err := yaml.JSONToYAML(jsonout) + if err != nil { + return bundleDir, cleanup, err + } + if err := ioutil.WriteFile(filepath.Join(bundleDir, bundle.ManifestsDir, "csv.yaml"), out, 0666); err != nil { + return bundleDir, cleanup, err + } + + out, err = yaml.Marshal(registry.AnnotationsFile{Annotations: annotations}) + if err != nil { + return bundleDir, cleanup, err + } + if err := ioutil.WriteFile(filepath.Join(bundleDir, bundle.MetadataDir, "annotations.yaml"), out, 0666); err != nil { + return bundleDir, cleanup, err + } + return bundleDir, cleanup, nil +} + +func TestValidateEdgeBundlePackage(t *testing.T) { + newInput := func(name, versionString, pkg, defaultChannel, channels, replaces string, skips []string) *registry.ImageInput { + v, err := semver.Parse(versionString) + require.NoError(t, err) + + spec := v1alpha1.ClusterServiceVersionSpec{ + Replaces: replaces, + Skips: skips, + Version: version.OperatorVersion{v}, + } + specJson, err := json.Marshal(&spec) + require.NoError(t, err) + + rawCSV, err := json.Marshal(registry.ClusterServiceVersion{ + TypeMeta: v1.TypeMeta{ + Kind: sqlite.ClusterServiceVersionKind, + }, + ObjectMeta: v1.ObjectMeta{ + Name: name, + }, + Spec: specJson, + }) + + rawObj := unstructured.Unstructured{} + require.NoError(t, json.Unmarshal(rawCSV, &rawObj)) + rawObj.SetCreationTimestamp(v1.Time{}) + + jsonout, err := rawObj.MarshalJSON() + require.NoError(t, err) + + b, err := registry.NewBundleFromStrings(name, versionString, pkg, defaultChannel, channels, string(jsonout)) + require.NoError(t, err) + return ®istry.ImageInput{Bundle: b} + } + + logrus.SetLevel(logrus.DebugLevel) + db, cleanup := CreateTestDb(t) + defer cleanup() + + store, err := createAndPopulateDB(db) + require.NoError(t, err) + + r := registry.NewDirectoryPopulator(nil, nil, store, nil, nil) + + tests := []struct { + name string + args []*registry.ImageInput + wantErr error + }{ + { + name: "conflictInAdded", + args: []*registry.ImageInput{ + newInput("b1", "0.0.1", "p1", "a", "a", "", []string{}), + newInput("b2", "0.0.2", "p2", "a", "a", "", []string{"b1"}), + }, + wantErr: fmt.Errorf("bundle b1 must belong to exactly one package, found on: [p1 p2]"), + }, + { + name: "conflictWithExisting", + args: []*registry.ImageInput{ + newInput("b1", "0.0.1", "p1", "a", "a", "", []string{"etcdoperator.v0.9.0"}), + }, + wantErr: fmt.Errorf("bundle etcdoperator.v0.9.0 belongs to package etcd on index, cannot be added as an edge for package p1"), + }, + { + name: "passForNonConflicting", + args: []*registry.ImageInput{ + newInput("b1", "0.0.1", "etcd", "a", "a", "", []string{"etcdoperator.v0.9.0"}), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := r.ValidateEdgeBundlePackage(tt.args) + if tt.wantErr == nil { + require.NoError(t, err) + return + } + require.EqualError(t, err, tt.wantErr.Error()) + }) + } +} diff --git a/staging/operator-registry/pkg/registry/query.go b/staging/operator-registry/pkg/registry/query.go index 00b654b99a..4ccf7eba4f 100644 --- a/staging/operator-registry/pkg/registry/query.go +++ b/staging/operator-registry/pkg/registry/query.go @@ -2,7 +2,10 @@ package registry import ( "context" + "encoding/json" "fmt" + "os" + "path/filepath" "sort" "github.com/operator-framework/operator-registry/alpha/model" @@ -11,6 +14,19 @@ import ( type Querier struct { pkgs model.Model + + tmpDir string + apiBundles map[apiBundleKey]string +} + +func (q Querier) Close() error { + return os.RemoveAll(q.tmpDir) +} + +type apiBundleKey struct { + pkgName string + chName string + name string } type SliceBundleSender []*api.Bundle @@ -23,10 +39,60 @@ func (s *SliceBundleSender) Send(b *api.Bundle) error { var _ GRPCQuery = &Querier{} -func NewQuerier(packages model.Model) *Querier { - return &Querier{ - pkgs: packages, +func NewQuerier(packages model.Model) (*Querier, error) { + q := &Querier{} + + tmpDir, err := os.MkdirTemp("", "opm-registry-querier-") + if err != nil { + return nil, err } + q.tmpDir = tmpDir + + q.apiBundles = map[apiBundleKey]string{} + for _, pkg := range packages { + for _, ch := range pkg.Channels { + for _, b := range ch.Bundles { + apiBundle, err := api.ConvertModelBundleToAPIBundle(*b) + if err != nil { + return q, err + } + jsonBundle, err := json.Marshal(apiBundle) + if err != nil { + return q, err + } + filename := filepath.Join(tmpDir, fmt.Sprintf("%s_%s_%s.json", pkg.Name, ch.Name, b.Name)) + if err := os.WriteFile(filename, jsonBundle, 0666); err != nil { + return q, err + } + q.apiBundles[apiBundleKey{pkg.Name, ch.Name, b.Name}] = filename + packages[pkg.Name].Channels[ch.Name].Bundles[b.Name] = &model.Bundle{ + Package: pkg, + Channel: ch, + Name: b.Name, + Replaces: b.Replaces, + Skips: b.Skips, + } + } + } + } + q.pkgs = packages + return q, nil +} + +func (q Querier) loadAPIBundle(k apiBundleKey) (*api.Bundle, error) { + filename, ok := q.apiBundles[k] + if !ok { + return nil, fmt.Errorf("package %q, channel %q, bundle %q not found", k.pkgName, k.chName, k.name) + } + d, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + var b api.Bundle + if err := json.Unmarshal(d, &b); err != nil { + return nil, err + } + return &b, nil } func (q Querier) ListPackages(_ context.Context) ([]string, error) { @@ -52,7 +118,7 @@ func (q Querier) SendBundles(_ context.Context, s BundleSender) error { for _, pkg := range q.pkgs { for _, ch := range pkg.Channels { for _, b := range ch.Bundles { - apiBundle, err := api.ConvertModelBundleToAPIBundle(*b) + apiBundle, err := q.loadAPIBundle(apiBundleKey{pkg.Name, ch.Name, b.Name}) if err != nil { return fmt.Errorf("convert bundle %q: %v", b.Name, err) } @@ -110,7 +176,7 @@ func (q Querier) GetBundle(_ context.Context, pkgName, channelName, csvName stri if !ok { return nil, fmt.Errorf("package %q, channel %q, bundle %q not found", pkgName, channelName, csvName) } - apiBundle, err := api.ConvertModelBundleToAPIBundle(*b) + apiBundle, err := q.loadAPIBundle(apiBundleKey{pkg.Name, ch.Name, b.Name}) if err != nil { return nil, fmt.Errorf("convert bundle %q: %v", b.Name, err) } @@ -134,7 +200,7 @@ func (q Querier) GetBundleForChannel(_ context.Context, pkgName string, channelN if err != nil { return nil, fmt.Errorf("package %q, channel %q has invalid head: %v", pkgName, channelName, err) } - apiBundle, err := api.ConvertModelBundleToAPIBundle(*head) + apiBundle, err := q.loadAPIBundle(apiBundleKey{pkg.Name, ch.Name, head.Name}) if err != nil { return nil, fmt.Errorf("convert bundle %q: %v", head.Name, err) } @@ -177,7 +243,7 @@ func (q Querier) GetBundleThatReplaces(_ context.Context, name, pkgName, channel // implementation to be non-deterministic as well. for _, b := range ch.Bundles { if bundleReplaces(*b, name) { - apiBundle, err := api.ConvertModelBundleToAPIBundle(*b) + apiBundle, err := q.loadAPIBundle(apiBundleKey{pkg.Name, ch.Name, b.Name}) if err != nil { return nil, fmt.Errorf("convert bundle %q: %v", b.Name, err) } @@ -197,7 +263,7 @@ func (q Querier) GetChannelEntriesThatProvide(_ context.Context, group, version, for _, pkg := range q.pkgs { for _, ch := range pkg.Channels { for _, b := range ch.Bundles { - provides, err := doesModelBundleProvide(*b, group, version, kind) + provides, err := q.doesModelBundleProvide(*b, group, version, kind) if err != nil { return nil, err } @@ -236,7 +302,7 @@ func (q Querier) GetLatestChannelEntriesThatProvide(_ context.Context, group, ve return nil, fmt.Errorf("package %q, channel %q has invalid head: %v", pkg.Name, ch.Name, err) } - provides, err := doesModelBundleProvide(*b, group, version, kind) + provides, err := q.doesModelBundleProvide(*b, group, version, kind) if err != nil { return nil, err } @@ -278,8 +344,8 @@ func (q Querier) GetBundleThatProvides(ctx context.Context, group, version, kind return nil, fmt.Errorf("no entry found that provides group:%q version:%q kind:%q", group, version, kind) } -func doesModelBundleProvide(b model.Bundle, group, version, kind string) (bool, error) { - apiBundle, err := api.ConvertModelBundleToAPIBundle(b) +func (q Querier) doesModelBundleProvide(b model.Bundle, group, version, kind string) (bool, error) { + apiBundle, err := q.loadAPIBundle(apiBundleKey{b.Package.Name, b.Channel.Name, b.Name}) if err != nil { return false, fmt.Errorf("convert bundle %q: %v", b.Name, err) } diff --git a/staging/operator-registry/pkg/registry/query_test.go b/staging/operator-registry/pkg/registry/query_test.go index 4a3988b980..1ba1613f36 100644 --- a/staging/operator-registry/pkg/registry/query_test.go +++ b/staging/operator-registry/pkg/registry/query_test.go @@ -10,9 +10,9 @@ import ( "github.com/operator-framework/operator-registry/alpha/declcfg" ) -var testModelQuerier = genTestModelQuerier() - func TestQuerier_GetBundle(t *testing.T) { + testModelQuerier := genTestModelQuerier(t) + defer testModelQuerier.Close() b, err := testModelQuerier.GetBundle(context.TODO(), "etcd", "singlenamespace-alpha", "etcdoperator.v0.9.4") require.NoError(t, err) require.Equal(t, b.PackageName, "etcd") @@ -21,6 +21,8 @@ func TestQuerier_GetBundle(t *testing.T) { } func TestQuerier_GetBundleForChannel(t *testing.T) { + testModelQuerier := genTestModelQuerier(t) + defer testModelQuerier.Close() b, err := testModelQuerier.GetBundleForChannel(context.TODO(), "etcd", "singlenamespace-alpha") require.NoError(t, err) require.NotNil(t, b) @@ -30,6 +32,8 @@ func TestQuerier_GetBundleForChannel(t *testing.T) { } func TestQuerier_GetBundleThatProvides(t *testing.T) { + testModelQuerier := genTestModelQuerier(t) + defer testModelQuerier.Close() b, err := testModelQuerier.GetBundleThatProvides(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdBackup") require.NoError(t, err) require.NotNil(t, b) @@ -39,6 +43,8 @@ func TestQuerier_GetBundleThatProvides(t *testing.T) { } func TestQuerier_GetBundleThatReplaces(t *testing.T) { + testModelQuerier := genTestModelQuerier(t) + defer testModelQuerier.Close() b, err := testModelQuerier.GetBundleThatReplaces(context.TODO(), "etcdoperator.v0.9.0", "etcd", "singlenamespace-alpha") require.NoError(t, err) require.NotNil(t, b) @@ -48,6 +54,8 @@ func TestQuerier_GetBundleThatReplaces(t *testing.T) { } func TestQuerier_GetChannelEntriesThatProvide(t *testing.T) { + testModelQuerier := genTestModelQuerier(t) + defer testModelQuerier.Close() entries, err := testModelQuerier.GetChannelEntriesThatProvide(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdBackup") require.NoError(t, err) require.NotNil(t, entries) @@ -92,6 +100,8 @@ func TestQuerier_GetChannelEntriesThatProvide(t *testing.T) { } func TestQuerier_GetChannelEntriesThatReplace(t *testing.T) { + testModelQuerier := genTestModelQuerier(t) + defer testModelQuerier.Close() entries, err := testModelQuerier.GetChannelEntriesThatReplace(context.TODO(), "etcdoperator.v0.9.0") require.NoError(t, err) require.NotNil(t, entries) @@ -112,6 +122,8 @@ func TestQuerier_GetChannelEntriesThatReplace(t *testing.T) { } func TestQuerier_GetLatestChannelEntriesThatProvide(t *testing.T) { + testModelQuerier := genTestModelQuerier(t) + defer testModelQuerier.Close() entries, err := testModelQuerier.GetLatestChannelEntriesThatProvide(context.TODO(), "etcd.database.coreos.com", "v1beta2", "EtcdBackup") require.NoError(t, err) require.NotNil(t, entries) @@ -132,6 +144,8 @@ func TestQuerier_GetLatestChannelEntriesThatProvide(t *testing.T) { } func TestQuerier_GetPackage(t *testing.T) { + testModelQuerier := genTestModelQuerier(t) + defer testModelQuerier.Close() p, err := testModelQuerier.GetPackage(context.TODO(), "etcd") require.NoError(t, err) require.NotNil(t, p) @@ -161,6 +175,8 @@ func TestQuerier_GetPackage(t *testing.T) { } func TestQuerier_ListBundles(t *testing.T) { + testModelQuerier := genTestModelQuerier(t) + defer testModelQuerier.Close() bundles, err := testModelQuerier.ListBundles(context.TODO()) require.NoError(t, err) require.NotNil(t, bundles) @@ -172,22 +188,27 @@ func TestQuerier_ListBundles(t *testing.T) { } func TestQuerier_ListPackages(t *testing.T) { + testModelQuerier := genTestModelQuerier(t) + defer testModelQuerier.Close() packages, err := testModelQuerier.ListPackages(context.TODO()) require.NoError(t, err) require.NotNil(t, packages) require.Equal(t, 2, len(packages)) } -func genTestModelQuerier() *Querier { +func genTestModelQuerier(t *testing.T) *Querier { + t.Helper() + cfg, err := declcfg.LoadFS(validFS) - if err != nil { - panic(err) - } + require.NoError(t, err) + m, err := declcfg.ConvertToModel(*cfg) - if err != nil { - panic(err) - } - return NewQuerier(m) + require.NoError(t, err) + + reg, err := NewQuerier(m) + require.NoError(t, err) + + return reg } var validFS = fstest.MapFS{ diff --git a/staging/operator-registry/pkg/registry/registry_to_model.go b/staging/operator-registry/pkg/registry/registry_to_model.go index 2deba2c15c..b45fdf8828 100644 --- a/staging/operator-registry/pkg/registry/registry_to_model.go +++ b/staging/operator-registry/pkg/registry/registry_to_model.go @@ -4,90 +4,11 @@ import ( "encoding/json" "fmt" "sort" - "strings" - "github.com/operator-framework/operator-registry/alpha/model" "github.com/operator-framework/operator-registry/alpha/property" ) -func ConvertRegistryBundleToModelBundles(b *Bundle) ([]model.Bundle, error) { - var bundles []model.Bundle - desc, err := b.csv.GetDescription() - if err != nil { - return nil, fmt.Errorf("Could not get description from bundle CSV:%s", err) - } - - i, err := b.csv.GetIcons() - if err != nil { - return nil, fmt.Errorf("Could not get icon from bundle CSV:%s", err) - } - mIcon := &model.Icon{ - MediaType: "", - Data: []byte{}, - } - if len(i) > 0 { - mIcon.MediaType = i[0].MediaType - mIcon.Data = []byte(i[0].Base64data) - } - - pkg := &model.Package{ - Name: b.Annotations.PackageName, - Description: desc, - Icon: mIcon, - Channels: make(map[string]*model.Channel), - } - - mb, err := registryBundleToModelBundle(b) - mb.Package = pkg - if err != nil { - return nil, err - } - - for _, ch := range extractChannels(b.Annotations.Channels) { - newCh := &model.Channel{ - Name: ch, - } - chBundle := mb - chBundle.Channel = newCh - bundles = append(bundles, *chBundle) - } - return bundles, nil -} - -func registryBundleToModelBundle(b *Bundle) (*model.Bundle, error) { - bundleProps, err := PropertiesFromBundle(b) - if err != nil { - return nil, fmt.Errorf("error converting properties for internal model: %v", err) - } - - csv, err := b.ClusterServiceVersion() - if err != nil { - return nil, fmt.Errorf("Could not get CVS for bundle: %s", err) - } - replaces, err := csv.GetReplaces() - if err != nil { - return nil, fmt.Errorf("Could not get Replaces from CSV for bundle: %s", err) - } - skips, err := csv.GetSkips() - if err != nil { - return nil, fmt.Errorf("Could not get Skips from CSV for bundle: %s", err) - } - relatedImages, err := convertToModelRelatedImages(csv) - if err != nil { - return nil, fmt.Errorf("Could not get Related images from bundle: %v", err) - } - - return &model.Bundle{ - Name: csv.Name, - Image: b.BundleImage, - Replaces: replaces, - Skips: skips, - Properties: bundleProps, - RelatedImages: relatedImages, - }, nil -} - -func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { +func ObjectsAndPropertiesFromBundle(b *Bundle) ([]string, []property.Property, error) { providedGVKs := map[property.GVK]struct{}{} requiredGVKs := map[property.GVKRequired]struct{}{} @@ -99,14 +20,14 @@ func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { case property.TypeGVK: var v property.GVK if err := json.Unmarshal(p.Value, &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + return nil, nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} } k := property.GVK{Group: v.Group, Kind: v.Kind, Version: v.Version} providedGVKs[k] = struct{}{} case property.TypePackage: var v property.Package if err := json.Unmarshal(p.Value, &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + return nil, nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} } p := property.MustBuildPackage(v.PackageName, v.Version) packageProvidedProperty = &p @@ -124,14 +45,14 @@ func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { case property.TypeGVK: var v property.GVK if err := json.Unmarshal(p.Value, &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + return nil, nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} } k := property.GVKRequired{Group: v.Group, Kind: v.Kind, Version: v.Version} requiredGVKs[k] = struct{}{} case property.TypePackage: var v property.Package if err := json.Unmarshal(p.Value, &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + return nil, nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} } packageRequiredProps = append(packageRequiredProps, property.MustBuildPackageRequired(v.PackageName, v.Version)) } @@ -139,12 +60,12 @@ func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { version, err := b.Version() if err != nil { - return nil, fmt.Errorf("get version: %v", err) + return nil, nil, fmt.Errorf("get version: %v", err) } providedApis, err := b.ProvidedAPIs() if err != nil { - return nil, fmt.Errorf("get provided apis: %v", err) + return nil, nil, fmt.Errorf("get provided apis: %v", err) } for p := range providedApis { @@ -155,7 +76,7 @@ func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { } requiredApis, err := b.RequiredAPIs() if err != nil { - return nil, fmt.Errorf("get required apis: %v", err) + return nil, nil, fmt.Errorf("get required apis: %v", err) } for p := range requiredApis { k := property.GVKRequired{Group: p.Group, Kind: p.Kind, Version: p.Version} @@ -164,67 +85,42 @@ func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { } } - var out []property.Property + var ( + props []property.Property + objects []string + ) + for _, obj := range b.Objects { + objData, err := json.Marshal(obj) + if err != nil { + return nil, nil, fmt.Errorf("marshal object %s/%s (%s) to json: %v", obj.GetName(), obj.GetNamespace(), obj.GroupVersionKind(), err) + } + props = append(props, property.MustBuildBundleObjectData(objData)) + objects = append(objects, string(objData)) + } + if packageProvidedProperty == nil { p := property.MustBuildPackage(b.Package, version) packageProvidedProperty = &p } - out = append(out, *packageProvidedProperty) + props = append(props, *packageProvidedProperty) for p := range providedGVKs { - out = append(out, property.MustBuildGVK(p.Group, p.Version, p.Kind)) + props = append(props, property.MustBuildGVK(p.Group, p.Version, p.Kind)) } for p := range requiredGVKs { - out = append(out, property.MustBuildGVKRequired(p.Group, p.Version, p.Kind)) + props = append(props, property.MustBuildGVKRequired(p.Group, p.Version, p.Kind)) } - out = append(out, packageRequiredProps...) - out = append(out, otherProps...) + props = append(props, packageRequiredProps...) + props = append(props, otherProps...) - sort.Slice(out, func(i, j int) bool { - if out[i].Type != out[j].Type { - return out[i].Type < out[j].Type + sort.Slice(props, func(i, j int) bool { + if props[i].Type != props[j].Type { + return props[i].Type < props[j].Type } - return string(out[i].Value) < string(out[j].Value) + return string(props[i].Value) < string(props[j].Value) }) - return out, nil -} - -func convertToModelRelatedImages(csv *ClusterServiceVersion) ([]model.RelatedImage, error) { - var objmap map[string]*json.RawMessage - if err := json.Unmarshal(csv.Spec, &objmap); err != nil { - return nil, err - } - - rawValue, ok := objmap[relatedImages] - if !ok || rawValue == nil { - return nil, nil - } - - type relatedImage struct { - Name string `json:"name"` - Ref string `json:"image"` - } - var relatedImages []relatedImage - if err := json.Unmarshal(*rawValue, &relatedImages); err != nil { - return nil, err - } - mrelatedImages := []model.RelatedImage{} - for _, img := range relatedImages { - mrelatedImages = append(mrelatedImages, model.RelatedImage{Name: img.Name, Image: img.Ref}) - } - return mrelatedImages, nil -} - -func extractChannels(annotationChannels string) []string { - var channels []string - for _, ch := range strings.Split(annotationChannels, ",") { - c := strings.TrimSpace(ch) - if c != "" { - channels = append(channels, ch) - } - } - return channels + return objects, props, nil } diff --git a/staging/operator-registry/pkg/registry/registry_to_model_test.go b/staging/operator-registry/pkg/registry/registry_to_model_test.go index 265894b02d..14e370d726 100644 --- a/staging/operator-registry/pkg/registry/registry_to_model_test.go +++ b/staging/operator-registry/pkg/registry/registry_to_model_test.go @@ -6,59 +6,47 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/operator-framework/operator-registry/alpha/model" "github.com/operator-framework/operator-registry/alpha/property" "github.com/operator-framework/operator-registry/pkg/image" ) -func TestConvertRegistryBundleToModelBundle(t *testing.T) { - registryBundle, err := testRegistryBundle() - require.NoError(t, err) - expected := testModelBundle() +func TestObjectsAndPropertiesFromBundle(t *testing.T) { + registryBundle := testRegistryBundle(t) - actual, err := registryBundleToModelBundle(registryBundle) + actualObjs, actualProps, err := ObjectsAndPropertiesFromBundle(registryBundle) require.NoError(t, err) - assertEqualsModelBundle(t, expected, *actual) - - registryBundles, err := ConvertRegistryBundleToModelBundles(registryBundle) - assert.Equal(t, len(registryBundles), 2) + assert.ElementsMatch(t, testExpectedObjects(), actualObjs) + assert.ElementsMatch(t, testExpectedProperties(), actualProps) } -func testModelBundle() model.Bundle { - b := model.Bundle{ - Name: "etcdoperator.v0.9.2", - Image: "quay.io/operatorhubio/etcd:v0.9.2", - Replaces: "etcdoperator.v0.9.0", - Skips: []string{"etcdoperator.v0.9.1"}, - Properties: []property.Property{ - property.MustBuildPackage("etcd", "0.9.2"), - property.MustBuildGVKRequired("etcd.database.coreos.com", "v1beta2", "EtcdCluster"), - property.MustBuildGVKRequired("testapi.coreos.com", "v1", "testapi"), - property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdCluster"), - property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), - property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdRestore"), - }, - } - return b +const testBundleDir = "../../bundles/etcd.0.9.2" + +func testRegistryBundle(t *testing.T) *Bundle { + input, err := NewImageInput(image.SimpleReference("quay.io/operatorhubio/etcd:v0.9.2"), testBundleDir) + require.NoError(t, err) + return input.Bundle } -func testRegistryBundle() (*Bundle, error) { - input, err := NewImageInput(image.SimpleReference("quay.io/operatorhubio/etcd:v0.9.2"), "../../bundles/etcd.0.9.2") - if err != nil { - return nil, err +func testExpectedObjects() []string { + return []string{ + "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdbackups.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdBackup\",\"listKind\":\"EtcdBackupList\",\"plural\":\"etcdbackups\",\"singular\":\"etcdbackup\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", + "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdclusters.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdCluster\",\"listKind\":\"EtcdClusterList\",\"plural\":\"etcdclusters\",\"shortNames\":[\"etcdclus\",\"etcd\"],\"singular\":\"etcdcluster\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", + "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"annotations\":{\"alm-examples\":\"[{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdCluster\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example\\\",\\\"namespace\\\":\\\"default\\\"},\\\"spec\\\":{\\\"size\\\":3,\\\"version\\\":\\\"3.2.13\\\"}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdRestore\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"spec\\\":{\\\"etcdCluster\\\":{\\\"name\\\":\\\"example-etcd-cluster\\\"},\\\"backupStorageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}},{\\\"apiVersion\\\":\\\"etcd.database.coreos.com/v1beta2\\\",\\\"kind\\\":\\\"EtcdBackup\\\",\\\"metadata\\\":{\\\"name\\\":\\\"example-etcd-cluster-backup\\\"},\\\"spec\\\":{\\\"etcdEndpoints\\\":[\\\"\\u003cetcd-cluster-endpoints\\u003e\\\"],\\\"storageType\\\":\\\"S3\\\",\\\"s3\\\":{\\\"path\\\":\\\"\\u003cfull-s3-path\\u003e\\\",\\\"awsSecret\\\":\\\"\\u003caws-secret\\u003e\\\"}}}]\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.9.2\",\"namespace\":\"placeholder\"},\"spec\":{\"customresourcedefinitions\":{\"owned\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"displayName\":\"Resource Requirements\",\"path\":\"pod.resources\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}],\"statusDescriptors\":[{\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"displayName\":\"Member Status\",\"path\":\"members\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"description\":\"The service at which the running etcd cluster can be accessed.\",\"displayName\":\"Service\",\"path\":\"serviceName\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"description\":\"The current size of the etcd cluster.\",\"displayName\":\"Cluster Size\",\"path\":\"size\"},{\"description\":\"The current version of the etcd cluster.\",\"displayName\":\"Current Version\",\"path\":\"currentVersion\"},{\"description\":\"The target version of the etcd cluster, after upgrading.\",\"displayName\":\"Target Version\",\"path\":\"targetVersion\"},{\"description\":\"The current status of the etcd cluster.\",\"displayName\":\"Status\",\"path\":\"phase\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"description\":\"Explanation for the current status of the cluster.\",\"displayName\":\"Status Details\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to backup an etcd cluster.\",\"displayName\":\"etcd Backup\",\"kind\":\"EtcdBackup\",\"name\":\"etcdbackups.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"Specifies the endpoints of an etcd cluster.\",\"displayName\":\"etcd Endpoint(s)\",\"path\":\"etcdEndpoints\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the backup was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any backup related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"displayName\":\"etcd Restore\",\"kind\":\"EtcdRestore\",\"name\":\"etcdrestores.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"References the EtcdCluster which should be restored,\",\"displayName\":\"etcd Cluster\",\"path\":\"etcdCluster.name\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the restore was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any restore related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"}],\"required\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]}],\"version\":\"v1beta2\"}]},\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"displayName\":\"etcd\",\"icon\":[{\"base64data\":\"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\",\"mediatype\":\"image/png\"}],\"install\":{\"spec\":{\"deployments\":[{\"name\":\"etcd-operator\",\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"etcd-operator-alm-owned\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"etcd-operator-alm-owned\"},\"name\":\"etcd-operator-alm-owned\"},\"spec\":{\"containers\":[{\"command\":[\"etcd-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-operator\"},{\"command\":[\"etcd-backup-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-backup-operator\"},{\"command\":[\"etcd-restore-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-restore-operator\"}],\"serviceAccountName\":\"etcd-operator\"}}}}],\"permissions\":[{\"rules\":[{\"apiGroups\":[\"etcd.database.coreos.com\"],\"resources\":[\"etcdclusters\",\"etcdbackups\",\"etcdrestores\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"pods\",\"services\",\"endpoints\",\"persistentvolumeclaims\",\"events\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apps\"],\"resources\":[\"deployments\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\"]}],\"serviceAccountName\":\"etcd-operator\"}]},\"strategy\":\"deployment\"},\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"labels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"maintainers\":[{\"email\":\"support@coreos.com\",\"name\":\"CoreOS, Inc\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CoreOS, Inc\"},\"replaces\":\"etcdoperator.v0.9.0\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"skips\":[\"etcdoperator.v0.9.1\"],\"version\":\"0.9.2\"}}", + "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdrestores.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdRestore\",\"listKind\":\"EtcdRestoreList\",\"plural\":\"etcdrestores\",\"singular\":\"etcdrestore\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}", } - return input.Bundle, nil } -func assertEqualsModelBundle(t *testing.T, a, b model.Bundle) bool { - assert.ElementsMatch(t, a.Properties, b.Properties) - assert.ElementsMatch(t, a.Skips, b.Skips) - assert.ElementsMatch(t, a.RelatedImages, b.RelatedImages) - - a.Properties, b.Properties = nil, nil - a.Objects, b.Objects = nil, nil - a.Skips, b.Skips = nil, nil - a.RelatedImages, b.RelatedImages = nil, nil - - return assert.Equal(t, a, b) +func testExpectedProperties() []property.Property { + props := []property.Property{ + property.MustBuildPackage("etcd", "0.9.2"), + property.MustBuildGVKRequired("etcd.database.coreos.com", "v1beta2", "EtcdCluster"), + property.MustBuildGVKRequired("testapi.coreos.com", "v1", "testapi"), + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdCluster"), + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdBackup"), + property.MustBuildGVK("etcd.database.coreos.com", "v1beta2", "EtcdRestore"), + } + for _, obj := range testExpectedObjects() { + props = append(props, property.MustBuildBundleObjectData([]byte(obj))) + } + return props } diff --git a/staging/operator-registry/pkg/registry/types.go b/staging/operator-registry/pkg/registry/types.go index 719127cfba..d7d2585520 100644 --- a/staging/operator-registry/pkg/registry/types.go +++ b/staging/operator-registry/pkg/registry/types.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/blang/semver" + "github.com/blang/semver/v4" ) var ( diff --git a/staging/operator-registry/pkg/server/server_test.go b/staging/operator-registry/pkg/server/server_test.go index 9fba96a570..09117ee844 100644 --- a/staging/operator-registry/pkg/server/server_test.go +++ b/staging/operator-registry/pkg/server/server_test.go @@ -60,10 +60,10 @@ func dbStore(dbPath string) *sqlite.SQLQuerier { return store } -func cfgStore() *registry.Querier { +func cfgStore() (*registry.Querier, error) { tmpDir, err := ioutil.TempDir("", "server_test-") if err != nil { - logrus.Fatal(err) + return nil, err } defer os.RemoveAll(tmpDir) @@ -72,10 +72,13 @@ func cfgStore() *registry.Querier { dbStore := dbStore(dbFile) m, err := sqlite.ToModel(context.TODO(), dbStore) if err != nil { - logrus.Fatal(err) + return nil, err } - store := registry.NewQuerier(m) - return store + store, err := registry.NewQuerier(m) + if err != nil { + return nil, err + } + return store, nil } func server(store registry.GRPCQuery) *grpc.Server { @@ -86,7 +89,13 @@ func server(store registry.GRPCQuery) *grpc.Server { func TestMain(m *testing.M) { s1 := server(dbStore(dbName)) - s2 := server(cfgStore()) + + cfgQuerier, err := cfgStore() + defer cfgQuerier.Close() + if err != nil { + logrus.Fatalf("failed to create fbc querier: %v", err) + } + s2 := server(cfgQuerier) go func() { lis, err := net.Listen("tcp", dbPort) if err != nil { diff --git a/staging/operator-registry/pkg/sqlite/load.go b/staging/operator-registry/pkg/sqlite/load.go index 65185ee656..47540b1811 100644 --- a/staging/operator-registry/pkg/sqlite/load.go +++ b/staging/operator-registry/pkg/sqlite/load.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "github.com/blang/semver" + "github.com/blang/semver/v4" _ "github.com/mattn/go-sqlite3" utilerrors "k8s.io/apimachinery/pkg/util/errors" @@ -28,6 +28,12 @@ type MigratableLoader interface { var _ MigratableLoader = &sqlLoader{} +// startDepth is the depth that channel heads should be assigned +// in the channel_entry table. This const exists so that all +// add modes (replaces, semver, and semver-skippatch) are +// consistent. +const startDepth = 0 + func newSQLLoader(db *sql.DB, opts ...DbOption) (*sqlLoader, error) { options := defaultDBOptions() for _, o := range opts { @@ -424,7 +430,7 @@ func (s *sqlLoader) AddPackageChannelsFromGraph(graph *registry.Package) error { // update each channel's graph for channelName, channel := range graph.Channels { currentNode := channel.Head - depth := 1 + depth := startDepth var previousNodeID int64 @@ -495,7 +501,12 @@ func (s *sqlLoader) AddPackageChannelsFromGraph(graph *registry.Package) error { // we got to the end of the channel graph if nextNode.IsEmpty() { - if len(channel.Nodes) != depth { + // expectedDepth is: + // + - 1 + // For example, if the number of nodes is 3 and the startDepth is 0, the expected depth is 2 (0, 1, 2) + // If the number of nodes is 5 and the startDepth is 3, the expected depth is 7 (3, 4, 5, 6, 7) + expectedDepth := len(channel.Nodes) + startDepth - 1 + if expectedDepth != depth { err := fmt.Errorf("Invalid graph: some (non-bottom) nodes defined in the graph were not mentioned as replacements of any node") errs = append(errs, err) } @@ -608,7 +619,7 @@ func (s *sqlLoader) addPackageChannels(tx *sql.Tx, manifest registry.PackageMani } for _, c := range channels { - res, err := addChannelEntry.Exec(c.Name, manifest.PackageName, c.CurrentCSVName, 0) + res, err := addChannelEntry.Exec(c.Name, manifest.PackageName, c.CurrentCSVName, startDepth) if err != nil { errs = append(errs, fmt.Errorf("failed to add channel %q in package %q: %s", c.Name, manifest.PackageName, err.Error())) continue @@ -620,7 +631,10 @@ func (s *sqlLoader) addPackageChannels(tx *sql.Tx, manifest registry.PackageMani } channelEntryCSVName := c.CurrentCSVName - depth := 1 + + // depth is set to `startDepth + 1` here because we already added the channel head + // with depth `startDepth` above. + depth := startDepth + 1 // Since this loop depends on following 'replaces', keep track of where it's been replaceCycle := map[string]bool{channelEntryCSVName: true} @@ -644,6 +658,16 @@ func (s *sqlLoader) addPackageChannels(tx *sql.Tx, manifest registry.PackageMani break } + deprecated, err := s.deprecated(tx, channelEntryCSVName) + if err != nil { + errs = append(errs, err) + break + } + if deprecated { + // The package is truncated below this point, we're done! + break + } + for _, skip := range skips { // add dummy channel entry for the skipped version skippedChannelEntry, err := addChannelEntry.Exec(c.Name, manifest.PackageName, skip, depth) @@ -707,15 +731,6 @@ func (s *sqlLoader) addPackageChannels(tx *sql.Tx, manifest registry.PackageMani errs = append(errs, err) break } - deprecated, err := s.deprecated(tx, channelEntryCSVName) - if err != nil { - errs = append(errs, err) - break - } - if deprecated { - // The package is truncated below this point, we're done! - break - } if _, _, _, err := s.getBundleSkipsReplacesVersion(tx, replaces); err != nil { errs = append(errs, fmt.Errorf("Invalid bundle %s, replaces nonexistent bundle %s", c.CurrentCSVName, replaces)) break @@ -967,6 +982,7 @@ func (s *sqlLoader) RemovePackage(packageName string) error { if _, err := deleteChannel.Exec(packageName); err != nil { return err } + return tx.Commit() }(); err != nil { return err @@ -1279,38 +1295,35 @@ func (s *sqlLoader) addBundleProperties(tx *sql.Tx, bundle *registry.Bundle) err return nil } -func (s *sqlLoader) rmChannelEntry(tx *sql.Tx, csvName string) error { - rows, err := tx.Query(`SELECT entry_id FROM channel_entry WHERE operatorbundle_name=?`, csvName) - if err != nil { - return err - } - - var entryIDs []int64 - for rows.Next() { - var entryID sql.NullInt64 - rows.Scan(&entryID) - entryIDs = append(entryIDs, entryID.Int64) - } - if err := rows.Close(); err != nil { - return err +func (s *sqlLoader) rmSharedChannelEntry(tx *sql.Tx, csvName, unsharedCsv string) error { + if len(unsharedCsv) == 0 { + return nil } - updateChannelEntry, err := tx.Prepare(`UPDATE channel_entry SET replaces=NULL WHERE replaces=?`) - if err != nil { - return err - } - for _, id := range entryIDs { - if _, err := updateChannelEntry.Exec(id); err != nil { - updateChannelEntry.Close() - return err - } - } - err = updateChannelEntry.Close() + // remove any edges that replace bundle to be removed on the channels of the unsharedCsv + _, err := tx.Exec(` + UPDATE channel_entry + SET replaces=NULL + WHERE replaces IN ( + SELECT entry_id FROM channel_entry + WHERE operatorbundle_name = ? + AND channel_name IN ( + SELECT channel_name FROM channel_entry + WHERE operatorbundle_name = ? + ))`, csvName, unsharedCsv) if err != nil { return err } - _, err = tx.Exec(`DELETE FROM channel WHERE head_operatorbundle_name=?`, csvName) + // delete the channel entries on the shared channel list + _, err = tx.Exec(` + DELETE FROM channel_entry + WHERE operatorbundle_name = ? + AND channel_name IN ( + SELECT channel_name + FROM channel_entry + WHERE operatorbundle_name = ? + )`, csvName, unsharedCsv) if err != nil { return err } @@ -1318,82 +1331,124 @@ func (s *sqlLoader) rmChannelEntry(tx *sql.Tx, csvName string) error { return nil } -func getTailFromBundle(tx *sql.Tx, head string) (bundles []string, err error) { - getReplacesSkips := `SELECT replaces, skips FROM operatorbundle WHERE name=?` - isDefaultChannelHead := `SELECT head_operatorbundle_name FROM channel - INNER JOIN package ON channel.name = package.default_channel - WHERE channel.head_operatorbundle_name = ?` +type tailBundle struct { + name string + version string + bundlepath string + channels []string + replaces []string // in addition to the replaces chain, there may also be real skipped entries + replacedBy []string // to handle any chain where a skipped entry may be a part of another channel that should not be truncated +} - visited := map[string]struct{}{} - next := []string{head} +func getTailFromBundle(tx *sql.Tx, head string) (bundles map[string]tailBundle, err error) { + // traverse replaces chain and collect channel list for each bundle. + // This assumes that replaces chain for a bundle is the same across channels. + // only real bundles with entries in the operator_bundle table are returned. + getReplacesChain := ` + WITH RECURSIVE + replaces_entry (operatorbundle_name, replaces, replaced_by, channel_name, package_name) AS ( + SELECT channel_entry.operatorbundle_name, replaces.operatorbundle_name, replaced_by.operatorbundle_name, channel_entry.channel_name, channel_entry.package_name + FROM channel_entry + LEFT OUTER JOIN channel_entry AS replaces + ON replaces.entry_id = channel_entry.replaces + LEFT OUTER JOIN channel_entry AS replaced_by + ON channel_entry.entry_id = replaced_by.replaces + WHERE channel_entry.operatorbundle_name = ? + UNION + SELECT channel_entry.operatorbundle_name, replaces.operatorbundle_name, replaced_by.operatorbundle_name, channel_entry.channel_name, channel_entry.package_name + FROM channel_entry + JOIN replaces_entry + ON replaces_entry.replaces = channel_entry.operatorbundle_name + LEFT OUTER JOIN channel_entry AS replaces + ON channel_entry.replaces = replaces.entry_id + LEFT OUTER JOIN channel_entry AS replaced_by + ON channel_entry.entry_id = replaced_by.replaces + ) + SELECT + replaces_entry.operatorbundle_name, + operatorbundle.version, + operatorbundle.bundlepath, + GROUP_CONCAT(DISTINCT replaces_entry.channel_name), + GROUP_CONCAT(DISTINCT replaces_entry.replaces), + GROUP_CONCAT(DISTINCT replaces_entry.replaced_by) + FROM replaces_entry + LEFT OUTER JOIN operatorbundle + ON operatorbundle.name = replaces_entry.operatorbundle_name + GROUP BY replaces_entry.operatorbundle_name, replaces_entry.package_name + ORDER BY replaces_entry.channel_name, replaces_entry.replaces, replaces_entry.replaced_by` - for len(next) > 0 { - // Pop the next bundle off of the queue - bundle := next[0] - next = next[1:] // Potentially inefficient queue implementation, but this function is only used when deprecate is called + getDefaultChannelHead := ` + SELECT head_operatorbundle_name FROM channel + INNER JOIN package ON channel.name = package.default_channel AND channel.package_name = package.name + INNER JOIN channel_entry on channel.package_name = channel_entry.package_name + WHERE channel_entry.operatorbundle_name = ? + LIMIT 1` - // Check if next is the head of the defaultChannel - // If it is, the defaultChannel would be removed -- this is not allowed because we cannot know which channel to promote as the new default - var err error - if row := tx.QueryRow(isDefaultChannelHead, bundle); row != nil { - err = row.Scan(&sql.NullString{}) - } - if err == nil { - // A nil error indicates that next is the default channel head - return nil, registry.ErrRemovingDefaultChannelDuringDeprecation - } else if err != sql.ErrNoRows { - return nil, err - } - - rows, err := tx.QueryContext(context.TODO(), getReplacesSkips, bundle) - if err != nil { - return nil, err - } + row := tx.QueryRow(getDefaultChannelHead, head) + if row == nil { + return nil, fmt.Errorf("could not find default channel head for %s", head) + } + var defaultChannelHead sql.NullString + err = row.Scan(&defaultChannelHead) + if err != nil { + return nil, fmt.Errorf("error getting default channel head for %s: %v", head, err) + } + if !defaultChannelHead.Valid || len(defaultChannelHead.String) == 0 { + return nil, fmt.Errorf("invalid default channel head '%s' for %s", defaultChannelHead.String, head) + } + rows, err := tx.QueryContext(context.TODO(), getReplacesChain, head) + if err != nil { + return nil, err + } + replacesChain := map[string]tailBundle{} + for rows.Next() { var ( - replaces sql.NullString - skips sql.NullString + bundle sql.NullString + version sql.NullString + bundlepath sql.NullString + channels sql.NullString + replaces sql.NullString + replacedBy sql.NullString ) - if rows.Next() { - if err := rows.Scan(&replaces, &skips); err != nil { - if nerr := rows.Close(); nerr != nil { - return nil, nerr - } - return nil, err + if err := rows.Scan(&bundle, &version, &bundlepath, &channels, &replaces, &replacedBy); err != nil { + if nerr := rows.Close(); nerr != nil { + return nil, nerr } - } - if err := rows.Close(); err != nil { return nil, err } - if skips.Valid && skips.String != "" { - for _, skip := range strings.Split(skips.String, ",") { - if _, ok := visited[skip]; ok { - // We've already visited this bundle's subgraph - continue - } - visited[skip] = struct{}{} - next = append(next, skip) - } + if !bundle.Valid || len(bundle.String) == 0 { + return nil, fmt.Errorf("invalid tail bundle %v for %s", bundle, head) } - if replaces.Valid && replaces.String != "" { - r := replaces.String - if _, ok := visited[r]; ok { - // We've already visited this bundle's subgraph - continue - } - visited[r] = struct{}{} - next = append(next, r) + + if bundle.String == defaultChannelHead.String { + // A nil error indicates that next is the default channel head + return nil, registry.ErrRemovingDefaultChannelDuringDeprecation + } + var channelList, replacesList, replacedList []string + if channels.Valid && len(channels.String) > 0 { + channelList = strings.Split(channels.String, ",") + } + if replaces.Valid && len(replaces.String) > 0 { + replacesList = strings.Split(replaces.String, ",") + } + if replacedBy.Valid && len(replacedBy.String) > 0 { + replacedList = strings.Split(replacedBy.String, ",") } - } - // The tail is exactly the set of bundles we visited while traversing the graph from head - var tail []string - for v := range visited { - tail = append(tail, v) + replacesChain[bundle.String] = tailBundle{ + name: bundle.String, + version: version.String, + bundlepath: bundlepath.String, + channels: channelList, + replaces: replacesList, + replacedBy: replacedList, + } } - - return tail, nil - + if err := rows.Close(); err != nil { + return nil, err + } + return replacesChain, nil } func getBundleNameAndVersionForImage(tx *sql.Tx, path string) (string, string, error) { @@ -1435,17 +1490,73 @@ func (s *sqlLoader) DeprecateBundle(path string) error { return err } - for _, bundle := range tailBundles { - if err := s.rmChannelEntry(tx, bundle); err != nil { + // track bundles that have already been added to removeOrDeprecate + removeOrDeprecate := []string{name} + seen := map[string]bool{name: true} + + headChannelsMap := map[string]struct{}{} + if _, ok := tailBundles[name]; ok { + for _, c := range tailBundles[name].channels { + headChannelsMap[c] = struct{}{} + } + } + + // Traverse replaces chain, removing bundles from all channels the initial deprecated bundle belongs to. + // If a bundle is removed from all its channels, it is truncated. +deprecate: + for ; len(removeOrDeprecate) > 0; removeOrDeprecate = removeOrDeprecate[1:] { + bundle := removeOrDeprecate[0] + if _, ok := tailBundles[bundle]; !ok { + continue + } + for _, b := range tailBundles[bundle].replaces { + if !seen[b] { + removeOrDeprecate = append(removeOrDeprecate, b) + seen[b] = true + } + } + if bundle == name { + // head bundle gets deprecated separately + continue + } + + // remove all channel_entries for bundle with same channel as the deprecated one + if err := s.rmSharedChannelEntry(tx, bundle, name); err != nil { return err } + + //rm channel entries for channels + if len(tailBundles[bundle].channels) > len(headChannelsMap) { + // bundle belongs to some channel that the initial deprecated bundle does not - we can't truncate this + continue + } + for _, c := range tailBundles[bundle].channels { + if _, ok := headChannelsMap[c]; !ok { + // bundle belongs to some channel that the initial deprecated bundle does not - we can't truncate this + continue deprecate + } + } + for _, b := range tailBundles[bundle].replacedBy { + if _, ok := tailBundles[b]; !ok { + // bundle is a replaces edge for some csv that isn't in the deprecated tail, can't be replaced safely. + continue deprecate + } + } + + // Remove bundle if err := s.rmBundle(tx, bundle); err != nil { return err } + + } + // remove links to deprecated/truncated bundles to avoid regenerating these on add/overwrite + _, err = tx.Exec(`UPDATE channel_entry SET replaces=NULL WHERE operatorbundle_name=?`, name) + if err != nil { + return err } - // Remove any channels that start with the deprecated bundle - _, err = tx.Exec(fmt.Sprintf(`DELETE FROM channel WHERE head_operatorbundle_name="%s"`, name)) + // a channel with a deprecated head is still visible on the console unless the channel_entry table has no entries for it + _, err = tx.Exec(`DELETE FROM channel WHERE head_operatorbundle_name=?`, name) if err != nil { return err } @@ -1466,13 +1577,9 @@ func (s *sqlLoader) DeprecateBundle(path string) error { return err } - // Clean up the deprecated table by dropping all truncated bundles - // (see pkg/sqlite/migrations/013_rm_truncated_deprecations.go for more details) - _, err = tx.Exec(`DELETE FROM deprecated WHERE deprecated.operatorbundle_name NOT IN (SELECT DISTINCT deprecated.operatorbundle_name FROM (deprecated INNER JOIN channel_entry ON deprecated.operatorbundle_name = channel_entry.operatorbundle_name))`) - if err != nil { + if err := s.rmStrandedDeprecated(tx); err != nil { return err } - return tx.Commit() } @@ -1489,11 +1596,73 @@ func (s *sqlLoader) RemoveStrandedBundles() error { return err } + if err := s.rmStrandedDeprecated(tx); err != nil { + return err + } return tx.Commit() } func (s *sqlLoader) rmStrandedBundles(tx *sql.Tx) error { - _, err := tx.Exec("DELETE FROM operatorbundle WHERE name NOT IN(select operatorbundle_name from channel_entry)") + // Remove everything without a channel_entry except deprecated channel heads + _, err := tx.Exec("DELETE FROM operatorbundle WHERE name NOT IN(select operatorbundle_name from channel_entry) AND name NOT IN (SELECT operatorbundle_name FROM deprecated)") + return err +} + +func (s *sqlLoader) rmStrandedDeprecated(tx *sql.Tx) error { + // Remove any deprecated channel heads which have no entries in the channel/channel_entry table to avoid being displayed on the console + rows, err := tx.Query("SELECT DISTINCT name FROM package") + if err != nil { + return err + } + defer rows.Close() + knownPackages := map[string]struct{}{} + for rows.Next() { + var pkg sql.NullString + if err := rows.Scan(&pkg); err != nil { + return err + } + if !pkg.Valid || len(pkg.String) == 0 { + return fmt.Errorf("invalid package %v", pkg) + } + knownPackages[pkg.String] = struct{}{} + } + + packagePropertiesQuery := `select distinct operatorbundle_name, value from properties where type = ?` + pRows, err := tx.Query(packagePropertiesQuery, registry.PackageType) + if err != nil { + return err + } + defer pRows.Close() + + for pRows.Next() { + var bundle, value sql.NullString + if err := pRows.Scan(&bundle, &value); err != nil { + return err + } + + if !bundle.Valid || len(bundle.String) == 0 { + return fmt.Errorf("invalid bundle %v", bundle) + } + + if !value.Valid || len(value.String) == 0 { + return fmt.Errorf("invalid package property on %v: %v", bundle, value) + } + + var prop registry.PackageProperty + if err := json.Unmarshal([]byte(value.String), &prop); err != nil { + return err + } + + if _, ok := knownPackages[prop.PackageName]; !ok { + if err := s.rmBundle(tx, bundle.String); err != nil { + return err + } + } + } + + // Clean up the deprecated table by dropping all truncated bundles + // (see pkg/sqlite/migrations/013_rm_truncated_deprecations.go for more details) + _, err = tx.Exec(`DELETE FROM deprecated WHERE deprecated.operatorbundle_name NOT IN (SELECT DISTINCT name FROM operatorbundle)`) return err } @@ -1590,3 +1759,97 @@ func (d *DeprecationAwareLoader) RemovePackage(pkg string) error { return d.sqlLoader.RemovePackage(pkg) } + +// RemoveOverwrittenChannelHead removes a bundle if it is the channel head and has nothing replacing it +func (s sqlLoader) RemoveOverwrittenChannelHead(pkg, bundle string) error { + tx, err := s.db.Begin() + if err != nil { + return err + } + defer func() { + tx.Rollback() + }() + // check if bundle has anything that replaces it + getBundlesThatReplaceHeadQuery := `SELECT DISTINCT operatorbundle.name AS replaces, channel_entry.channel_name + FROM channel_entry + LEFT OUTER JOIN channel_entry replaces + ON replaces.replaces = channel_entry.entry_id + INNER JOIN operatorbundle + ON replaces.operatorbundle_name = operatorbundle.name + WHERE channel_entry.package_name = ? + AND channel_entry.operatorbundle_name = ? + LIMIT 1` + + rows, err := tx.QueryContext(context.TODO(), getBundlesThatReplaceHeadQuery, pkg, bundle) + if err != nil { + return err + } + defer rows.Close() + if rows != nil { + for rows.Next() { + var replaces, channel sql.NullString + if err := rows.Scan(&replaces, &channel); err != nil { + return err + } + // This is not a head bundle for all channels it is a member of. Cannot remove + return fmt.Errorf("cannot overwrite bundle %s from package %s: replaced by %s on channel %s", bundle, pkg, replaces.String, channel.String) + } + } + + getReplacingBundlesQuery := ` + SELECT replaces.name as replaces, channel_entry.channel_name, min(depth) + from channel_entry + LEFT JOIN ( + SELECT entry_id, name FROM channel_entry + INNER JOIN operatorbundle + ON channel_entry.operatorbundle_name = operatorbundle.name + ) AS replaces + ON channel_entry.replaces = replaces.entry_id + WHERE channel_entry.package_name = ? + AND channel_entry.operatorbundle_name = ? + GROUP BY channel_name + ` + + pRows, err := tx.QueryContext(context.TODO(), getReplacingBundlesQuery, pkg, bundle) + if err != nil { + return err + } + defer pRows.Close() + + channelHeadUpdateQuery := `UPDATE channel SET head_operatorbundle_name = ? WHERE package_name = ? AND name = ? AND head_operatorbundle_name = ?` + for pRows.Next() { + var replaces, channel sql.NullString + var depth sql.NullInt64 + if err := pRows.Scan(&replaces, &channel, &depth); err != nil { + return err + } + + if !channel.Valid { + return fmt.Errorf("channel name column corrupt for bundle %s", bundle) + } + if replaces.Valid && len(replaces.String) != 0 { + // replace any valid entries as channel heads to avoid rmBundle from truncating the entire channel + if _, err = tx.Exec(channelHeadUpdateQuery, replaces, pkg, channel, bundle); err != nil { + return err + } + } else { + // NULL default channel before dropping to let packagemanifest detect default channel + if _, err := tx.Exec(`UPDATE channel SET head_operatorbundle_name = NULL WHERE name = ? AND package_name = ? AND name IN (SELECT default_channel FROM package WHERE name = ?)`, channel, pkg, pkg); err != nil { + return err + } + } + } + + if err := s.rmBundle(tx, bundle); err != nil { + return err + } + // remove from deprecated + if _, err = tx.Exec(`DELETE FROM deprecated WHERE deprecated.operatorbundle_name = ?`, bundle); err != nil { + return err + } + err = tx.Commit() + if err != nil { + return err + } + return nil +} diff --git a/staging/operator-registry/pkg/sqlite/load_test.go b/staging/operator-registry/pkg/sqlite/load_test.go index 52c4a93d0a..af289ceae1 100644 --- a/staging/operator-registry/pkg/sqlite/load_test.go +++ b/staging/operator-registry/pkg/sqlite/load_test.go @@ -2,7 +2,9 @@ package sqlite import ( "context" + "database/sql" "encoding/json" + "errors" "fmt" "strings" "testing" @@ -148,13 +150,76 @@ func TestAddPackageChannels(t *testing.T) { querier := NewSQLLiteQuerierFromDb(db) pkgs, err := querier.ListPackages(context.Background()) require.NoError(t, err) - t.Logf("%#v", tt.expected.pkgs) - t.Logf("%#v", pkgs) require.ElementsMatch(t, tt.expected.pkgs, pkgs) }) } } +func TestAddBundleSemver(t *testing.T) { + // Create a test DB + db, cleanup := CreateTestDb(t) + defer cleanup() + store, err := NewSQLLiteLoader(db) + require.NoError(t, err) + err = store.Migrate(context.TODO()) + require.NoError(t, err) + graphLoader, err := NewSQLGraphLoaderFromDB(db) + require.NoError(t, err) + + // Seed the db with a replaces-mode bundle/package + replacesBundle := newBundle(t, "csv-a", "pkg-foo", []string{"stable"}, newUnstructuredCSV(t, "csv-a", "")) + err = store.AddOperatorBundle(replacesBundle) + require.NoError(t, err) + + err = store.AddPackageChannels(registry.PackageManifest{ + PackageName: "pkg-foo", + Channels: []registry.PackageChannel{ + { + Name: "stable", + CurrentCSVName: "csv-a", + }, + }, + DefaultChannelName: "stable", + }) + require.NoError(t, err) + + // Add semver bundles in non-semver order. + bundles := []*registry.Bundle{ + newBundle(t, "csv-3", "pkg-0", []string{"stable"}, newUnstructuredCSVWithVersion(t, "csv-3", "0.3.0")), + newBundle(t, "csv-1", "pkg-0", []string{"stable"}, newUnstructuredCSVWithVersion(t, "csv-1", "0.1.0")), + newBundle(t, "csv-2", "pkg-0", []string{"stable"}, newUnstructuredCSVWithVersion(t, "csv-2", "0.2.0")), + } + for _, b := range bundles { + graph, err := graphLoader.Generate(b.Package) + require.Conditionf(t, func() bool { + return err == nil || errors.Is(err, registry.ErrPackageNotInDatabase) + }, "got unexpected error: %v", err) + bundleLoader := registry.BundleGraphLoader{} + updatedGraph, err := bundleLoader.AddBundleToGraph(b, graph, ®istry.AnnotationsFile{Annotations: *b.Annotations}, false) + require.NoError(t, err) + err = store.AddBundleSemver(updatedGraph, b) + require.NoError(t, err) + } + + // Ensure bundles can be queried with expected replaces and skips values. + querier := NewSQLLiteQuerierFromDb(db) + gotBundles, err := querier.ListBundles(context.Background()) + require.NoError(t, err) + replaces := map[string]string{} + for _, b := range gotBundles { + if b.PackageName != "pkg-0" { + continue + } + require.Len(t, b.Skips, 0, "unexpected skips value(s) for bundle %q", b.CsvName) + replaces[b.CsvName] = b.Replaces + } + require.Equal(t, map[string]string{ + "csv-3": "csv-2", + "csv-2": "csv-1", + "csv-1": "", + }, replaces) +} + func TestClearNonHeadBundles(t *testing.T) { db, cleanup := CreateTestDb(t) defer cleanup() @@ -231,7 +296,6 @@ func newUnstructuredCSVWithSkips(t *testing.T, name, replaces string, skips ...s allSkips, err := json.Marshal(skips) require.NoError(t, err) replacesSkips := fmt.Sprintf(`{"replaces": "%s", "skips": %s}`, replaces, string(allSkips)) - t.Logf("%v", replacesSkips) csv.Spec = json.RawMessage(replacesSkips) out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(csv) @@ -239,6 +303,18 @@ func newUnstructuredCSVWithSkips(t *testing.T, name, replaces string, skips ...s return &unstructured.Unstructured{Object: out} } +func newUnstructuredCSVWithVersion(t *testing.T, name, version string) *unstructured.Unstructured { + csv := ®istry.ClusterServiceVersion{} + csv.TypeMeta.Kind = "ClusterServiceVersion" + csv.SetName(name) + versionJson := fmt.Sprintf(`{"version": "%s"}`, version) + csv.Spec = json.RawMessage(versionJson) + + out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(csv) + require.NoError(t, err) + return &unstructured.Unstructured{Object: out} +} + func newBundle(t *testing.T, name, pkgName string, channels []string, objs ...*unstructured.Unstructured) *registry.Bundle { bundle := registry.NewBundle(name, ®istry.Annotations{ PackageName: pkgName, @@ -278,8 +354,9 @@ func TestDeprecationAwareLoader(t *testing.T) { pkg string } type expected struct { - err error - deprecated map[string]struct{} + err error + deprecated map[string]struct{} + nontruncated map[string]struct{} } tests := []struct { description string @@ -311,8 +388,9 @@ func TestDeprecationAwareLoader(t *testing.T) { pkg: "pkg-0", }, expected: expected{ - err: nil, - deprecated: map[string]struct{}{}, + err: nil, + deprecated: map[string]struct{}{}, + nontruncated: map[string]struct{}{}, }, }, { @@ -342,8 +420,9 @@ func TestDeprecationAwareLoader(t *testing.T) { pkg: "pkg-0", }, expected: expected{ - err: nil, - deprecated: map[string]struct{}{}, + err: nil, + deprecated: map[string]struct{}{}, + nontruncated: map[string]struct{}{}, }, }, { @@ -388,7 +467,11 @@ func TestDeprecationAwareLoader(t *testing.T) { expected: expected{ err: nil, deprecated: map[string]struct{}{ - "csv-b": struct{}{}, + "csv-b": {}, + }, + nontruncated: map[string]struct{}{ + "csv-b:stable": {}, + "csv-bb:stable": {}, }, }, }, @@ -422,6 +505,95 @@ func TestDeprecationAwareLoader(t *testing.T) { deprecated: map[string]struct{}{ "csv-aa": struct{}{}, // csv-b remains in the deprecated table since it has been truncated and hasn't been removed }, + nontruncated: map[string]struct{}{ + "csv-aa:stable": {}, + "csv-aaa:stable": {}, + }, + }, + }, + { + description: "DeprecateTruncateRemoveDeprecatedChannelHeadOnPackageRemoval", + fields: fields{ + bundles: []*registry.Bundle{ + withBundleImage("quay.io/my/bundle-a", newBundle(t, "csv-a", "pkg-0", []string{"a"}, newUnstructuredCSV(t, "csv-a", ""))), + withBundleImage("quay.io/my/bundle-aa", newBundle(t, "csv-aa", "pkg-0", []string{"a"}, newUnstructuredCSV(t, "csv-aa", "csv-a"))), + withBundleImage("quay.io/my/bundle-b", newBundle(t, "csv-b", "pkg-0", []string{"b"}, newUnstructuredCSV(t, "csv-b", "csv-a"))), + }, + pkgs: []registry.PackageManifest{ + { + PackageName: "pkg-0", + Channels: []registry.PackageChannel{ + { + Name: "a", + CurrentCSVName: "csv-aa", + }, + { + Name: "b", + CurrentCSVName: "csv-b", + }, + }, + DefaultChannelName: "b", + }, + }, + deprecatedPaths: []string{ + "quay.io/my/bundle-aa", + }, + }, + args: args{ + pkg: "pkg-0", + }, + expected: expected{ + err: nil, + deprecated: map[string]struct{}{}, + }, + }, + { + description: "DeprecateChannelHead", + fields: fields{ + bundles: []*registry.Bundle{ + withBundleImage("quay.io/my/bundle-a", newBundle(t, "csv-a", "pkg-0", []string{"a"}, newUnstructuredCSV(t, "csv-a", ""))), + withBundleImage("quay.io/my/bundle-b", newBundle(t, "csv-b", "pkg-0", []string{"b"}, newUnstructuredCSV(t, "csv-b", "csv-a"))), + withBundleImage("quay.io/my/bundle-aa", newBundle(t, "csv-aa", "pkg-0", []string{"a"}, newUnstructuredCSV(t, "csv-aa", "csv-a"))), + withBundleImage("quay.io/my/bundle-aaa", newBundle(t, "csv-aaa", "pkg-0", []string{"a"}, newUnstructuredCSVWithSkips(t, "csv-aaa", "csv-aa", "csv-cc"))), + withBundleImage("quay.io/my/bundle-cc", newBundle(t, "csv-cc", "pkg-0", []string{"c"}, newUnstructuredCSV(t, "csv-cc", "csv-c"))), + withBundleImage("quay.io/my/bundle-c", newBundle(t, "csv-c", "pkg-0", []string{"c"}, newUnstructuredCSV(t, "csv-c", ""))), + }, + pkgs: []registry.PackageManifest{ + { + PackageName: "pkg-0", + Channels: []registry.PackageChannel{ + { + Name: "a", + CurrentCSVName: "csv-aaa", + }, + { + Name: "b", + CurrentCSVName: "csv-b", + }, + { + Name: "c", + CurrentCSVName: "csv-cc", + }, + }, + DefaultChannelName: "b", + }, + }, + deprecatedPaths: []string{ + "quay.io/my/bundle-aaa", + }, + }, + expected: expected{ + err: nil, + deprecated: map[string]struct{}{ + "csv-aaa": {}, + }, + nontruncated: map[string]struct{}{ + "csv-a:b": {}, + "csv-aaa:": {}, + "csv-b:b": {}, + "csv-c:c": {}, + "csv-cc:c": {}, + }, }, }, } @@ -442,7 +614,6 @@ func TestDeprecationAwareLoader(t *testing.T) { for _, pkg := range tt.fields.pkgs { require.NoError(t, store.AddPackageChannels(pkg)) } - for _, deprecatedPath := range tt.fields.deprecatedPaths { require.NoError(t, store.DeprecateBundle(deprecatedPath)) } @@ -459,19 +630,25 @@ func TestDeprecationAwareLoader(t *testing.T) { tx, err := db.Begin() require.NoError(t, err) - rows, err := tx.Query(`SELECT operatorbundle_name FROM deprecated`) - require.NoError(t, err) - require.NotNil(t, rows) - - var bundleName string - for rows.Next() { - require.NoError(t, rows.Scan(&bundleName)) - _, ok := tt.expected.deprecated[bundleName] - require.True(t, ok, "bundle shouldn't be in the deprecated table: %s", bundleName) - delete(tt.expected.deprecated, bundleName) + checkForBundles := func(query, table string, bundleMap map[string]struct{}) { + rows, err := tx.Query(query) + require.NoError(t, err) + require.NotNil(t, rows) + + var bundleName string + for rows.Next() { + require.NoError(t, rows.Scan(&bundleName)) + _, ok := bundleMap[bundleName] + require.True(t, ok, "bundle shouldn't be in the %s table: %s", table, bundleName) + delete(bundleMap, bundleName) + } + + require.Len(t, bundleMap, 0, "not all expected bundles exist in %s table: %v", table, bundleMap) } + checkForBundles(`SELECT operatorbundle_name FROM deprecated`, "deprecated", tt.expected.deprecated) + // operatorbundle_name: + checkForBundles(`SELECT name||":"|| coalesce(group_concat(distinct channel_name), "") FROM (SELECT name, channel_name from operatorbundle left outer join channel_entry on name=operatorbundle_name order by channel_name) group by name`, "operatorbundle", tt.expected.nontruncated) - require.Len(t, tt.expected.deprecated, 0, "not all expected bundles exist in deprecated table: %v", tt.expected.deprecated) }) } } @@ -486,7 +663,7 @@ func TestGetTailFromBundle(t *testing.T) { } type expected struct { err error - tail []string + tail map[string]tailBundle } tests := []struct { description string @@ -557,8 +734,9 @@ func TestGetTailFromBundle(t *testing.T) { }, expected: expected{ err: nil, - tail: []string{ - "csv-c", + tail: map[string]tailBundle{ + "csv-b": {name: "csv-b", channels: []string{"alpha", "stable"}, replaces: []string{"csv-c"}, replacedBy: []string{"csv-a"}}, + "csv-c": {name: "csv-c", channels: []string{"alpha", "stable"}, replacedBy: []string{"csv-b"}}, }, }, }, @@ -593,11 +771,12 @@ func TestGetTailFromBundle(t *testing.T) { }, expected: expected{ err: nil, - tail: []string{ - "csv-c", - "csv-d", - "csv-e", - "csv-f", + tail: map[string]tailBundle{ + "csv-b": {name: "csv-b", channels: []string{"alpha", "stable"}, replaces: []string{"csv-c", "csv-d", "csv-e", "csv-f"}, replacedBy: []string{"csv-a"}}, + "csv-c": {name: "csv-c", channels: []string{"alpha", "stable"}, replaces: []string{"csv-d"}, replacedBy: []string{"csv-b"}}, + "csv-d": {name: "csv-d", channels: []string{"alpha", "stable"}, replacedBy: []string{"csv-b", "csv-c"}}, + "csv-e": {name: "csv-e", channels: []string{"alpha", "stable"}, replacedBy: []string{"csv-b"}}, + "csv-f": {name: "csv-f", channels: []string{"alpha", "stable"}, replacedBy: []string{"csv-b"}}, }, }, }, @@ -635,6 +814,57 @@ func TestGetTailFromBundle(t *testing.T) { tail: nil, }, }, + { + /* + 0.1.2 <- 0.1.1 <- 0.1.0 + V (skips) + 1.1.2 <- 1.1.1 <- 1.1.0 + */ + description: "branchPoint", + fields: fields{ + bundles: []*registry.Bundle{ + newBundle(t, "csv-0.1.0", "pkg-0", []string{"0.1.x"}, newUnstructuredCSV(t, "csv-0.1.0", "")), + newBundle(t, "csv-0.1.1", "pkg-0", []string{"0.1.x", "lts"}, newUnstructuredCSV(t, "csv-0.1.1", "csv-0.1.0")), + newBundle(t, "csv-0.1.2", "pkg-0", []string{"0.1.x"}, newUnstructuredCSV(t, "csv-0.1.2", "csv-0.1.1")), + newBundle(t, "csv-1.1.0", "pkg-0", []string{"1.1.x"}, newUnstructuredCSV(t, "csv-1.1.0", "")), + newBundle(t, "csv-1.1.1", "pkg-0", []string{"1.1.x", "lts"}, newUnstructuredCSVWithSkips(t, "csv-1.1.1", "csv-1.1.0", "csv-0.1.1")), + newBundle(t, "csv-1.1.2", "pkg-0", []string{"1.1.x", "lts"}, newUnstructuredCSV(t, "csv-1.1.2", "csv-1.1.1")), + }, + pkgs: []registry.PackageManifest{ + { + PackageName: "pkg-0", + Channels: []registry.PackageChannel{ + { + Name: "0.1.x", + CurrentCSVName: "csv-0.1.2", + }, + { + Name: "1.1.x", + CurrentCSVName: "csv-1.1.2", + }, + { + Name: "lts", + CurrentCSVName: "csv-1.1.2", + }, + }, + DefaultChannelName: "0.1.x", + }, + }, + }, + args: args{ + bundle: "csv-1.1.2", + }, + expected: expected{ + err: nil, + tail: map[string]tailBundle{ + "csv-1.1.2": {name: "csv-1.1.2", channels: []string{"1.1.x", "lts"}, replaces: []string{"csv-1.1.1"}}, + "csv-1.1.1": {name: "csv-1.1.1", channels: []string{"1.1.x", "lts"}, replaces: []string{"csv-0.1.1", "csv-1.1.0"}, replacedBy: []string{"csv-1.1.2"}}, + "csv-1.1.0": {name: "csv-1.1.0", channels: []string{"1.1.x", "lts"}, replacedBy: []string{"csv-1.1.1"}}, + "csv-0.1.1": {name: "csv-0.1.1", channels: []string{"0.1.x", "1.1.x", "lts"}, replaces: []string{"csv-0.1.0"}, replacedBy: []string{"csv-0.1.2", "csv-1.1.1"}}, // 0.1.2 present in replacedBy but not in tail + "csv-0.1.0": {name: "csv-0.1.0", channels: []string{"0.1.x"}, replacedBy: []string{"csv-0.1.1"}}, + }, + }, + }, } for _, tt := range tests { @@ -647,22 +877,18 @@ func TestGetTailFromBundle(t *testing.T) { require.NoError(t, err) for _, bundle := range tt.fields.bundles { - // Throw away any errors loading bundles (not testing this) - store.AddOperatorBundle(bundle) + require.NoError(t, store.AddOperatorBundle(bundle)) } for _, pkg := range tt.fields.pkgs { - // Throw away any errors loading packages (not testing this) - store.AddPackageChannels(pkg) + require.NoError(t, store.AddPackageChannels(pkg)) } tx, err := db.Begin() require.NoError(t, err) tail, err := getTailFromBundle(tx, tt.args.bundle) require.Equal(t, tt.expected.err, err) - t.Logf("tt.expected.tail %#v", tt.expected.tail) - t.Logf("tail %#v", tail) - require.ElementsMatch(t, tt.expected.tail, tail) + require.EqualValues(t, tt.expected.tail, tail) }) } } @@ -789,3 +1015,265 @@ func TestAddBundlePropertiesFromAnnotations(t *testing.T) { }) } } + +func TestRemoveOverwrittenChannelHead(t *testing.T) { + type fields struct { + bundles []*registry.Bundle + pkgs []registry.PackageManifest + } + type args struct { + bundle string + pkg string + } + type expected struct { + err error + bundles map[string]struct{} + } + tests := []struct { + description string + fields fields + args args + expected expected + }{ + { + description: "ChannelHead/SingleBundlePackage", + fields: fields{ + bundles: []*registry.Bundle{ + newBundle(t, "csv-a", "pkg-0", []string{"a", "b"}, newUnstructuredCSV(t, "csv-a", "")), + newBundle(t, "csv-b", "pkg-1", []string{"a", "b"}, newUnstructuredCSV(t, "csv-b", "")), + }, + pkgs: []registry.PackageManifest{ + { + PackageName: "pkg-0", + Channels: []registry.PackageChannel{ + { + Name: "a", + CurrentCSVName: "csv-a", + }, + { + Name: "b", + CurrentCSVName: "csv-a", + }, + }, + DefaultChannelName: "a", + }, + { + PackageName: "pkg-1", + Channels: []registry.PackageChannel{ + { + Name: "a", + CurrentCSVName: "csv-b", + }, + { + Name: "b", + CurrentCSVName: "csv-b", + }, + }, + DefaultChannelName: "a", + }, + }, + }, + args: args{ + bundle: "csv-a", + pkg: "pkg-0", + }, + expected: expected{ + bundles: map[string]struct{}{ + "pkg-1/a/csv-b": {}, + "pkg-1/b/csv-b": {}, + }, + }, + }, + { + description: "ChannelHead/WithReplacement", + fields: fields{ + bundles: []*registry.Bundle{ + newBundle(t, "csv-a", "pkg-0", []string{"a", "b"}, newUnstructuredCSV(t, "csv-a", "")), + newBundle(t, "csv-aa", "pkg-0", []string{"b"}, newUnstructuredCSV(t, "csv-aa", "csv-a")), + }, + pkgs: []registry.PackageManifest{ + { + PackageName: "pkg-0", + Channels: []registry.PackageChannel{ + { + Name: "a", + CurrentCSVName: "csv-a", + }, + { + Name: "b", + CurrentCSVName: "csv-aa", + }, + }, + DefaultChannelName: "b", + }, + }, + }, + args: args{ + bundle: "csv-a", + pkg: "pkg-0", + }, + expected: expected{ + err: fmt.Errorf("cannot overwrite bundle csv-a from package pkg-0: replaced by csv-aa on channel b"), + bundles: map[string]struct{}{ + "pkg-0/a/csv-a": {}, + "pkg-0/b/csv-a": {}, + "pkg-0/b/csv-aa": {}, + }, + }, + }, + { + description: "ChannelHead", + fields: fields{ + bundles: []*registry.Bundle{ + newBundle(t, "csv-a", "pkg-0", []string{"a", "b"}, newUnstructuredCSVWithSkips(t, "csv-a", "csv-b", "csv-c")), + newBundle(t, "csv-b", "pkg-0", []string{"b", "d"}, newUnstructuredCSV(t, "csv-b", "")), + newBundle(t, "csv-d", "pkg-0", []string{"d"}, newUnstructuredCSV(t, "csv-d", "csv-b")), + newBundle(t, "csv-c", "pkg-0", []string{"c"}, newUnstructuredCSV(t, "csv-c", "")), + }, + pkgs: []registry.PackageManifest{ + { + PackageName: "pkg-0", + Channels: []registry.PackageChannel{ + { + Name: "a", + CurrentCSVName: "csv-a", + }, + { + Name: "b", + CurrentCSVName: "csv-a", + }, + { + Name: "c", + CurrentCSVName: "csv-c", + }, + { + Name: "d", + CurrentCSVName: "csv-d", + }, + }, + DefaultChannelName: "a", + }, + }, + }, + args: args{ + bundle: "csv-a", + pkg: "pkg-0", + }, + expected: expected{ + err: nil, + bundles: map[string]struct{}{ + "pkg-0/a/csv-b": {}, + "pkg-0/b/csv-b": {}, + "pkg-0/a/csv-c": {}, + "pkg-0/b/csv-c": {}, + "pkg-0/c/csv-c": {}, + "pkg-0/d/csv-d": {}, + "pkg-0/d/csv-b": {}, + }, + }, + }, + + { + description: "PersistDefaultChannel", + fields: fields{ + bundles: []*registry.Bundle{ + newBundle(t, "csv-a", "pkg-0", []string{"a"}, newUnstructuredCSV(t, "csv-a", "")), + newBundle(t, "csv-b", "pkg-0", []string{"b"}, newUnstructuredCSV(t, "csv-b", "")), + }, + pkgs: []registry.PackageManifest{ + { + PackageName: "pkg-0", + Channels: []registry.PackageChannel{ + { + Name: "a", + CurrentCSVName: "csv-a", + }, + { + Name: "b", + CurrentCSVName: "csv-b", + }, + }, + DefaultChannelName: "a", + }, + }, + }, + args: args{ + bundle: "csv-a", + pkg: "pkg-0", + }, + expected: expected{ + err: nil, + bundles: map[string]struct{}{ + "pkg-0/b/csv-b": {}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + db, cleanup := CreateTestDb(t) + defer cleanup() + store, err := NewSQLLiteLoader(db) + require.NoError(t, err) + err = store.Migrate(context.Background()) + require.NoError(t, err) + + for _, bundle := range tt.fields.bundles { + // Throw away any errors loading bundles (not testing this) + store.AddOperatorBundle(bundle) + } + + for _, pkg := range tt.fields.pkgs { + // Throw away any errors loading packages (not testing this) + store.AddPackageChannels(pkg) + } + + getDefaultChannel := func(pkg string) sql.NullString { + // get defaultChannel before delete + rows, err := db.QueryContext(context.Background(), `SELECT default_channel FROM package WHERE name = ?`, pkg) + require.NoError(t, err) + defer rows.Close() + var defaultChannel sql.NullString + for rows.Next() { + require.NoError(t, rows.Scan(&defaultChannel)) + break + } + return defaultChannel + } + oldDefaultChannel := getDefaultChannel(tt.args.pkg) + + err = store.(registry.HeadOverwriter).RemoveOverwrittenChannelHead(tt.args.pkg, tt.args.bundle) + if tt.expected.err != nil { + require.EqualError(t, err, tt.expected.err.Error()) + } else { + require.NoError(t, err) + } + + querier := NewSQLLiteQuerierFromDb(db) + + bundles, err := querier.ListBundles(context.Background()) + require.NoError(t, err) + + var extra []string + for _, b := range bundles { + key := fmt.Sprintf("%s/%s/%s", b.PackageName, b.ChannelName, b.CsvName) + if _, ok := tt.expected.bundles[key]; ok { + delete(tt.expected.bundles, key) + } else { + extra = append(extra, key) + } + } + + if len(tt.expected.bundles) > 0 { + t.Errorf("not all expected bundles were found: missing %v", tt.expected.bundles) + } + if len(extra) > 0 { + t.Errorf("unexpected bundles found: %v", extra) + } + + // should preserve defaultChannel entry in package table + currentDefaultChannel := getDefaultChannel(tt.args.pkg) + require.Equal(t, oldDefaultChannel, currentDefaultChannel) + }) + } +} diff --git a/staging/operator-registry/pkg/sqlite/migrations/013_rm_truncated_deprecations.go b/staging/operator-registry/pkg/sqlite/migrations/013_rm_truncated_deprecations.go index 33ac71b765..75ad315179 100644 --- a/staging/operator-registry/pkg/sqlite/migrations/013_rm_truncated_deprecations.go +++ b/staging/operator-registry/pkg/sqlite/migrations/013_rm_truncated_deprecations.go @@ -16,11 +16,11 @@ var rmTruncatedDeprecationsMigration = &Migration{ Id: RmTruncatedDeprecationsMigrationKey, Up: func(ctx context.Context, tx *sql.Tx) error { - // Delete deprecation history for all bundles that no longer exist in the channel_entries table + // Delete deprecation history for all bundles that no longer exist in the operatorbundle table // These bundles have been truncated by more recent deprecations and would only confuse future operations on an index; // e.g. adding a previously truncated bundle to a package removed via `opm index|registry rm` would lead to that bundle // being deprecated - _, err := tx.ExecContext(ctx, `DELETE FROM deprecated WHERE deprecated.operatorbundle_name NOT IN (SELECT DISTINCT deprecated.operatorbundle_name FROM (deprecated INNER JOIN channel_entry ON deprecated.operatorbundle_name = channel_entry.operatorbundle_name))`) + _, err := tx.ExecContext(ctx, `DELETE FROM deprecated WHERE deprecated.operatorbundle_name NOT IN (SELECT DISTINCT deprecated.operatorbundle_name FROM (deprecated INNER JOIN operatorbundle ON deprecated.operatorbundle_name = operatorbundle.name))`) return err }, diff --git a/staging/operator-registry/pkg/sqlite/query.go b/staging/operator-registry/pkg/sqlite/query.go index 432b998112..6d46d544c2 100644 --- a/staging/operator-registry/pkg/sqlite/query.go +++ b/staging/operator-registry/pkg/sqlite/query.go @@ -1342,9 +1342,10 @@ func (s *SQLQuerier) listBundleChannels(ctx context.Context, bundleName string) // PackageFromDefaultChannelHeadBundle returns the package name if the provided bundle is the head of its default channel. func (s *SQLQuerier) PackageFromDefaultChannelHeadBundle(ctx context.Context, bundle string) (string, error) { packageFromDefaultChannelHeadBundle := ` - SELECT package_name FROM package - INNER JOIN channel ON channel.name = package.default_channel - WHERE channel.head_operatorbundle_name = (SELECT name FROM operatorbundle WHERE bundlepath=? LIMIT 1) ` + SELECT package_name FROM package, channel + WHERE channel.package_name == package.name + AND package.default_channel == channel.name + AND channel.head_operatorbundle_name = (SELECT name FROM operatorbundle WHERE bundlepath=? LIMIT 1) ` rows, err := s.db.QueryContext(ctx, packageFromDefaultChannelHeadBundle, bundle) if err != nil { diff --git a/staging/operator-registry/pkg/sqlite/remove_test.go b/staging/operator-registry/pkg/sqlite/remove_test.go index e4b8063eb8..2d60c77598 100644 --- a/staging/operator-registry/pkg/sqlite/remove_test.go +++ b/staging/operator-registry/pkg/sqlite/remove_test.go @@ -34,7 +34,7 @@ func TestRemover(t *testing.T) { map[image.Reference]string{ image.SimpleReference("quay.io/test/" + name): "../../bundles/" + name, }, - make(map[string]map[image.Reference]string, 0), false).Populate(registry.ReplacesMode) + nil).Populate(registry.ReplacesMode) } for _, name := range []string{"etcd.0.9.0", "etcd.0.9.2", "prometheus.0.14.0", "prometheus.0.15.0", "prometheus.0.22.2"} { require.NoError(t, populate(name)) diff --git a/staging/operator-registry/pkg/sqlite/stranded_test.go b/staging/operator-registry/pkg/sqlite/stranded_test.go index e82a0c5684..509d3502af 100644 --- a/staging/operator-registry/pkg/sqlite/stranded_test.go +++ b/staging/operator-registry/pkg/sqlite/stranded_test.go @@ -33,7 +33,7 @@ func TestStrandedBundleRemover(t *testing.T) { map[image.Reference]string{ image.SimpleReference("quay.io/test/" + name): "./testdata/strandedbundles/" + name, }, - make(map[string]map[image.Reference]string, 0), false).Populate(registry.ReplacesMode) + nil).Populate(registry.ReplacesMode) } for _, name := range []string{"prometheus.0.14.0", "prometheus.0.15.0", "prometheus.0.22.2"} { require.NoError(t, populate(name)) diff --git a/staging/operator-registry/release/goreleaser.opm.Dockerfile b/staging/operator-registry/release/goreleaser.opm.Dockerfile index eac78673c8..51637d34b6 100644 --- a/staging/operator-registry/release/goreleaser.opm.Dockerfile +++ b/staging/operator-registry/release/goreleaser.opm.Dockerfile @@ -2,8 +2,14 @@ # build opm images. See the configurations in .goreleaser.yaml # and .github/workflows/release.yaml. -FROM --platform=$BUILDPLATFORM ghcr.io/grpc-ecosystem/grpc-health-probe:v0.4.4 as grpc_health_probe -FROM gcr.io/distroless/base:debug +FROM alpine as grpc_health_probe +ARG TARGETARCH +RUN apk update && \ + apk add curl && \ + curl --silent --show-error --location --output /grpc_health_probe \ + https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/v0.4.5/grpc_health_probe-linux-$TARGETARCH && \ + chmod 755 /grpc_health_probe +FROM gcr.io/distroless/static:debug COPY --from=grpc_health_probe /grpc_health_probe /bin/grpc_health_probe COPY ["nsswitch.conf", "/etc/nsswitch.conf"] COPY opm /bin/opm diff --git a/vendor/github.com/operator-framework/api/pkg/validation/internal/bundle.go b/vendor/github.com/operator-framework/api/pkg/validation/internal/bundle.go index d1e7217dee..79fa93e35c 100644 --- a/vendor/github.com/operator-framework/api/pkg/validation/internal/bundle.go +++ b/vendor/github.com/operator-framework/api/pkg/validation/internal/bundle.go @@ -8,6 +8,8 @@ import ( operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/api/pkg/validation/errors" interfaces "github.com/operator-framework/api/pkg/validation/interfaces" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -26,9 +28,42 @@ func validateBundles(objs ...interface{}) (results []errors.ManifestResult) { func validateBundle(bundle *manifests.Bundle) (result errors.ManifestResult) { result = validateOwnedCRDs(bundle, bundle.CSV) result.Name = bundle.CSV.Spec.Version.String() + saErrors := validateServiceAccounts(bundle) + if saErrors != nil { + result.Add(saErrors...) + } return result } +func validateServiceAccounts(bundle *manifests.Bundle) []errors.Error { + // get service account names defined in the csv + saNamesFromCSV := make(map[string]struct{}, 0) + for _, deployment := range bundle.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs { + saName := deployment.Spec.Template.Spec.ServiceAccountName + saNamesFromCSV[saName] = struct{}{} + } + + // find any hardcoded service account objects are in the bundle, then check if they match any sa definition in the csv + var errs []errors.Error + for _, obj := range bundle.Objects { + if obj.GroupVersionKind() != v1.SchemeGroupVersion.WithKind("ServiceAccount") { + continue + } + sa := v1.ServiceAccount{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &sa); err == nil { + if _, ok := saNamesFromCSV[sa.Name]; ok { + errs = append(errs, errors.ErrInvalidBundle(fmt.Sprintf("invalid service account found in bundle. " + + "This service account %s in your bundle is not valid, because a service account with the same name " + + "was already specified in your CSV. If this was unintentional, please remove the service account " + + "manifest from your bundle. If it was intentional to specify a separate service account, " + + "please rename the SA in either the bundle manifest or the CSV.",sa.Name), sa.Name)) + } + } + } + + return errs +} + func validateOwnedCRDs(bundle *manifests.Bundle, csv *operatorsv1alpha1.ClusterServiceVersion) (result errors.ManifestResult) { ownedKeys := getOwnedCustomResourceDefintionKeys(csv) diff --git a/vendor/github.com/operator-framework/api/pkg/validation/internal/community.go b/vendor/github.com/operator-framework/api/pkg/validation/internal/community.go index 4157332370..3a42d7ec2e 100644 --- a/vendor/github.com/operator-framework/api/pkg/validation/internal/community.go +++ b/vendor/github.com/operator-framework/api/pkg/validation/internal/community.go @@ -21,6 +21,9 @@ const IndexImagePathKey = "index-path" // where the bundle will be distributed const ocpLabelindex = "com.redhat.openshift.versions" +// OCP version where the apis v1beta1 is no longer supported +const ocpVerV1beta1Unsupported = "4.9" + // CommunityOperatorValidator validates the bundle manifests against the required criteria to publish // the projects on the community operators // diff --git a/vendor/github.com/operator-framework/api/pkg/validation/internal/operatorhub.go b/vendor/github.com/operator-framework/api/pkg/validation/internal/operatorhub.go index 2aa4553050..1b56b6bf28 100644 --- a/vendor/github.com/operator-framework/api/pkg/validation/internal/operatorhub.go +++ b/vendor/github.com/operator-framework/api/pkg/validation/internal/operatorhub.go @@ -18,20 +18,72 @@ import ( interfaces "github.com/operator-framework/api/pkg/validation/interfaces" ) -// k8sVersionKey defines the key which can be used by its consumers -// to inform what is the K8S version that should be used to do the tests against. -const k8sVersionKey = "k8s-version" - -const minKubeVersionWarnMessage = "csv.Spec.minKubeVersion is not informed. It is recommended you provide this information. " + - "Otherwise, it would mean that your operator project can be distributed and installed in any cluster version " + - "available, which is not necessarily the case for all projects." - // OperatorHubValidator validates the bundle manifests against the required criteria to publish // the projects on OperatorHub.io. // +// This validator will ensure that: +// - The annotations capabilities into the CSV has a valid option, which are: +// * "Basic Install" +// * "Seamless Upgrades" +// * "Full Lifecycle" +// * "Deep Insights" +// * "Auto Pilot" +// - The annotations categories into the CSV has a valid option, which are: +// * "AI/Machine Learning" +// * "Application Runtime" +// * "Big Data" +// * "Cloud Provider" +// * "Developer Tools" +// * "Database" +// * "Integration & Delivery" +// * "Logging & Tracing" +// * "Modernization & Migration" +// * "Monitoring" +// * "Networking" +// * "OpenShift Optional" +// * "Security" +// * "Storage" +// * "Streaming & Messaging" +// +// NOTE: The operatorhub validator can verify against custom bundle categories by setting the OPERATOR_BUNDLE_CATEGORIES +// environment variable. Setting the OPERATOR_BUNDLE_CATEGORIES environment variable to the path to a json file +// containing a list of categories will enable those categories to be used when comparing CSV categories for +// operatorhub validation. The json file should be in the following format: +// +// ```json +// { +// "categories":[ +// "Cloud Pak", +// "Registry", +// "MyCoolThing", +// ] +// } +// ``` +// +// - The csv.Spec.Provider.Name was provided +// - The csv.Spec.Maintainers elements contains both name and email +// - The csv.Spec.Links elements contains both name and url +// - The csv.Spec.Links.Url is a valid value +// - The csv.Spec.Version is provided +// - The checks.csv.Spec.Icon was provided and has not more than one element +// - The csv.Spec.Icon elements should contain both data and mediatype +// - The csv.Spec.Icon elements should contain both data and mediatype +// - The csv.Spec.Icon has a valid mediatype, which are +// * "image/gif" +// * "image/jpeg" +// * "image/png" +// * "image/svg+xml" +// - If informed ONLY, check if the value csv.Spec.MinKubeVersion is parsable according to semver (https://semver.org/) +// Also, this validator will raise warnings when: +// - The bundle name (CSV.metadata.name) does not follow the naming convention: .v e.g. memcached-operator.v0.0.1 +// NOTE: The bundle name must be 63 characters or less because it will be used as k8s ownerref label which only allows max of 63 characters. +// - The channel names seems are not following the convention https://olm.operatorframework.io/docs/best-practices/channel-naming/ +// - The usage of the removed APIs on Kubernetes 1.22 is found. More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22 +// // Note that this validator allows to receive a List of optional values as key=values. Currently, only the // `k8s-version` key is allowed. If informed, it will perform the checks against this specific Kubernetes version where the -// operator bundle is intend to be distribute for. +// operator bundle is intend to be used and will raise errors instead of warnings. +// Currently, this check is capable of verifying the removed APIs only for Kubernetes 1.22 version. var OperatorHubValidator interfaces.Validator = interfaces.ValidatorFunc(validateOperatorHub) var validCapabilities = map[string]struct{}{ @@ -50,20 +102,21 @@ var validMediatypes = map[string]struct{}{ } var validCategories = map[string]struct{}{ - "AI/Machine Learning": {}, - "Application Runtime": {}, - "Big Data": {}, - "Cloud Provider": {}, - "Developer Tools": {}, - "Database": {}, - "Integration & Delivery": {}, - "Logging & Tracing": {}, - "Monitoring": {}, - "Networking": {}, - "OpenShift Optional": {}, - "Security": {}, - "Storage": {}, - "Streaming & Messaging": {}, + "AI/Machine Learning": {}, + "Application Runtime": {}, + "Big Data": {}, + "Cloud Provider": {}, + "Developer Tools": {}, + "Database": {}, + "Integration & Delivery": {}, + "Logging & Tracing": {}, + "Monitoring": {}, + "Modernization & Migration": {}, + "Networking": {}, + "OpenShift Optional": {}, + "Security": {}, + "Storage": {}, + "Streaming & Messaging": {}, } func validateOperatorHub(objs ...interface{}) (results []errors.ManifestResult) { @@ -111,7 +164,7 @@ func validateBundleOperatorHub(bundle *manifests.Bundle, k8sVersion string) erro result.Add(errors.WarnInvalidCSV(warn.Error(), bundle.CSV.GetName())) } - errs, warns := validateHubDeprecatedAPIS(bundle, k8sVersion) + errs, warns := validateDeprecatedAPIS(bundle, k8sVersion) for _, err := range errs { result.Add(errors.ErrFailedValidation(err.Error(), bundle.CSV.GetName())) } @@ -153,77 +206,6 @@ func validateHubChannels(channels []string) error { return nil } -// validateHubDeprecatedAPIS will check if the operator bundle is using a deprecated or no longer supported k8s api -// Note if the k8s was informed via "k8s=1.22" it will be used. Otherwise, we will use the minKubeVersion in -// the CSV to do the checks. So, the criteria is >=minKubeVersion. By last, if the minKubeVersion is not provided -// then, we should consider the operator bundle is intend to work well in any Kubernetes version. -// Then, it means that: -//--optional-values="k8s-version=value" flag with a value => 1.16 <= 1.22 the validator will return result as warning. -//--optional-values="k8s-version=value" flag with a value => 1.22 the validator will return result as error. -//minKubeVersion >= 1.22 return the error result. -//minKubeVersion empty returns a warning since it would mean the same of allow install in any supported version -func validateHubDeprecatedAPIS(bundle *manifests.Bundle, versionProvided string) (errs, warns []error) { - // K8s version where the apis v1betav1 is no longer supported - const k8sVerV1betav1Unsupported = "1.22.0" - // K8s version where the apis v1betav1 was deprecated - const k8sVerV1betav1Deprecated = "1.16.0" - // semver of the K8s version where the apis v1betav1 is no longer supported to allow us compare - semVerK8sVerV1betav1Unsupported := semver.MustParse(k8sVerV1betav1Unsupported) - // semver of the K8s version where the apis v1betav1 is deprecated to allow us compare - semVerk8sVerV1betav1Deprecated := semver.MustParse(k8sVerV1betav1Deprecated) - // isVersionProvided defines if the k8s version to test against was or not informed - isVersionProvided := len(versionProvided) > 0 - - // Transform the key/option versionProvided in semver Version to compare - var semVerVersionProvided semver.Version - if isVersionProvided { - var err error - semVerVersionProvided, err = semver.ParseTolerant(versionProvided) - if err != nil { - errs = append(errs, fmt.Errorf("invalid value informed via the k8s key option : %s", versionProvided)) - } else { - // we might want to return it as info instead of warning in the future. - warns = append(warns, fmt.Errorf("checking APIs against Kubernetes version : %s", versionProvided)) - } - } - - // Transform the spec minKubeVersion in semver Version to compare - var semverMinKube semver.Version - if len(bundle.CSV.Spec.MinKubeVersion) > 0 { - var err error - if semverMinKube, err = semver.ParseTolerant(bundle.CSV.Spec.MinKubeVersion); err != nil { - errs = append(errs, fmt.Errorf("unable to use csv.Spec.MinKubeVersion to verify the CRD/Webhook apis "+ - "because it has an invalid value: %s", bundle.CSV.Spec.MinKubeVersion)) - } - } - - // if the k8s value was informed and it is >=1.16 we should check - // if the k8s value was not informed we also should check since the - // check should occurs with any minKubeVersion value: - // - if minKubeVersion empty then means that the project can be installed in any version - // - if minKubeVersion any version defined it means that we are considering install - // in any upper version from that where the check is always applied - if !isVersionProvided || semVerVersionProvided.GE(semVerk8sVerV1betav1Deprecated) { - deprecatedAPIs := getRemovedAPIsOn1_22From(bundle) - if len(deprecatedAPIs) > 0 { - deprecatedAPIsMessage := generateMessageWithDeprecatedAPIs(deprecatedAPIs) - // isUnsupported is true only if the key/value OR minKubeVersion were informed and are >= 1.22 - isUnsupported := semVerVersionProvided.GE(semVerK8sVerV1betav1Unsupported) || - semverMinKube.GE(semVerK8sVerV1betav1Unsupported) - // We only raise an error when the version >= 1.22 was informed via - // the k8s key/value option or is specifically defined in the CSV - msg := fmt.Errorf("this bundle is using APIs which were deprecated and removed in v1.22. More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. Migrate the API(s) for %s", deprecatedAPIsMessage) - if isUnsupported { - errs = append(errs, msg) - } else { - warns = append(warns, msg) - } - } - } - - return errs, warns -} - // validateHubCSVSpec will check the CSV against the criteria to publish an // operator bundle in the OperatorHub.io func validateHubCSVSpec(csv v1alpha1.ClusterServiceVersion) CSVChecks { diff --git a/vendor/github.com/operator-framework/api/pkg/validation/internal/removed_apis.go b/vendor/github.com/operator-framework/api/pkg/validation/internal/removed_apis.go index 028176e64a..9377b50b00 100644 --- a/vendor/github.com/operator-framework/api/pkg/validation/internal/removed_apis.go +++ b/vendor/github.com/operator-framework/api/pkg/validation/internal/removed_apis.go @@ -2,13 +2,148 @@ package internal import ( "fmt" + "github.com/blang/semver" "github.com/operator-framework/api/pkg/manifests" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/operator-framework/api/pkg/validation/errors" + interfaces "github.com/operator-framework/api/pkg/validation/interfaces" ) -// OCP version where the apis v1beta1 is no longer supported -const ocpVerV1beta1Unsupported = "4.9" +// k8sVersionKey defines the key which can be used by its consumers +// to inform what is the K8S version that should be used to do the tests against. +const k8sVersionKey = "k8s-version" + +const minKubeVersionWarnMessage = "csv.Spec.minKubeVersion is not informed. It is recommended you provide this information. " + + "Otherwise, it would mean that your operator project can be distributed and installed in any cluster version " + + "available, which is not necessarily the case for all projects." + +// K8s version where the apis v1betav1 is no longer supported +const k8sVerV1betav1Unsupported = "1.22.0" + +// K8s version where the apis v1betav1 was deprecated +const k8sVerV1betav1Deprecated = "1.16.0" + +// AlphaDeprecatedAPIsValidator validates if the bundles is using versions API version which are deprecate or +// removed in specific Kubernetes versions informed via optional key value `k8s-version`. +var AlphaDeprecatedAPIsValidator interfaces.Validator = interfaces.ValidatorFunc(validateDeprecatedAPIsValidator) + +func validateDeprecatedAPIsValidator(objs ...interface{}) (results []errors.ManifestResult) { + + // Obtain the k8s version if informed via the objects an optional + k8sVersion := "" + for _, obj := range objs { + switch obj.(type) { + case map[string]string: + k8sVersion = obj.(map[string]string)[k8sVersionKey] + if len(k8sVersion) > 0 { + break + } + } + } + + for _, obj := range objs { + switch v := obj.(type) { + case *manifests.Bundle: + results = append(results, validateDeprecatedAPIs(v, k8sVersion)) + } + } + + return results +} + +func validateDeprecatedAPIs(bundle *manifests.Bundle, k8sVersion string) errors.ManifestResult { + result := errors.ManifestResult{Name: bundle.Name} + + if bundle == nil { + result.Add(errors.ErrInvalidBundle("Bundle is nil", nil)) + return result + } + + if bundle.CSV == nil { + result.Add(errors.ErrInvalidBundle("Bundle csv is nil", bundle.Name)) + return result + } + + errs, warns := validateDeprecatedAPIS(bundle, k8sVersion) + for _, err := range errs { + result.Add(errors.ErrFailedValidation(err.Error(), bundle.CSV.GetName())) + } + for _, warn := range warns { + result.Add(errors.WarnFailedValidation(warn.Error(), bundle.CSV.GetName())) + } + + return result +} + +// validateDeprecatedAPIS will check if the operator bundle is using a deprecated or no longer supported k8s api +// Note if the k8s was informed via "k8s=1.22" it will be used. Otherwise, we will use the minKubeVersion in +// the CSV to do the checks. So, the criteria is >=minKubeVersion. By last, if the minKubeVersion is not provided +// then, we should consider the operator bundle is intend to work well in any Kubernetes version. +// Then, it means that: +//--optional-values="k8s-version=value" flag with a value => 1.16 <= 1.22 the validator will return result as warning. +//--optional-values="k8s-version=value" flag with a value => 1.22 the validator will return result as error. +//minKubeVersion >= 1.22 return the error result. +//minKubeVersion empty returns a warning since it would mean the same of allow install in any supported version +func validateDeprecatedAPIS(bundle *manifests.Bundle, versionProvided string) (errs, warns []error) { + + // semver of the K8s version where the apis v1betav1 is no longer supported to allow us compare + semVerK8sVerV1betav1Unsupported := semver.MustParse(k8sVerV1betav1Unsupported) + // semver of the K8s version where the apis v1betav1 is deprecated to allow us compare + semVerk8sVerV1betav1Deprecated := semver.MustParse(k8sVerV1betav1Deprecated) + // isVersionProvided defines if the k8s version to test against was or not informed + isVersionProvided := len(versionProvided) > 0 + + // Transform the key/option versionProvided in semver Version to compare + var semVerVersionProvided semver.Version + if isVersionProvided { + var err error + semVerVersionProvided, err = semver.ParseTolerant(versionProvided) + if err != nil { + errs = append(errs, fmt.Errorf("invalid value informed via the k8s key option : %s", versionProvided)) + } else { + // we might want to return it as info instead of warning in the future. + warns = append(warns, fmt.Errorf("checking APIs against Kubernetes version : %s", versionProvided)) + } + } + + // Transform the spec minKubeVersion in semver Version to compare + var semverMinKube semver.Version + if len(bundle.CSV.Spec.MinKubeVersion) > 0 { + var err error + if semverMinKube, err = semver.ParseTolerant(bundle.CSV.Spec.MinKubeVersion); err != nil { + errs = append(errs, fmt.Errorf("unable to use csv.Spec.MinKubeVersion to verify the CRD/Webhook apis "+ + "because it has an invalid value: %s", bundle.CSV.Spec.MinKubeVersion)) + } + } + + // if the k8s value was informed and it is >=1.16 we should check + // if the k8s value was not informed we also should check since the + // check should occurs with any minKubeVersion value: + // - if minKubeVersion empty then means that the project can be installed in any version + // - if minKubeVersion any version defined it means that we are considering install + // in any upper version from that where the check is always applied + if !isVersionProvided || semVerVersionProvided.GE(semVerk8sVerV1betav1Deprecated) { + deprecatedAPIs := getRemovedAPIsOn1_22From(bundle) + if len(deprecatedAPIs) > 0 { + deprecatedAPIsMessage := generateMessageWithDeprecatedAPIs(deprecatedAPIs) + // isUnsupported is true only if the key/value OR minKubeVersion were informed and are >= 1.22 + isUnsupported := semVerVersionProvided.GE(semVerK8sVerV1betav1Unsupported) || + semverMinKube.GE(semVerK8sVerV1betav1Unsupported) + // We only raise an error when the version >= 1.22 was informed via + // the k8s key/value option or is specifically defined in the CSV + msg := fmt.Errorf("this bundle is using APIs which were deprecated and removed in v1.22. More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. Migrate the API(s) for %s", deprecatedAPIsMessage) + if isUnsupported { + errs = append(errs, msg) + } else { + warns = append(warns, msg) + } + } + } + + return errs, warns +} // generateMessageWithDeprecatedAPIs will return a list with the kind and the name // of the resource which were found and required to be upgraded diff --git a/vendor/github.com/operator-framework/api/pkg/validation/validation.go b/vendor/github.com/operator-framework/api/pkg/validation/validation.go index 4084a8a7a6..3c35db4b15 100644 --- a/vendor/github.com/operator-framework/api/pkg/validation/validation.go +++ b/vendor/github.com/operator-framework/api/pkg/validation/validation.go @@ -42,6 +42,10 @@ var OperatorGroupValidator = internal.OperatorGroupValidator // for the Community Operator requirements. var CommunityOperatorValidator = internal.CommunityOperatorValidator +// AlphaDeprecatedAPIsValidator implements Validator to validate bundle objects +// for API deprecation requirements. +var AlphaDeprecatedAPIsValidator = internal.AlphaDeprecatedAPIsValidator + // AllValidators implements Validator to validate all Operator manifest types. var AllValidators = interfaces.Validators{ PackageManifestValidator, @@ -52,6 +56,7 @@ var AllValidators = interfaces.Validators{ ObjectValidator, OperatorGroupValidator, CommunityOperatorValidator, + AlphaDeprecatedAPIsValidator, } var DefaultBundleValidators = interfaces.Validators{ diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/action/diff.go b/vendor/github.com/operator-framework/operator-registry/alpha/action/diff.go index 4f7427f95a..7912cf8a7c 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/action/diff.go +++ b/vendor/github.com/operator-framework/operator-registry/alpha/action/diff.go @@ -4,8 +4,12 @@ import ( "context" "errors" "fmt" + "io" + "github.com/blang/semver/v4" "github.com/sirupsen/logrus" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/yaml" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/model" @@ -21,6 +25,10 @@ type Diff struct { // of bundles included in the diff if true. SkipDependencies bool + IncludeConfig DiffIncludeConfig + // IncludeAdditively catalog objects specified in IncludeConfig. + IncludeAdditively bool + Logger *logrus.Entry } @@ -63,8 +71,10 @@ func (a Diff) Run(ctx context.Context) (*declcfg.DeclarativeConfig, error) { } g := &declcfg.DiffGenerator{ - Logger: a.Logger, - SkipDependencies: a.SkipDependencies, + Logger: a.Logger, + SkipDependencies: a.SkipDependencies, + Includer: convertIncludeConfigToIncluder(a.IncludeConfig), + IncludeAdditively: a.IncludeAdditively, } diffModel, err := g.Run(oldModel, newModel) if err != nil { @@ -81,3 +91,90 @@ func (p Diff) validate() error { } return nil } + +// DiffIncludeConfig configures Diff.Run() to include a set of packages, +// channels, and/or bundles/versions in the output DeclarativeConfig. +// These override other diff mechanisms. For example, if running in +// heads-only mode but package "foo" channel "stable" is specified, +// the entire "stable" channel (all channel bundles) is added to the output. +type DiffIncludeConfig struct { + // Packages to include. + Packages []DiffIncludePackage `json:"packages" yaml:"packages"` +} + +// DiffIncludePackage contains a name (required) and channels and/or versions +// (optional) to include in the diff. The full package is only included if no channels +// or versions are specified. +type DiffIncludePackage struct { + // Name of package. + Name string `json:"name" yaml:"name"` + // Channels to include. + Channels []DiffIncludeChannel `json:"channels,omitempty" yaml:"channels,omitempty"` + // Versions to include. All channels containing these versions + // are parsed for an upgrade graph. + Versions []semver.Version `json:"versions,omitempty" yaml:"versions,omitempty"` + // Bundles are bundle names to include. All channels containing these bundles + // are parsed for an upgrade graph. + // Set this field only if the named bundle has no semantic version metadata. + Bundles []string `json:"bundles,omitempty" yaml:"bundles,omitempty"` +} + +// DiffIncludeChannel contains a name (required) and versions (optional) +// to include in the diff. The full channel is only included if no versions are specified. +type DiffIncludeChannel struct { + // Name of channel. + Name string `json:"name" yaml:"name"` + // Versions to include. + Versions []semver.Version `json:"versions,omitempty" yaml:"versions,omitempty"` + // Bundles are bundle names to include. + // Set this field only if the named bundle has no semantic version metadata. + Bundles []string `json:"bundles,omitempty" yaml:"bundles,omitempty"` +} + +// LoadDiffIncludeConfig loads a (YAML or JSON) DiffIncludeConfig from r. +func LoadDiffIncludeConfig(r io.Reader) (c DiffIncludeConfig, err error) { + dec := yaml.NewYAMLOrJSONDecoder(r, 8) + if err := dec.Decode(&c); err != nil { + return DiffIncludeConfig{}, err + } + + if len(c.Packages) == 0 { + return c, fmt.Errorf("must specify at least one package in include config") + } + + var errs []error + for pkgI, pkg := range c.Packages { + if pkg.Name == "" { + errs = append(errs, fmt.Errorf("package at index %v requires a name", pkgI)) + continue + } + for chI, ch := range pkg.Channels { + if ch.Name == "" { + errs = append(errs, fmt.Errorf("package %s: channel at index %v requires a name", pkg.Name, chI)) + continue + } + } + } + return c, utilerrors.NewAggregate(errs) +} + +func convertIncludeConfigToIncluder(c DiffIncludeConfig) (includer declcfg.DiffIncluder) { + includer.Packages = make([]declcfg.DiffIncludePackage, len(c.Packages)) + for pkgI, cpkg := range c.Packages { + pkg := &includer.Packages[pkgI] + pkg.Name = cpkg.Name + pkg.AllChannels.Versions = cpkg.Versions + pkg.AllChannels.Bundles = cpkg.Bundles + + if len(cpkg.Channels) != 0 { + pkg.Channels = make([]declcfg.DiffIncludeChannel, len(cpkg.Channels)) + for chI, cch := range cpkg.Channels { + ch := &pkg.Channels[chI] + ch.Name = cch.Name + ch.Versions = cch.Versions + ch.Bundles = cch.Bundles + } + } + } + return includer +} diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/action/migrate.go b/vendor/github.com/operator-framework/operator-registry/alpha/action/migrate.go new file mode 100644 index 0000000000..8cca8b8974 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/action/migrate.go @@ -0,0 +1,99 @@ +package action + +import ( + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/pkg/image" +) + +type Migrate struct { + CatalogRef string + OutputDir string + + WriteFunc WriteFunc + FileExt string + Registry image.Registry +} + +type WriteFunc func(config declcfg.DeclarativeConfig, w io.Writer) error + +func (m Migrate) Run(ctx context.Context) error { + entries, err := ioutil.ReadDir(m.OutputDir) + if err != nil && !os.IsNotExist(err) { + return err + } + if len(entries) > 0 { + return fmt.Errorf("output dir %q must be empty", m.OutputDir) + } + + r := Render{ + Refs: []string{m.CatalogRef}, + + // Only allow sqlite images and files to be migrated. Other types cannot + // always be migrated cleanly because they may contain file references. + // Rendered sqlite databases never contain file references. + AllowedRefMask: RefSqliteImage | RefSqliteFile, + + skipSqliteDeprecationLog: true, + } + if m.Registry != nil { + r.Registry = m.Registry + } + + cfg, err := r.Run(ctx) + if err != nil { + return fmt.Errorf("render catalog image: %w", err) + } + + return writeToFS(*cfg, m.OutputDir, m.WriteFunc, m.FileExt) +} + +func writeToFS(cfg declcfg.DeclarativeConfig, rootDir string, writeFunc WriteFunc, fileExt string) error { + channelsByPackage := map[string][]declcfg.Channel{} + for _, c := range cfg.Channels { + channelsByPackage[c.Package] = append(channelsByPackage[c.Package], c) + } + bundlesByPackage := map[string][]declcfg.Bundle{} + for _, b := range cfg.Bundles { + bundlesByPackage[b.Package] = append(bundlesByPackage[b.Package], b) + } + + if err := os.MkdirAll(rootDir, 0777); err != nil { + return err + } + + for _, p := range cfg.Packages { + fcfg := declcfg.DeclarativeConfig{ + Packages: []declcfg.Package{p}, + Channels: channelsByPackage[p.Name], + Bundles: bundlesByPackage[p.Name], + } + pkgDir := filepath.Join(rootDir, p.Name) + if err := os.MkdirAll(pkgDir, 0777); err != nil { + return err + } + filename := filepath.Join(pkgDir, fmt.Sprintf("catalog%s", fileExt)) + if err := writeFile(fcfg, filename, writeFunc); err != nil { + return err + } + } + return nil +} + +func writeFile(cfg declcfg.DeclarativeConfig, filename string, writeFunc WriteFunc) error { + buf := &bytes.Buffer{} + if err := writeFunc(cfg, buf); err != nil { + return fmt.Errorf("write to buffer for %q: %v", filename, err) + } + if err := ioutil.WriteFile(filename, buf.Bytes(), 0666); err != nil { + return fmt.Errorf("write file %q: %v", filename, err) + } + return nil +} diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/action/render.go b/vendor/github.com/operator-framework/operator-registry/alpha/action/render.go index 389c8ff44c..3f38390a0e 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/action/render.go +++ b/vendor/github.com/operator-framework/operator-registry/alpha/action/render.go @@ -52,6 +52,8 @@ type Render struct { Refs []string Registry image.Registry AllowedRefMask RefType + + skipSqliteDeprecationLog bool } func nullLogger() *logrus.Entry { @@ -61,6 +63,10 @@ func nullLogger() *logrus.Entry { } func (r Render) Run(ctx context.Context) (*declcfg.DeclarativeConfig, error) { + if r.skipSqliteDeprecationLog { + // exhaust once with a no-op function. + logDeprecationMessage.Do(func() {}) + } if r.Registry == nil { reg, err := r.createRegistry() if err != nil { @@ -290,7 +296,7 @@ func populateDBRelatedImages(ctx context.Context, cfg *declcfg.DeclarativeConfig } func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error) { - bundleProperties, err := registry.PropertiesFromBundle(bundle) + objs, props, err := registry.ObjectsAndPropertiesFromBundle(bundle) if err != nil { return nil, fmt.Errorf("get properties for bundle %q: %v", bundle.Name, err) } @@ -298,14 +304,25 @@ func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error if err != nil { return nil, fmt.Errorf("get related images for bundle %q: %v", bundle.Name, err) } + var csvJson []byte + for _, obj := range bundle.Objects { + if obj.GetKind() == "ClusterServiceVersion" { + csvJson, err = json.Marshal(obj) + if err != nil { + return nil, fmt.Errorf("marshal CSV JSON for bundle %q: %v", bundle.Name, err) + } + } + } dBundle := declcfg.Bundle{ Schema: "olm.bundle", Name: bundle.Name, Package: bundle.Package, Image: bundle.BundleImage, - Properties: bundleProperties, + Properties: props, RelatedImages: relatedImages, + Objects: objs, + CsvJSON: string(csvJson), } return &declcfg.DeclarativeConfig{Bundles: []declcfg.Bundle{dBundle}}, nil @@ -322,14 +339,12 @@ func getRelatedImages(b *registry.Bundle) ([]declcfg.RelatedImage, error) { return nil, err } - rawValue, ok := objmap["relatedImages"] - if !ok || rawValue == nil { - return nil, err - } - var relatedImages []declcfg.RelatedImage - if err = json.Unmarshal(*rawValue, &relatedImages); err != nil { - return nil, err + rawValue, ok := objmap["relatedImages"] + if ok && rawValue != nil { + if err = json.Unmarshal(*rawValue, &relatedImages); err != nil { + return nil, err + } } // Keep track of the images we've already found, so that we don't add diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foo.v0.2.0.csv.yaml b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foo.v0.2.0.csv.yaml new file mode 100644 index 0000000000..82aeecdaf0 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foo.v0.2.0.csv.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: foo.v0.2.0 + annotations: + olm.skipRange: <0.2.0 +spec: + displayName: "Foo Operator" + customresourcedefinitions: + owned: + - group: test.foo + version: v1 + kind: Foo + name: foos.test.foo + version: 0.2.0 + replaces: foo.v0.1.0 + skips: + - foo.v0.1.1 + - foo.v0.1.2 + install: + strategy: deployment + spec: + deployments: + - name: foo-operator + spec: + template: + spec: + initContainers: + - image: test.registry/foo-operator/foo-init:v0.2.0 + containers: + - image: test.registry/foo-operator/foo:v0.2.0 + - name: foo-operator-2 + spec: + template: + spec: + initContainers: + - image: test.registry/foo-operator/foo-init-2:v0.2.0 + containers: + - image: test.registry/foo-operator/foo-2:v0.2.0 diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foos.test.foo.crd.yaml b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foos.test.foo.crd.yaml new file mode 100644 index 0000000000..39b762b505 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/manifests/foos.test.foo.crd.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: foos.test.foo +spec: + group: test.foo + names: + kind: Foo + plural: foos + versions: + - name: v1 diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/annotations.yaml b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/annotations.yaml new file mode 100644 index 0000000000..dc4cc05f68 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/annotations.yaml @@ -0,0 +1,4 @@ +annotations: + operators.operatorframework.io.bundle.package.v1: foo + operators.operatorframework.io.bundle.channels.v1: beta,stable + operators.operatorframework.io.bundle.channel.default.v1: beta diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/dependencies.yaml b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/dependencies.yaml new file mode 100644 index 0000000000..e5d50aefd0 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/foo-bundle-v0.2.0-no-csv-related-images/metadata/dependencies.yaml @@ -0,0 +1,11 @@ +--- +dependencies: + - type: olm.package + value: + packageName: bar + version: <0.1.0 + - type: olm.gvk + value: + group: "test.bar" + version: "v1alpha1" + kind: "Bar" diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/index-declcfgs/exp-headsonly/index.yaml b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/index-declcfgs/exp-headsonly/index.yaml index 6aef07f88e..4b8fc10458 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/index-declcfgs/exp-headsonly/index.yaml +++ b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/index-declcfgs/exp-headsonly/index.yaml @@ -10,6 +10,7 @@ entries: - name: bar.v0.1.0 - name: bar.v0.2.0 replaces: bar.v0.1.0 + skipRange: <0.2.0 skips: - bar.v0.1.0 - name: bar.v1.0.0 diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-channel/index.yaml b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-channel/index.yaml new file mode 100644 index 0000000000..f49e7ae202 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-channel/index.yaml @@ -0,0 +1,248 @@ +--- +defaultChannel: stable +name: bar +schema: olm.package +--- +name: alpha +package: bar +schema: olm.channel +entries: + - name: bar.v0.1.0 + - name: bar.v0.2.0 + replaces: bar.v0.1.0 + skipRange: <0.2.0 + skips: + - bar.v0.1.0 + - name: bar.v1.0.0 + replaces: bar.v0.2.0 +--- +name: stable +package: bar +schema: olm.channel +entries: + - name: bar.v1.0.0 +--- +# Added because foo.v0.3.1 depends on bar <0.2.0 +image: test.registry/bar-operator/bar-bundle:v0.1.0 +name: bar.v0.1.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MC4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djAuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMC4xLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 0.1.0 +relatedImages: +- image: test.registry/bar-operator/bar:v0.1.0 + name: operator +schema: olm.bundle +--- +# Added because foo.v0.3.1 depends on Bar, test.bar/v1alpha1. +# Note that dependency selection cannot decide between bar 0.1.0 +# and 0.2.0, as this is resolver territory. +image: test.registry/bar-operator/bar-bundle:v0.2.0 +name: bar.v0.2.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMi4wIn0sIm5hbWUiOiJiYXIudjAuMi4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJhciIsImtpbmQiOiJCYXIiLCJuYW1lIjoiYmFycy50ZXN0LmJhciIsInZlcnNpb24iOiJ2MWFscGhhMSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXItb3BlcmF0b3IvYmFyOnYwLjIuMCIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmFyLnYwLjEuMCJdLCJ2ZXJzaW9uIjoiMC4yLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 0.2.0 +relatedImages: +- image: test.registry/bar-operator/bar:v0.2.0 + name: operator +schema: olm.bundle +--- +image: test.registry/bar-operator/bar-bundle:v1.0.0 +name: bar.v1.0.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSIsInNlcnZlZCI6dHJ1ZSwic3RvcmFnZSI6ZmFsc2V9LHsibmFtZSI6InYxIiwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfV19fQ== +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MS4wLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn0seyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djEuMC4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImJhci52MC4yLjAiLCJ2ZXJzaW9uIjoiMS4wLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1 +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 1.0.0 +relatedImages: +- image: test.registry/bar-operator/bar:v1.0.0 + name: operator +schema: olm.bundle +--- +defaultChannel: stable +name: baz +schema: olm.package +--- +schema: olm.channel +package: baz +name: stable +entries: +- name: baz.v1.0.0 + skipRange: <1.0.0 +- name: baz.v1.0.1 + replaces: baz.v1.0.0 + skipRange: <1.0.0 + skips: + - baz.v1.0.0 +- name: baz.v1.1.0 + replaces: baz.v1.0.0 + skips: + - baz.v1.0.1 +--- +image: test.registry/baz-operator/baz-bundle:v1.0.0 +name: baz.v1.0.0 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhei52MS4wLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmF6Iiwia2luZCI6IkJheiIsIm5hbWUiOiJiYXpzLnRlc3QuYmF6IiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhei1vcGVyYXRvci9iYXo6djEuMC4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMS4wLjAifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.0.0 +relatedImages: +- image: test.registry/baz-operator/baz:v1.0.0 + name: operator +schema: olm.bundle +--- +image: test.registry/baz-operator/baz-bundle:v1.0.1 +name: baz.v1.0.1 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzEuMC4xIn0sIm5hbWUiOiJiYXoudjEuMC4xIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJheiIsImtpbmQiOiJCYXoiLCJuYW1lIjoiYmF6cy50ZXN0LmJheiIsInZlcnNpb24iOiJ2MSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXotb3BlcmF0b3IvYmF6OnYxLjAuMSIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmF6LnYxLjAuMCJdLCJ2ZXJzaW9uIjoiMS4wLjEifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.0.1 +relatedImages: +- image: test.registry/baz-operator/baz:v1.0.1 + name: operator +schema: olm.bundle +--- +image: test.registry/baz-operator/baz-bundle:v1.1.0 +name: baz.v1.1.0 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhei52MS4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmF6Iiwia2luZCI6IkJheiIsIm5hbWUiOiJiYXpzLnRlc3QuYmF6IiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhei1vcGVyYXRvci9iYXo6djEuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImJhei52MS4wLjAiLCJ2ZXJzaW9uIjoiMS4xLjAifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.1.0 +relatedImages: +- image: test.registry/baz-operator/baz:v1.1.0 + name: operator +schema: olm.bundle +--- +defaultChannel: beta +name: foo +schema: olm.package +--- +name: beta +package: foo +schema: olm.channel +entries: + - name: foo.v0.3.1 + replaces: foo.v0.2.0 + skips: + - foo.v0.3.0 +--- +image: test.registry/foo-operator/foo-bundle:v0.3.1 +name: foo.v0.3.1 +package: foo +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvb3MudGVzdC5mb28ifSwic3BlYyI6eyJncm91cCI6InRlc3QuZm9vIiwibmFtZXMiOnsia2luZCI6IkZvbyIsInBsdXJhbCI6ImZvb3MifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSIsInNlcnZlZCI6dHJ1ZSwic3RvcmFnZSI6ZmFsc2V9LHsibmFtZSI6InYyIiwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfV19fQ== +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvby52MC4zLjEifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuZm9vIiwia2luZCI6IkZvbyIsIm5hbWUiOiJmb29zLnRlc3QuZm9vIiwidmVyc2lvbiI6InYxIn0seyJncm91cCI6InRlc3QuZm9vIiwia2luZCI6IkZvbyIsIm5hbWUiOiJmb29zLnRlc3QuZm9vIiwidmVyc2lvbiI6InYyIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Zvby1vcGVyYXRvci9mb286djAuMy4xIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImZvby52MC4yLjAiLCJza2lwcyI6WyJmb28udjAuMy4wIl0sInZlcnNpb24iOiIwLjMuMSJ9fQ== +- type: olm.gvk + value: + group: test.foo + kind: Foo + version: v1 +- type: olm.gvk + value: + group: test.foo + kind: Foo + version: v2 +- type: olm.gvk.required + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: foo + version: 0.3.1 +- type: olm.package.required + value: + packageName: bar + packageName: bar + versionRange: <0.2.0 +relatedImages: +- image: test.registry/foo-operator/foo:v0.3.1 + name: operator +schema: olm.bundle diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-pkg/index.yaml b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-pkg/index.yaml new file mode 100644 index 0000000000..f49e7ae202 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/alpha/action/testdata/index-declcfgs/exp-include-pkg/index.yaml @@ -0,0 +1,248 @@ +--- +defaultChannel: stable +name: bar +schema: olm.package +--- +name: alpha +package: bar +schema: olm.channel +entries: + - name: bar.v0.1.0 + - name: bar.v0.2.0 + replaces: bar.v0.1.0 + skipRange: <0.2.0 + skips: + - bar.v0.1.0 + - name: bar.v1.0.0 + replaces: bar.v0.2.0 +--- +name: stable +package: bar +schema: olm.channel +entries: + - name: bar.v1.0.0 +--- +# Added because foo.v0.3.1 depends on bar <0.2.0 +image: test.registry/bar-operator/bar-bundle:v0.1.0 +name: bar.v0.1.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MC4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djAuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMC4xLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 0.1.0 +relatedImages: +- image: test.registry/bar-operator/bar:v0.1.0 + name: operator +schema: olm.bundle +--- +# Added because foo.v0.3.1 depends on Bar, test.bar/v1alpha1. +# Note that dependency selection cannot decide between bar 0.1.0 +# and 0.2.0, as this is resolver territory. +image: test.registry/bar-operator/bar-bundle:v0.2.0 +name: bar.v0.2.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzAuMi4wIn0sIm5hbWUiOiJiYXIudjAuMi4wIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJhciIsImtpbmQiOiJCYXIiLCJuYW1lIjoiYmFycy50ZXN0LmJhciIsInZlcnNpb24iOiJ2MWFscGhhMSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXItb3BlcmF0b3IvYmFyOnYwLjIuMCIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmFyLnYwLjEuMCJdLCJ2ZXJzaW9uIjoiMC4yLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 0.2.0 +relatedImages: +- image: test.registry/bar-operator/bar:v0.2.0 + name: operator +schema: olm.bundle +--- +image: test.registry/bar-operator/bar-bundle:v1.0.0 +name: bar.v1.0.0 +package: bar +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhcnMudGVzdC5iYXIifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmFyIiwibmFtZXMiOnsia2luZCI6IkJhciIsInBsdXJhbCI6ImJhcnMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSIsInNlcnZlZCI6dHJ1ZSwic3RvcmFnZSI6ZmFsc2V9LHsibmFtZSI6InYxIiwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfV19fQ== +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhci52MS4wLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxYWxwaGExIn0seyJncm91cCI6InRlc3QuYmFyIiwia2luZCI6IkJhciIsIm5hbWUiOiJiYXJzLnRlc3QuYmFyIiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhci1vcGVyYXRvci9iYXI6djEuMC4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImJhci52MC4yLjAiLCJ2ZXJzaW9uIjoiMS4wLjAifX0= +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1 +- type: olm.gvk + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: bar + version: 1.0.0 +relatedImages: +- image: test.registry/bar-operator/bar:v1.0.0 + name: operator +schema: olm.bundle +--- +defaultChannel: stable +name: baz +schema: olm.package +--- +schema: olm.channel +package: baz +name: stable +entries: +- name: baz.v1.0.0 + skipRange: <1.0.0 +- name: baz.v1.0.1 + replaces: baz.v1.0.0 + skipRange: <1.0.0 + skips: + - baz.v1.0.0 +- name: baz.v1.1.0 + replaces: baz.v1.0.0 + skips: + - baz.v1.0.1 +--- +image: test.registry/baz-operator/baz-bundle:v1.0.0 +name: baz.v1.0.0 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhei52MS4wLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmF6Iiwia2luZCI6IkJheiIsIm5hbWUiOiJiYXpzLnRlc3QuYmF6IiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhei1vcGVyYXRvci9iYXo6djEuMC4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJ2ZXJzaW9uIjoiMS4wLjAifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.0.0 +relatedImages: +- image: test.registry/baz-operator/baz:v1.0.0 + name: operator +schema: olm.bundle +--- +image: test.registry/baz-operator/baz-bundle:v1.0.1 +name: baz.v1.0.1 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsib2xtLnNraXBSYW5nZSI6Ilx1MDAzYzEuMC4xIn0sIm5hbWUiOiJiYXoudjEuMC4xIn0sInNwZWMiOnsiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZ3JvdXAiOiJ0ZXN0LmJheiIsImtpbmQiOiJCYXoiLCJuYW1lIjoiYmF6cy50ZXN0LmJheiIsInZlcnNpb24iOiJ2MSJ9XX0sInJlbGF0ZWRJbWFnZXMiOlt7ImltYWdlIjoidGVzdC5yZWdpc3RyeS9iYXotb3BlcmF0b3IvYmF6OnYxLjAuMSIsIm5hbWUiOiJvcGVyYXRvciJ9XSwic2tpcHMiOlsiYmF6LnYxLjAuMCJdLCJ2ZXJzaW9uIjoiMS4wLjEifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.0.1 +relatedImages: +- image: test.registry/baz-operator/baz:v1.0.1 + name: operator +schema: olm.bundle +--- +image: test.registry/baz-operator/baz-bundle:v1.1.0 +name: baz.v1.1.0 +package: baz +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhenMudGVzdC5iYXoifSwic3BlYyI6eyJncm91cCI6InRlc3QuYmF6IiwibmFtZXMiOnsia2luZCI6IkJheiIsInBsdXJhbCI6ImJhenMifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSJ9XX19 +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImJhei52MS4xLjAifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuYmF6Iiwia2luZCI6IkJheiIsIm5hbWUiOiJiYXpzLnRlc3QuYmF6IiwidmVyc2lvbiI6InYxIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Jhei1vcGVyYXRvci9iYXo6djEuMS4wIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImJhei52MS4wLjAiLCJ2ZXJzaW9uIjoiMS4xLjAifX0= +- type: olm.gvk + value: + group: test.baz + kind: Baz + version: v1 +- type: olm.package + value: + packageName: baz + version: 1.1.0 +relatedImages: +- image: test.registry/baz-operator/baz:v1.1.0 + name: operator +schema: olm.bundle +--- +defaultChannel: beta +name: foo +schema: olm.package +--- +name: beta +package: foo +schema: olm.channel +entries: + - name: foo.v0.3.1 + replaces: foo.v0.2.0 + skips: + - foo.v0.3.0 +--- +image: test.registry/foo-operator/foo-bundle:v0.3.1 +name: foo.v0.3.1 +package: foo +properties: +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvb3MudGVzdC5mb28ifSwic3BlYyI6eyJncm91cCI6InRlc3QuZm9vIiwibmFtZXMiOnsia2luZCI6IkZvbyIsInBsdXJhbCI6ImZvb3MifSwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MSIsInNlcnZlZCI6dHJ1ZSwic3RvcmFnZSI6ZmFsc2V9LHsibmFtZSI6InYyIiwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlfV19fQ== +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsibmFtZSI6ImZvby52MC4zLjEifSwic3BlYyI6eyJjdXN0b21yZXNvdXJjZWRlZmluaXRpb25zIjp7Im93bmVkIjpbeyJncm91cCI6InRlc3QuZm9vIiwia2luZCI6IkZvbyIsIm5hbWUiOiJmb29zLnRlc3QuZm9vIiwidmVyc2lvbiI6InYxIn0seyJncm91cCI6InRlc3QuZm9vIiwia2luZCI6IkZvbyIsIm5hbWUiOiJmb29zLnRlc3QuZm9vIiwidmVyc2lvbiI6InYyIn1dfSwicmVsYXRlZEltYWdlcyI6W3siaW1hZ2UiOiJ0ZXN0LnJlZ2lzdHJ5L2Zvby1vcGVyYXRvci9mb286djAuMy4xIiwibmFtZSI6Im9wZXJhdG9yIn1dLCJyZXBsYWNlcyI6ImZvby52MC4yLjAiLCJza2lwcyI6WyJmb28udjAuMy4wIl0sInZlcnNpb24iOiIwLjMuMSJ9fQ== +- type: olm.gvk + value: + group: test.foo + kind: Foo + version: v1 +- type: olm.gvk + value: + group: test.foo + kind: Foo + version: v2 +- type: olm.gvk.required + value: + group: test.bar + kind: Bar + version: v1alpha1 +- type: olm.package + value: + packageName: foo + version: 0.3.1 +- type: olm.package.required + value: + packageName: bar + packageName: bar + versionRange: <0.2.0 +relatedImages: +- image: test.registry/foo-operator/foo:v0.3.1 + name: operator +schema: olm.bundle diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/declcfg_to_model.go b/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/declcfg_to_model.go index d539c165f6..55d7751797 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/declcfg_to_model.go +++ b/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/declcfg_to_model.go @@ -3,7 +3,7 @@ package declcfg import ( "fmt" - "github.com/blang/semver" + "github.com/blang/semver/v4" "k8s.io/apimachinery/pkg/util/sets" "github.com/operator-framework/operator-registry/alpha/model" @@ -18,6 +18,10 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { return nil, fmt.Errorf("config contains package with no name") } + if _, ok := mpkgs[p.Name]; ok { + return nil, fmt.Errorf("duplicate package %q", p.Name) + } + mpkg := &model.Package{ Name: p.Name, Description: p.Description, @@ -44,6 +48,10 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { return nil, fmt.Errorf("package %q contains channel with no name", c.Package) } + if _, ok := mpkg.Channels[c.Name]; ok { + return nil, fmt.Errorf("package %q has duplicate channel %q", c.Package, c.Name) + } + mch := &model.Channel{ Package: mpkg, Name: c.Name, @@ -75,6 +83,10 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { } } + // packageBundles tracks the set of bundle names for each package + // and is used to detect duplicate bundles. + packageBundles := map[string]sets.String{} + for _, b := range cfg.Bundles { if b.Package == "" { return nil, fmt.Errorf("package name must be set for bundle %q", b.Name) @@ -84,6 +96,16 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { return nil, fmt.Errorf("unknown package %q for bundle %q", b.Package, b.Name) } + bundles, ok := packageBundles[b.Package] + if !ok { + bundles = sets.NewString() + } + if bundles.Has(b.Name) { + return nil, fmt.Errorf("package %q has duplicate bundle %q", b.Package, b.Name) + } + bundles.Insert(b.Name) + packageBundles[b.Package] = bundles + props, err := property.Parse(b.Properties) if err != nil { return nil, fmt.Errorf("parse properties for bundle %q: %v", b.Name, err) @@ -98,9 +120,10 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) { } // Parse version from the package property. - ver, err := semver.Parse(props.Packages[0].Version) + rawVersion := props.Packages[0].Version + ver, err := semver.Parse(rawVersion) if err != nil { - return nil, fmt.Errorf("error parsing bundle version: %v", err) + return nil, fmt.Errorf("error parsing bundle %q version %q: %v", b.Name, rawVersion, err) } channelDefinedEntries[b.Package] = channelDefinedEntries[b.Package].Delete(b.Name) diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/diff.go b/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/diff.go index c4f78d660f..077ddc1655 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/diff.go +++ b/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/diff.go @@ -6,7 +6,7 @@ import ( "sort" "sync" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/mitchellh/hashstructure/v2" "github.com/sirupsen/logrus" @@ -21,6 +21,10 @@ type DiffGenerator struct { // SkipDependencies directs Run() to not include dependencies // of bundles included in the diff if true. SkipDependencies bool + // Includer for adding catalog objects to Run() output. + Includer DiffIncluder + // IncludeAdditively catalog objects specified in Includer in headsOnly mode. + IncludeAdditively bool initOnce sync.Once } @@ -30,13 +34,21 @@ func (g *DiffGenerator) init() { if g.Logger == nil { g.Logger = &logrus.Entry{} } + if g.Includer.Logger == nil { + g.Includer.Logger = g.Logger + } }) } -// Run returns a Model containing everything in newModel not in oldModel, -// and all bundles that exist in oldModel but are different in newModel. -// If oldModel is empty, only channel heads in newModel's packages are -// added to the output Model. All dependencies not in oldModel are also added. +// Run returns a Model containing a subset of catalog objects in newModel: +// - If g.Includer contains objects: +// - If g.IncludeAdditively is false, a diff will be generated only on those objects, +// depending on the mode. +// - If g.IncludeAdditionally is true, the diff will contain included objects, +// plus those added by the mode. +// - If in heads-only mode (oldModel == nil), then the heads of channels are added to the output. +// - If in latest mode, a diff between old and new Models is added to the output. +// - Dependencies are added in all modes if g.SkipDependencies is false. func (g *DiffGenerator) Run(oldModel, newModel model.Model) (model.Model, error) { g.init() @@ -45,53 +57,102 @@ func (g *DiffGenerator) Run(oldModel, newModel model.Model) (model.Model, error) // load by package. outputModel := model.Model{} - if len(oldModel) == 0 { - // Heads-only mode. - - // Make shallow copies of packages and channels that are only - // filled with channel heads. - for _, newPkg := range newModel { - outputPkg := copyPackageNoChannels(newPkg) - outputModel[outputPkg.Name] = outputPkg - for _, newCh := range newPkg.Channels { - outputCh := copyChannelNoBundles(newCh, outputPkg) - outputPkg.Channels[outputCh.Name] = outputCh - head, err := newCh.Head() - if err != nil { - return nil, err - } - outputBundle := copyBundle(head, outputCh, outputPkg) - outputModel.AddBundle(*outputBundle) - } - } - } else { - // Latest mode. - - // Copy newModel to create an output model by deletion, - // which is more succinct than by addition and potentially - // more memory efficient. - for _, newPkg := range newModel { - outputModel[newPkg.Name] = copyPackage(newPkg) - } - // NB(estroz): if a net-new package or channel is published, - // this currently adds the entire package. I'm fairly sure - // this behavior is ok because the next diff after a new - // package is published still has only new data. + // Prunes old objects from outputModel if they exist. + latestPruneFromOutput := func() error { + for _, outputPkg := range outputModel { oldPkg, oldHasPkg := oldModel[outputPkg.Name] if !oldHasPkg { // outputPkg was already copied to outputModel above. continue } - if err := diffPackages(oldPkg, outputPkg); err != nil { - return nil, err + if err := pruneOldFromNewPackage(oldPkg, outputPkg); err != nil { + return err } if len(outputPkg.Channels) == 0 { // Remove empty packages. delete(outputModel, outputPkg.Name) } } + + return nil + } + + headsOnlyMode := len(oldModel) == 0 + latestMode := !headsOnlyMode + isInclude := len(g.Includer.Packages) != 0 + + switch { + case !g.IncludeAdditively && isInclude: // Only diff between included objects. + + // Add included packages/channels/bundles from newModel to outputModel. + if err := g.Includer.Run(newModel, outputModel); err != nil { + return nil, err + } + + if latestMode { + if err := latestPruneFromOutput(); err != nil { + return nil, err + } + } + + case isInclude: // Add included objects to outputModel. + + // Add included packages/channels/bundles from newModel to outputModel. + if err := g.Includer.Run(newModel, outputModel); err != nil { + return nil, err + } + + fallthrough + default: + + if headsOnlyMode { // Net-new diff of heads only. + + // Make shallow copies of packages and channels that are only + // filled with channel heads. + for _, newPkg := range newModel { + // This package may have been created in the include step. + outputPkg, pkgIncluded := outputModel[newPkg.Name] + if !pkgIncluded { + outputPkg = copyPackageNoChannels(newPkg) + outputModel[outputPkg.Name] = outputPkg + } + for _, newCh := range newPkg.Channels { + if _, chIncluded := outputPkg.Channels[newCh.Name]; chIncluded { + // Head (and other bundles) were added in the include step. + continue + } + outputCh := copyChannelNoBundles(newCh, outputPkg) + outputPkg.Channels[outputCh.Name] = outputCh + head, err := newCh.Head() + if err != nil { + return nil, err + } + outputBundle := copyBundle(head, outputCh, outputPkg) + outputModel.AddBundle(*outputBundle) + } + } + + } else { // Diff between old and new Model. + + // Copy newModel to create an output model by deletion, + // which is more succinct than by addition. + for _, newPkg := range newModel { + if _, pkgIncluded := outputModel[newPkg.Name]; pkgIncluded { + // The user has specified the state they want this package to have in the diff + // via an inclusion entry, so the package created above should not be changed. + continue + } + outputModel[newPkg.Name] = copyPackage(newPkg) + } + + if err := latestPruneFromOutput(); err != nil { + return nil, err + } + + } + } if !g.SkipDependencies { @@ -103,8 +164,8 @@ func (g *DiffGenerator) Run(oldModel, newModel model.Model) (model.Model, error) // Default channel may not have been copied, so set it to the new default channel here. for _, outputPkg := range outputModel { - outputHasDefault := false newPkg := newModel[outputPkg.Name] + var outputHasDefault bool outputPkg.DefaultChannel, outputHasDefault = outputPkg.Channels[newPkg.DefaultChannel.Name] if !outputHasDefault { // Create a name-only channel since oldModel contains the channel already. @@ -115,9 +176,9 @@ func (g *DiffGenerator) Run(oldModel, newModel model.Model) (model.Model, error) return outputModel, nil } -// diffPackages removes any bundles and channels from newPkg that +// pruneOldFromNewPackage prune any bundles and channels from newPkg that // are in oldPkg, but not those that differ in any way. -func diffPackages(oldPkg, newPkg *model.Package) error { +func pruneOldFromNewPackage(oldPkg, newPkg *model.Package) error { for _, newCh := range newPkg.Channels { oldCh, oldHasCh := oldPkg.Channels[newCh.Name] if !oldHasCh { @@ -274,7 +335,6 @@ func getBundles(m model.Model) (bundles []*model.Bundle) { for _, pkg := range m { for _, ch := range pkg.Channels { for _, b := range ch.Bundles { - b := b bundles = append(bundles, b) } } @@ -332,7 +392,6 @@ func getBundlesThatProvide(pkg *model.Package, reqGVKs map[property.GVK]struct{} bundlesProvidingGVK := make(map[property.GVK][]*model.Bundle) for _, ch := range pkg.Channels { for _, b := range ch.Bundles { - b := b for _, gvk := range b.PropertiesP.GVKs { if _, hasGVK := reqGVKs[gvk]; hasGVK { bundlesProvidingGVK[gvk] = append(bundlesProvidingGVK[gvk], b) @@ -438,15 +497,19 @@ func copyChannel(in *model.Channel, pkg *model.Package) *model.Channel { func copyBundle(in *model.Bundle, ch *model.Channel, pkg *model.Package) *model.Bundle { cp := &model.Bundle{ - Name: in.Name, - Channel: ch, - Package: pkg, - Image: in.Image, - Replaces: in.Replaces, - Version: semver.MustParse(in.Version.String()), - CsvJSON: in.CsvJSON, + Name: in.Name, + Channel: ch, + Package: pkg, + Image: in.Image, + Replaces: in.Replaces, + Version: semver.MustParse(in.Version.String()), + CsvJSON: in.CsvJSON, + SkipRange: in.SkipRange, + } + if in.PropertiesP != nil { + cp.PropertiesP = new(property.Properties) + *cp.PropertiesP = *in.PropertiesP } - cp.PropertiesP, _ = property.Parse(in.Properties) if len(in.Skips) != 0 { cp.Skips = make([]string, len(in.Skips)) copy(cp.Skips, in.Skips) diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/diff_include.go b/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/diff_include.go index 14a4d5c890..06e9a64a61 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/diff_include.go +++ b/vendor/github.com/operator-framework/operator-registry/alpha/declcfg/diff_include.go @@ -1,14 +1,215 @@ package declcfg import ( + "fmt" + "strings" + + "github.com/blang/semver/v4" + "github.com/sirupsen/logrus" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "github.com/operator-framework/operator-registry/alpha/model" ) +// DiffIncluder knows how to add packages, channels, and bundles +// from a source to a destination model.Model. +type DiffIncluder struct { + // Packages to add. + Packages []DiffIncludePackage + Logger *logrus.Entry +} + +// DiffIncludePackage specifies a package, and optionally channels +// or a set of bundles from all channels (wrapped by a DiffIncludeChannel), +// to include. +type DiffIncludePackage struct { + // Name of package. + Name string + // Channels in package. + Channels []DiffIncludeChannel + // AllChannels contains bundle versions in package. + // Upgrade graphs from all channels in the named package containing a version + // from this field are included. + AllChannels DiffIncludeChannel +} + +// DiffIncludeChannel specifies a channel, and optionally bundles and bundle versions to include. +type DiffIncludeChannel struct { + // Name of channel. + Name string + // Versions of bundles. + Versions []semver.Version + // Bundles are bundle names to include. + // Set this field only if the named bundle has no semantic version metadata. + Bundles []string +} + +// Run adds all packages and channels in DiffIncluder with matching names +// directly, and all versions plus their upgrade graphs to channel heads, +// from newModel to outputModel. +func (i DiffIncluder) Run(newModel, outputModel model.Model) error { + var includeErrs []error + for _, ipkg := range i.Packages { + pkgLog := i.Logger.WithField("package", ipkg.Name) + includeErrs = append(includeErrs, ipkg.includeNewInOutputModel(newModel, outputModel, pkgLog)...) + } + if len(includeErrs) != 0 { + return fmt.Errorf("error including items:\n%v", utilerrors.NewAggregate(includeErrs)) + } + return nil +} + +// includeNewInOutputModel adds all packages, channels, and versions (bundles) +// specified by ipkg that exist in newModel to outputModel. Any package, channel, +// or version in ipkg not satisfied by newModel is an error. +func (ipkg DiffIncludePackage) includeNewInOutputModel(newModel, outputModel model.Model, logger *logrus.Entry) (ierrs []error) { + + newPkg, newHasPkg := newModel[ipkg.Name] + if !newHasPkg { + ierrs = append(ierrs, fmt.Errorf("[package=%q] package does not exist in new model", ipkg.Name)) + return ierrs + } + pkgLog := logger.WithField("package", newPkg.Name) + + // No channels or versions were specified, meaning "include the full package". + if len(ipkg.Channels) == 0 && len(ipkg.AllChannels.Versions) == 0 && len(ipkg.AllChannels.Bundles) == 0 { + outputModel[ipkg.Name] = newPkg + return nil + } + + outputPkg := copyPackageNoChannels(newPkg) + outputModel[outputPkg.Name] = outputPkg + + // Add all channels to ipkg.Channels if bundles or versions were specified to include across all channels. + // skipMissingBundleForChannels's value for a channel will be true IFF at least one version is specified, + // since some other channel may contain that version. + skipMissingBundleForChannels := map[string]bool{} + if len(ipkg.AllChannels.Versions) != 0 || len(ipkg.AllChannels.Bundles) != 0 { + for newChName := range newPkg.Channels { + ipkg.Channels = append(ipkg.Channels, DiffIncludeChannel{ + Name: newChName, + Versions: ipkg.AllChannels.Versions, + Bundles: ipkg.AllChannels.Bundles, + }) + skipMissingBundleForChannels[newChName] = true + } + } + + for _, ich := range ipkg.Channels { + newCh, pkgHasCh := newPkg.Channels[ich.Name] + if !pkgHasCh { + ierrs = append(ierrs, fmt.Errorf("[package=%q channel=%q] channel does not exist in new model", newPkg.Name, ich.Name)) + continue + } + chLog := pkgLog.WithField("channel", newCh.Name) + + bundles, err := getBundlesForVersions(newCh, ich.Versions, ich.Bundles, chLog, skipMissingBundleForChannels[newCh.Name]) + if err != nil { + ierrs = append(ierrs, fmt.Errorf("[package=%q channel=%q] %v", newPkg.Name, newCh.Name, err)) + continue + } + outputCh := copyChannelNoBundles(newCh, outputPkg) + outputPkg.Channels[outputCh.Name] = outputCh + for _, b := range bundles { + tb := copyBundle(b, outputCh, outputPkg) + outputCh.Bundles[tb.Name] = tb + } + } + + return ierrs +} + +// getBundlesForVersions returns all bundles matching a version in vers +// and their upgrade graph(s) to ch.Head(). +// If skipMissingBundles is true, bundle names and versions not satisfied by bundles in ch +// will not result in errors. +func getBundlesForVersions(ch *model.Channel, vers []semver.Version, names []string, logger *logrus.Entry, skipMissingBundles bool) (bundles []*model.Bundle, err error) { + + // Short circuit when no versions were specified, meaning "include the whole channel". + if len(vers) == 0 { + for _, b := range ch.Bundles { + bundles = append(bundles, b) + } + return bundles, nil + } + + // Add every bundle with a specified bundle name or directly satisfying a bundle version to bundles. + versionsToInclude := make(map[string]struct{}, len(vers)) + for _, ver := range vers { + versionsToInclude[ver.String()] = struct{}{} + } + namesToInclude := make(map[string]struct{}, len(vers)) + for _, name := range names { + namesToInclude[name] = struct{}{} + } + for _, b := range ch.Bundles { + _, includeVersionedBundle := versionsToInclude[b.Version.String()] + _, includeNamedBundle := namesToInclude[b.Name] + if includeVersionedBundle || includeNamedBundle { + bundles = append(bundles, b) + } + } + + // Some version was not satisfied by this channel. + if len(bundles) != len(versionsToInclude)+len(namesToInclude) && !skipMissingBundles { + for _, b := range bundles { + delete(versionsToInclude, b.Version.String()) + delete(namesToInclude, b.Name) + } + var verStrs, nameStrs []string + for verStr := range versionsToInclude { + verStrs = append(verStrs, verStr) + } + for nameStr := range namesToInclude { + nameStrs = append(nameStrs, nameStr) + } + sb := strings.Builder{} + if len(verStrs) != 0 { + sb.WriteString(fmt.Sprintf("versions=%+q ", verStrs)) + } + if len(nameStrs) != 0 { + sb.WriteString(fmt.Sprintf("names=%+q", nameStrs)) + } + return nil, fmt.Errorf("bundles do not exist in channel: %s", strings.TrimSpace(sb.String())) + } + + // Fill in the upgrade graph between each bundle and head. + // Regardless of semver order, this step needs to be performed + // for each included bundle because there might be leaf nodes + // in the upgrade graph for a particular version not captured + // by any other fill due to skips not being honored here. + head, err := ch.Head() + if err != nil { + return nil, err + } + graph := makeUpgradeGraph(ch) + bundleSet := map[string]*model.Bundle{} + for _, ib := range bundles { + if _, addedBundle := bundleSet[ib.Name]; addedBundle { + // A prior graph traverse already included this bundle. + continue + } + intersectingBundles, intersectionFound := findIntersectingBundles(ch, ib, head, graph) + if !intersectionFound { + logger.Debugf("channel head %q not reachable from bundle %q, adding without upgrade graph", head.Name, ib.Name) + bundleSet[ib.Name] = ib + } + + for _, rb := range intersectingBundles { + bundleSet[rb.Name] = rb + } + } + for _, b := range bundleSet { + bundles = append(bundles, b) + } + + return bundles, nil +} + // makeUpgradeGraph creates a DAG of bundles with map key Bundle.Replaces. func makeUpgradeGraph(ch *model.Channel) map[string][]*model.Bundle { graph := map[string][]*model.Bundle{} for _, b := range ch.Bundles { - b := b if b.Replaces != "" { graph[b.Replaces] = append(graph[b.Replaces], b) } diff --git a/vendor/github.com/operator-framework/operator-registry/alpha/model/model.go b/vendor/github.com/operator-framework/operator-registry/alpha/model/model.go index babcd08cc7..75042469a2 100644 --- a/vendor/github.com/operator-framework/operator-registry/alpha/model/model.go +++ b/vendor/github.com/operator-framework/operator-registry/alpha/model/model.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/h2non/filetype" "github.com/h2non/filetype/matchers" "github.com/h2non/filetype/types" diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/build.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/build.go index 79901e1af7..c35ce80c92 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/build.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/build.go @@ -23,25 +23,25 @@ func newBundleBuildCmd() *cobra.Command { Use: "build", Short: "Build operator bundle image", Long: `The "opm alpha bundle build" command will generate operator - bundle metadata if needed and build bundle image with operator manifest - and metadata for a specific version. +bundle metadata if needed and build bundle image with operator manifest +and metadata for a specific version. - For example: The command will generate annotations.yaml metadata plus - Dockerfile for bundle image and then build a container image from - provided operator bundle manifests generated metadata - e.g. "quay.io/example/operator:v0.0.1". +For example: The command will generate annotations.yaml metadata plus +Dockerfile for bundle image and then build a container image from +provided operator bundle manifests generated metadata +e.g. "quay.io/example/operator:v0.0.1". - After the build process is completed, a container image would be built - locally in docker and available to push to a container registry. +After the build process is completed, a container image would be built +locally in docker and available to push to a container registry. - $ opm alpha bundle build --directory /test/0.1.0/ --tag quay.io/example/operator:v0.1.0 \ - --package test-operator --channels stable,beta --default stable --overwrite +$ opm alpha bundle build --directory /test/0.1.0/ --tag quay.io/example/operator:v0.1.0 \ + --package test-operator --channels stable,beta --default stable --overwrite - Note: - * Bundle image is not runnable. - * All manifests yaml must be in the same directory. - `, +Note: +* Bundle image is not runnable. +* All manifests yaml must be in the same directory. `, RunE: buildFunc, + Args: cobra.NoArgs, } bundleBuildCmd.Flags().StringVarP(&buildDir, "directory", "d", "", @@ -79,7 +79,7 @@ func newBundleBuildCmd() *cobra.Command { return bundleBuildCmd } -func buildFunc(cmd *cobra.Command, args []string) error { +func buildFunc(cmd *cobra.Command, _ []string) error { return bundle.BuildFunc( buildDir, outputDir, diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/cmd.go index bdd95bf13a..c318db6beb 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/cmd.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/cmd.go @@ -9,6 +9,7 @@ func NewCmd() *cobra.Command { Use: "bundle", Short: "Operator bundle commands", Long: `Generate operator bundle metadata and build bundle image.`, + Args: cobra.NoArgs, } runCmd.AddCommand(newBundleGenerateCmd()) diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/extract.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/extract.go index ba8b716266..d07273b3c4 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/extract.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/extract.go @@ -14,7 +14,7 @@ var extractCmd = &cobra.Command{ Short: "Extracts the data in a bundle directory via ConfigMap", Long: "Extract takes as input a directory containing manifests and writes the per file contents to a ConfipMap", - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -22,6 +22,7 @@ var extractCmd = &cobra.Command{ }, RunE: runExtractCmd, + Args: cobra.NoArgs, } func init() { @@ -35,7 +36,7 @@ func init() { extractCmd.MarkPersistentFlagRequired("configmapname") } -func runExtractCmd(cmd *cobra.Command, args []string) error { +func runExtractCmd(cmd *cobra.Command, _ []string) error { manifestsDir, err := cmd.Flags().GetString("manifestsdir") if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/generate.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/generate.go index 1aaa092321..417a22c379 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/generate.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/generate.go @@ -14,15 +14,15 @@ func newBundleGenerateCmd() *cobra.Command { Use: "generate", Short: "Generate operator bundle metadata and Dockerfile", Long: `The "opm alpha bundle generate" command will generate operator - bundle metadata if needed and a Dockerfile to build Operator bundle image. +bundle metadata if needed and a Dockerfile to build Operator bundle image. - $ opm alpha bundle generate --directory /test/0.1.0/ --package test-operator \ - --channels stable,beta --default stable +$ opm alpha bundle generate --directory /test/0.1.0/ --package test-operator \ + --channels stable,beta --default stable - Note: - * All manifests yaml must be in the same directory. - `, +Note: +* All manifests yaml must be in the same directory.`, RunE: generateFunc, + Args: cobra.NoArgs, } bundleGenerateCmd.Flags().StringVarP(&buildDir, "directory", "d", "", @@ -48,7 +48,7 @@ func newBundleGenerateCmd() *cobra.Command { return bundleGenerateCmd } -func generateFunc(cmd *cobra.Command, args []string) error { +func generateFunc(cmd *cobra.Command, _ []string) error { return bundle.GenerateFunc( buildDir, outputDir, diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/validate.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/validate.go index 4e32503bc9..9406e79a3f 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/validate.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle/validate.go @@ -38,6 +38,7 @@ Optional validators. These validators are disabled by default and can be enabled See https://olm.operatorframework.io/docs/tasks/validate-package/#validation for more info.`, Example: `$ opm alpha bundle validate --tag quay.io/test/test-operator:latest --image-builder docker`, RunE: validateFunc, + Args: cobra.NoArgs, } bundleValidateCmd.Flags().StringVarP(&tag, "tag", "t", "", @@ -52,7 +53,7 @@ See https://olm.operatorframework.io/docs/tasks/validate-package/#validation for return bundleValidateCmd } -func validateFunc(cmd *cobra.Command, args []string) error { +func validateFunc(cmd *cobra.Command, _ []string) error { logger := log.WithFields(log.Fields{"container-tool": containerTool}) log.SetLevel(log.DebugLevel) diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/cmd.go index 78335f76b7..7afed8b098 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/cmd.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/cmd.go @@ -14,6 +14,7 @@ func NewCmd() *cobra.Command { Hidden: true, Use: "alpha", Short: "Run an alpha subcommand", + Args: cobra.NoArgs, } runCmd.AddCommand( diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/diff/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/diff/cmd.go index f1a83f759e..752e4d343a 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/diff/cmd.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/diff/cmd.go @@ -24,9 +24,11 @@ const ( ) type diff struct { - oldRefs []string - newRefs []string - skipDeps bool + oldRefs []string + newRefs []string + skipDeps bool + includeAdditive bool + includeFile string output string caFile string @@ -35,6 +37,18 @@ type diff struct { logger *logrus.Entry } +// Example include file needs to be formatted separately so indentation is not messed up. +var includeFileExample = fmt.Sprintf(`packages: +%[1]s- name: foo +%[1]s- name: bar +%[1]s channels: +%[1]s - name: stable +%[1]s- name: baz +%[1]s channels: +%[1]s - name: alpha +%[1]s versions: +%[1]s - 0.2.0-alpha.0`, templates.Indentation) + func NewCmd() *cobra.Command { a := diff{ logger: logrus.NewEntry(logrus.New()), @@ -43,32 +57,28 @@ func NewCmd() *cobra.Command { Use: "diff [old-refs]... new-refs...", Short: "Diff old and new catalog references into a declarative config", Long: templates.LongDesc(` -Diff a set of old and new catalog references ("refs") to produce a -declarative config containing only packages channels, and versions not present -in the old set, and versions that differ between the old and new sets. This is known as "latest" mode. - -These references are passed through 'opm render' to produce a single declarative config. -Bundle image refs are not supported directly; a valid "olm.package" declarative config object -referring to the bundle's package must exist in all input refs. - -This command has special behavior when old-refs are omitted, called "heads-only" mode: -instead of the output being that of 'opm render refs...' -(which would be the case given the preceding behavior description), -only the channel heads of all channels in all packages are included in the output, -and dependencies. Dependencies are assumed to be provided by either an old ref, -in which case they are not included in the diff, or a new ref, in which -case they are included. Dependencies provided by some catalog unknown to -'opm alpha diff' will not cause the command to error, but an error will occur -if that catalog is not serving these dependencies at runtime. -Dependency inclusion can be turned off with --no-deps, although this is not recommended -unless you are certain some in-cluster catalog satisfies all dependencies. +'diff' returns a declarative config containing packages, channels, and versions +from new-refs, optionally removing those in old-refs or those omitted by an include config file. + +Each set of refs is passed to 'opm render ' to produce a single, normalized delcarative config. + +Depending on what arguments are provided to the command, a particular "mode" is invoked to produce a diff: -NOTE: for now, if any dependency exists, the entire dependency's package is added to the diff. -In the future, these packages will be pruned such that only the latest dependencies -satisfying a package version range or GVK, and their upgrade graph(s) to their latest -channel head(s), are included in the diff. +- If in heads-only mode (old-refs is not specified), then the heads of channels in new-refs are added to the output. +- If in latest mode (old-refs is specified), a diff between old-refs and new-refs is added to the output. +- If --include-file is set, items from that file will be added to the diff: + - If --include-additive is false (the default), a diff will be generated only on those objects, depending on the mode. + - If --include-additive is true, the diff will contain included objects, plus those added by the mode's invocation. + +Dependencies are added in all modes if --skip-deps is false (the default). +Dependencies are assumed to be provided by either an old-ref, in which case they are not included in the diff, +or a new-ref, in which case they are included. +Dependencies provided by some catalog unknown to 'diff' will not cause the command to error, +but an error will occur if that catalog is not serving these dependencies at runtime. +While dependency inclusion can be turned off with --skip-deps, doing so is not recommended +unless you are certain some in-cluster catalog satisfies all dependencies. `), - Example: templates.Examples(` + Example: fmt.Sprintf(templates.Examples(` # Create a directory for your declarative config diff. mkdir -p my-catalog-index @@ -81,13 +91,27 @@ opm alpha diff registry.org/my-catalog:abc123 registry.org/my-catalog:def456 -o # Create a new catalog from the heads of an existing catalog. opm alpha diff registry.org/my-catalog:def456 -o yaml > my-catalog-index/index.yaml +# OR: +# Only include all of package "foo", package "bar" channel "stable", +# and package "baz" channel "alpha" version "0.2.0-alpha.0" (and its upgrade graph) in the diff. +cat < include.yaml +%s +EOF +opm alpha diff registry.org/my-catalog:def456 -i include.yaml -o yaml > pruned-index/index.yaml + +# OR: +# Include all of package "foo", package "bar" channel "stable", +# and package "baz" channel "alpha" version "0.2.0-alpha.0" in the diff +# on top of heads of all other channels in all packages (using the above include.yaml). +opm alpha diff registry.org/my-catalog:def456 -i include.yaml --include-additive -o yaml > pruned-index/index.yaml + # FINALLY: # Build an index image containing the diff-ed declarative config, # then tag and push it. opm alpha generate dockerfile ./my-catalog-index docker build -t registry.org/my-catalog:diff-latest -f index.Dockerfile . docker push registry.org/my-catalog:diff-latest -`), +`), includeFileExample), Args: cobra.RangeArgs(1, 2), PreRunE: func(cmd *cobra.Command, args []string) error { if a.debug { @@ -102,7 +126,12 @@ docker push registry.org/my-catalog:diff-latest cmd.Flags().BoolVar(&a.skipDeps, "skip-deps", false, "do not include bundle dependencies in the output catalog") cmd.Flags().StringVarP(&a.output, "output", "o", "yaml", "Output format (json|yaml)") - cmd.Flags().StringVarP(&a.caFile, "ca-file", "", "", "the root Certificates to use with this command") + cmd.Flags().StringVar(&a.caFile, "ca-file", "", "the root Certificates to use with this command") + cmd.Flags().StringVarP(&a.includeFile, "include-file", "i", "", + "YAML defining packages, channels, and/or bundles/versions to extract from the new refs. "+ + "Upgrade graphs from individual bundles/versions to their channel's head are also included") + cmd.Flags().BoolVar(&a.includeAdditive, "include-additive", false, + "Ref objects from --include-file are returned on top of 'heads-only' or 'latest' output") cmd.Flags().BoolVar(&a.debug, "debug", false, "enable debug logging") return cmd @@ -111,9 +140,8 @@ docker push registry.org/my-catalog:diff-latest func (a *diff) addFunc(cmd *cobra.Command, args []string) error { a.parseArgs(args) - skipTLS, err := cmd.Flags().GetBool("skip-tls") - if err != nil { - logrus.Panic(err) + if cmd.Flags().Changed("include-additive") && a.includeFile == "" { + a.logger.Fatal("must set --include-file if --include-additive is set") } var write func(declcfg.DeclarativeConfig, io.Writer) error @@ -126,6 +154,10 @@ func (a *diff) addFunc(cmd *cobra.Command, args []string) error { return fmt.Errorf("invalid --output value: %q", a.output) } + skipTLS, err := cmd.Flags().GetBool("skip-tls") + if err != nil { + logrus.Panic(err) + } rootCAs, err := certs.RootCAs(a.caFile) if err != nil { a.logger.Fatalf("error getting root CAs: %v", err) @@ -140,16 +172,33 @@ func (a *diff) addFunc(cmd *cobra.Command, args []string) error { } }() + diff := action.Diff{ + Registry: reg, + OldRefs: a.oldRefs, + NewRefs: a.newRefs, + SkipDependencies: a.skipDeps, + IncludeAdditively: a.includeAdditive, + Logger: a.logger, + } + + if a.includeFile != "" { + f, err := os.Open(a.includeFile) + if err != nil { + a.logger.Fatalf("error opening include file: %v", err) + } + defer func() { + if cerr := f.Close(); cerr != nil { + a.logger.Error(cerr) + } + }() + if diff.IncludeConfig, err = action.LoadDiffIncludeConfig(f); err != nil { + a.logger.Fatalf("error loading include file: %v", err) + } + } + ctx, cancel := context.WithTimeout(context.TODO(), timeout) defer cancel() - diff := action.Diff{ - Registry: reg, - OldRefs: a.oldRefs, - NewRefs: a.newRefs, - SkipDependencies: a.skipDeps, - Logger: a.logger, - } cfg, err := diff.Run(ctx) if err != nil { a.logger.Fatalf("error generating diff: %v", err) diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/generate/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/generate/cmd.go index 092f2310af..7871a7f52f 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/generate/cmd.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/alpha/generate/cmd.go @@ -44,7 +44,7 @@ When specifying extra labels, note that if duplicate keys exist, only the last value of each duplicate key will be added to the generated Dockerfile. `, RunE: func(_ *cobra.Command, args []string) error { - fromDir := args[0] + fromDir := filepath.Clean(args[0]) extraLabels, err := parseLabels(extraLabelStrs) if err != nil { diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/add.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/add.go index 480cd9acac..ea218d6e04 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/add.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/add.go @@ -45,13 +45,14 @@ func addIndexAddCmd(parent *cobra.Command) { Use: "add", Short: "Add operator bundles to an index.", Long: addLong, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } return nil }, RunE: runIndexAddCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") @@ -89,7 +90,7 @@ func addIndexAddCmd(parent *cobra.Command) { } -func runIndexAddCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexAddCmdFunc(cmd *cobra.Command, _ []string) error { generate, err := cmd.Flags().GetBool("generate") if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/cmd.go index 44141415b9..b7cdd14d15 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/cmd.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/cmd.go @@ -16,18 +16,19 @@ func AddCommand(parent *cobra.Command) { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } return nil }, - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRun: func(cmd *cobra.Command, _ []string) { sqlite.LogSqliteDeprecation() if skipTLS, err := cmd.Flags().GetBool("skip-tls"); err == nil && skipTLS { logrus.Warn("--skip-tls flag is set: this mode is insecure and meant for development purposes only.") } }, + Args: cobra.NoArgs, } parent.AddCommand(cmd) diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/delete.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/delete.go index 9b249e2021..c9472b8ed2 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/delete.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/delete.go @@ -17,7 +17,7 @@ func newIndexDeleteCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -25,6 +25,7 @@ func newIndexDeleteCmd() *cobra.Command { }, RunE: runIndexDeleteCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") @@ -53,7 +54,7 @@ func newIndexDeleteCmd() *cobra.Command { } -func runIndexDeleteCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexDeleteCmdFunc(cmd *cobra.Command, _ []string) error { generate, err := cmd.Flags().GetBool("generate") if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/deprecatetruncate.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/deprecatetruncate.go index dc76e9ab9c..9324d35b8e 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/deprecatetruncate.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/deprecatetruncate.go @@ -37,13 +37,14 @@ func newIndexDeprecateTruncateCmd() *cobra.Command { Use: "deprecatetruncate", Short: "Deprecate and truncate operator bundles from an index.", Long: deprecateLong, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } return nil }, RunE: runIndexDeprecateTruncateCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") @@ -68,7 +69,7 @@ func newIndexDeprecateTruncateCmd() *cobra.Command { return indexCmd } -func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, _ []string) error { generate, err := cmd.Flags().GetBool("generate") if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/export.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/export.go index 910b6d02e2..f4e32672e7 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/export.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/export.go @@ -27,7 +27,7 @@ func newIndexExportCmd() *cobra.Command { Short: "Export an operator from an index into the appregistry format", Long: exportLong, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -35,6 +35,7 @@ func newIndexExportCmd() *cobra.Command { }, RunE: runIndexExportCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") indexCmd.Flags().StringP("index", "i", "", "index to get package from") @@ -58,7 +59,7 @@ func newIndexExportCmd() *cobra.Command { } -func runIndexExportCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexExportCmdFunc(cmd *cobra.Command, _ []string) error { index, err := cmd.Flags().GetString("index") if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/prune.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/prune.go index 5cee902794..c80ebf6198 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/prune.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/prune.go @@ -19,7 +19,7 @@ func newIndexPruneCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -27,6 +27,7 @@ func newIndexPruneCmd() *cobra.Command { }, RunE: runIndexPruneCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") @@ -53,7 +54,7 @@ func newIndexPruneCmd() *cobra.Command { } -func runIndexPruneCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexPruneCmdFunc(cmd *cobra.Command, _ []string) error { generate, err := cmd.Flags().GetBool("generate") if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/prunestranded.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/prunestranded.go index c323c619a3..03e739cff5 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/prunestranded.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/prunestranded.go @@ -19,7 +19,7 @@ func newIndexPruneStrandedCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -27,6 +27,7 @@ func newIndexPruneStrandedCmd() *cobra.Command { }, RunE: runIndexPruneStrandedCmdFunc, + Args: cobra.NoArgs, } indexCmd.Flags().Bool("debug", false, "enable debug logging") @@ -48,7 +49,7 @@ func newIndexPruneStrandedCmd() *cobra.Command { } -func runIndexPruneStrandedCmdFunc(cmd *cobra.Command, args []string) error { +func runIndexPruneStrandedCmdFunc(cmd *cobra.Command, _ []string) error { generate, err := cmd.Flags().GetBool("generate") if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/migrate/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/migrate/cmd.go new file mode 100644 index 0000000000..6405060b0a --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/migrate/cmd.go @@ -0,0 +1,54 @@ +package migrate + +import ( + "log" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/operator-framework/operator-registry/alpha/action" + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/pkg/sqlite" +) + +func NewCmd() *cobra.Command { + var ( + migrate action.Migrate + output string + ) + cmd := &cobra.Command{ + Use: "migrate ", + Short: "Migrate a sqlite-based index image or database file to a file-based catalog", + Long: `Migrate a sqlite-based index image or database file to a file-based catalog. + +` + sqlite.DeprecationMessage, + Args: cobra.ExactArgs(2), + PersistentPreRun: func(_ *cobra.Command, _ []string) { + sqlite.LogSqliteDeprecation() + }, + RunE: func(cmd *cobra.Command, args []string) error { + migrate.CatalogRef = args[0] + migrate.OutputDir = args[1] + + switch output { + case "yaml": + migrate.WriteFunc = declcfg.WriteYAML + migrate.FileExt = ".yaml" + case "json": + migrate.WriteFunc = declcfg.WriteJSON + migrate.FileExt = ".json" + default: + log.Fatalf("invalid --output value %q, expected (json|yaml)", output) + } + + logrus.Infof("rendering index %q as file-based catalog", migrate.CatalogRef) + if err := migrate.Run(cmd.Context()); err != nil { + logrus.New().Fatal(err) + } + logrus.Infof("wrote rendered file-based catalog to %q\n", migrate.OutputDir) + return nil + }, + } + cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)") + return cmd +} diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/add.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/add.go index 0e7a399ef0..ceb3fcd1ad 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/add.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/add.go @@ -21,7 +21,7 @@ func newRegistryAddCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -29,6 +29,7 @@ func newRegistryAddCmd() *cobra.Command { }, RunE: addFunc, + Args: cobra.NoArgs, } rootCmd.Flags().Bool("debug", false, "enable debug logging") @@ -39,11 +40,18 @@ func newRegistryAddCmd() *cobra.Command { rootCmd.Flags().String("ca-file", "", "the root certificates to use when --container-tool=none; see docker/podman docs for certificate loading instructions") rootCmd.Flags().StringP("mode", "", "replaces", "graph update mode that defines how channel graphs are updated. One of: [replaces, semver, semver-skippatch]") rootCmd.Flags().StringP("container-tool", "c", "none", "tool to interact with container images (save, build, etc.). One of: [none, docker, podman]") - + rootCmd.Flags().Bool("overwrite-latest", false, "overwrite the latest bundles (channel heads) with those of the same csv name given by --bundles") + if err := rootCmd.Flags().MarkHidden("overwrite-latest"); err != nil { + logrus.Panic(err.Error()) + } + rootCmd.Flags().Bool("enable-alpha", false, "enable unsupported alpha features of the OPM CLI") + if err := rootCmd.Flags().MarkHidden("enable-alpha"); err != nil { + logrus.Panic(err.Error()) + } return rootCmd } -func addFunc(cmd *cobra.Command, args []string) error { +func addFunc(cmd *cobra.Command, _ []string) error { permissive, err := cmd.Flags().GetBool("permissive") if err != nil { return err @@ -77,6 +85,15 @@ func addFunc(cmd *cobra.Command, args []string) error { if err != nil { return err } + overwrite, err := cmd.Flags().GetBool("overwrite-latest") + if err != nil { + return err + } + + enableAlpha, err := cmd.Flags().GetBool("enable-alpha") + if err != nil { + return err + } if caFile != "" { if skipTLS { @@ -96,7 +113,8 @@ func addFunc(cmd *cobra.Command, args []string) error { Bundles: bundleImages, Mode: modeEnum, ContainerTool: containerTool, - Overwrite: false, + Overwrite: overwrite, + EnableAlpha: enableAlpha, } logger := logrus.WithFields(logrus.Fields{"bundles": bundleImages}) diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/cmd.go index 15019f9c09..f4b058bc8f 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/cmd.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/cmd.go @@ -18,12 +18,13 @@ func NewOpmRegistryCmd() *cobra.Command { PersistentPreRun: func(_ *cobra.Command, _ []string) { sqlite.LogSqliteDeprecation() }, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } return nil }, + Args: cobra.NoArgs, } rootCmd.AddCommand(newRegistryServeCmd()) @@ -31,6 +32,7 @@ func NewOpmRegistryCmd() *cobra.Command { rootCmd.AddCommand(newRegistryRmCmd()) rootCmd.AddCommand(newRegistryPruneCmd()) rootCmd.AddCommand(newRegistryPruneStrandedCmd()) + rootCmd.AddCommand(newRegistryDeprecateCmd()) return rootCmd } diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/deprecatetruncate.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/deprecatetruncate.go new file mode 100644 index 0000000000..65eb66278c --- /dev/null +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/deprecatetruncate.go @@ -0,0 +1,76 @@ +package registry + +import ( + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/operator-framework/operator-registry/pkg/lib/registry" + "github.com/operator-framework/operator-registry/pkg/sqlite" +) + +func newRegistryDeprecateCmd() *cobra.Command { + cmd := &cobra.Command{ + Hidden: true, + Use: "deprecatetruncate", + Short: "deprecate operator bundle from registry DB", + Long: `deprecate operator bundle from registry DB + +` + sqlite.DeprecationMessage, + + PreRunE: func(cmd *cobra.Command, _ []string) error { + if debug, _ := cmd.Flags().GetBool("debug"); debug { + logrus.SetLevel(logrus.DebugLevel) + } + return nil + }, + + RunE: deprecateFunc, + Args: cobra.NoArgs, + } + + cmd.Flags().Bool("debug", false, "enable debug logging") + cmd.Flags().StringP("database", "d", "index.db", "relative path to database file") + cmd.Flags().StringSliceP("bundle-images", "b", []string{}, "comma separated list of links to bundle image") + cmd.Flags().Bool("permissive", false, "allow registry load errors") + cmd.Flags().Bool("allow-package-removal", false, "removes the entire package if the heads of all channels in the package are deprecated") + + return cmd +} + +func deprecateFunc(cmd *cobra.Command, _ []string) error { + permissive, err := cmd.Flags().GetBool("permissive") + if err != nil { + return err + } + fromFilename, err := cmd.Flags().GetString("database") + if err != nil { + return err + } + bundleImages, err := cmd.Flags().GetStringSlice("bundle-images") + if err != nil { + return err + } + allowPackageRemoval, err := cmd.Flags().GetBool("allow-package-removal") + if err != nil { + return err + } + + request := registry.DeprecateFromRegistryRequest{ + Permissive: permissive, + InputDatabase: fromFilename, + Bundles: bundleImages, + AllowPackageRemoval: allowPackageRemoval, + } + + logger := logrus.WithFields(logrus.Fields{"bundles": bundleImages}) + + logger.Info("deprecating from registry") + + registryDeprecator := registry.NewRegistryDeprecator(logger) + + err = registryDeprecator.DeprecateFromRegistry(request) + if err != nil { + logger.Fatal(err) + } + return nil +} diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/mirror.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/mirror.go index cda5d030e4..5beae77925 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/mirror.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/mirror.go @@ -40,6 +40,7 @@ func MirrorCmd() *cobra.Command { } return nil }, + Args: cobra.ExactArgs(2), } flags := cmd.Flags() diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/prune.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/prune.go index 05bbdd712c..0160e6bd20 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/prune.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/prune.go @@ -16,7 +16,7 @@ func newRegistryPruneCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -24,6 +24,7 @@ func newRegistryPruneCmd() *cobra.Command { }, RunE: runRegistryPruneCmdFunc, + Args: cobra.NoArgs, } rootCmd.Flags().Bool("debug", false, "enable debug logging") @@ -37,7 +38,7 @@ func newRegistryPruneCmd() *cobra.Command { return rootCmd } -func runRegistryPruneCmdFunc(cmd *cobra.Command, args []string) error { +func runRegistryPruneCmdFunc(cmd *cobra.Command, _ []string) error { fromFilename, err := cmd.Flags().GetString("database") if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/prunestranded.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/prunestranded.go index a3ff44a0f3..800272a2f2 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/prunestranded.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/prunestranded.go @@ -16,7 +16,7 @@ func newRegistryPruneStrandedCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -24,6 +24,7 @@ func newRegistryPruneStrandedCmd() *cobra.Command { }, RunE: runRegistryPruneStrandedCmdFunc, + Args: cobra.NoArgs, } rootCmd.Flags().Bool("debug", false, "enable debug logging") @@ -32,7 +33,7 @@ func newRegistryPruneStrandedCmd() *cobra.Command { return rootCmd } -func runRegistryPruneStrandedCmdFunc(cmd *cobra.Command, args []string) error { +func runRegistryPruneStrandedCmdFunc(cmd *cobra.Command, _ []string) error { fromFilename, err := cmd.Flags().GetString("database") if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/rm.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/rm.go index 17a4a51672..ba884ca8d9 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/rm.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/rm.go @@ -16,7 +16,7 @@ func newRegistryRmCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -24,6 +24,7 @@ func newRegistryRmCmd() *cobra.Command { }, RunE: rmFunc, + Args: cobra.NoArgs, } rootCmd.Flags().Bool("debug", false, "enable debug logging") @@ -37,7 +38,7 @@ func newRegistryRmCmd() *cobra.Command { return rootCmd } -func rmFunc(cmd *cobra.Command, args []string) error { +func rmFunc(cmd *cobra.Command, _ []string) error { fromFilename, err := cmd.Flags().GetString("database") if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/serve.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/serve.go index 794553fa4d..b6defb75ee 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/serve.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/registry/serve.go @@ -32,7 +32,7 @@ func newRegistryServeCmd() *cobra.Command { ` + sqlite.DeprecationMessage, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } @@ -40,6 +40,7 @@ func newRegistryServeCmd() *cobra.Command { }, RunE: serveFunc, + Args: cobra.NoArgs, } rootCmd.Flags().Bool("debug", false, "enable debug logging") @@ -52,7 +53,7 @@ func newRegistryServeCmd() *cobra.Command { return rootCmd } -func serveFunc(cmd *cobra.Command, args []string) error { +func serveFunc(cmd *cobra.Command, _ []string) error { // Immediately set up termination log terminationLogPath, err := cmd.Flags().GetString("termination-log") if err != nil { diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/root/cmd.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/root/cmd.go index 7b07ccbc75..1b00fecc01 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/root/cmd.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/root/cmd.go @@ -7,6 +7,7 @@ import ( "github.com/operator-framework/operator-registry/cmd/opm/alpha" "github.com/operator-framework/operator-registry/cmd/opm/index" initcmd "github.com/operator-framework/operator-registry/cmd/opm/init" + "github.com/operator-framework/operator-registry/cmd/opm/migrate" "github.com/operator-framework/operator-registry/cmd/opm/registry" "github.com/operator-framework/operator-registry/cmd/opm/render" "github.com/operator-framework/operator-registry/cmd/opm/serve" @@ -19,15 +20,16 @@ func NewCmd() *cobra.Command { Use: "opm", Short: "operator package manager", Long: "CLI to interact with operator-registry and build indexes of operator content", - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) } return nil }, + Args: cobra.NoArgs, } - cmd.AddCommand(registry.NewOpmRegistryCmd(), alpha.NewCmd(), initcmd.NewCmd(), serve.NewCmd(), render.NewCmd(), validate.NewCmd()) + cmd.AddCommand(registry.NewOpmRegistryCmd(), alpha.NewCmd(), initcmd.NewCmd(), migrate.NewCmd(), serve.NewCmd(), render.NewCmd(), validate.NewCmd()) index.AddCommand(cmd) version.AddCommand(cmd) diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/serve/serve.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/serve/serve.go index 0549262221..6a075c07ef 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/serve/serve.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/serve/serve.go @@ -87,7 +87,11 @@ func (s *serve) run(ctx context.Context) error { if err != nil { return fmt.Errorf("could not build index model from declarative config: %v", err) } - store := registry.NewQuerier(m) + store, err := registry.NewQuerier(m) + defer store.Close() + if err != nil { + return err + } lis, err := net.Listen("tcp", ":"+s.port) if err != nil { diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go b/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go index 20d7e5402e..94b5fd01df 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go @@ -16,7 +16,7 @@ const ( PodDisruptionBudgetKind = "PodDisruptionBudget" PriorityClassKind = "PriorityClass" VerticalPodAutoscalerKind = "VerticalPodAutoscaler" - ConsoleYamlSampleKind = "ConsoleYamlSample" + ConsoleYAMLSampleKind = "ConsoleYAMLSample" ConsoleQuickStartKind = "ConsoleQuickStart" ConsoleCLIDownloadKind = "ConsoleCLIDownload" ConsoleLinkKind = "ConsoleLink" @@ -43,7 +43,7 @@ var supportedResources = map[string]Namespaced{ PodDisruptionBudgetKind: true, PriorityClassKind: false, VerticalPodAutoscalerKind: false, - ConsoleYamlSampleKind: false, + ConsoleYAMLSampleKind: false, ConsoleQuickStartKind: false, ConsoleCLIDownloadKind: false, ConsoleLinkKind: false, diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/lib/registry/registry.go b/vendor/github.com/operator-framework/operator-registry/pkg/lib/registry/registry.go index e8ec05cce2..9ed73940e1 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/lib/registry/registry.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/lib/registry/registry.go @@ -128,6 +128,8 @@ func unpackImage(ctx context.Context, reg image.Registry, ref image.Reference) ( func populate(ctx context.Context, loader registry.Load, graphLoader registry.GraphLoader, querier registry.Query, reg image.Registry, refs []image.Reference, mode registry.Mode, overwrite bool) error { unpackedImageMap := make(map[image.Reference]string, 0) + overwrittenBundles := map[string][]string{} + var imagesToAdd []*registry.Bundle for _, ref := range refs { to, from, cleanup, err := unpackImage(ctx, reg, ref) if err != nil { @@ -135,16 +137,14 @@ func populate(ctx context.Context, loader registry.Load, graphLoader registry.Gr } unpackedImageMap[to] = from defer cleanup() - } - overwriteImageMap := make(map[string]map[image.Reference]string, 0) - if overwrite { - // find all bundles that are attempting to overwrite - for to, from := range unpackedImageMap { - img, err := registry.NewImageInput(to, from) - if err != nil { - return err - } + img, err := registry.NewImageInput(to, from) + if err != nil { + return err + } + imagesToAdd = append(imagesToAdd, img.Bundle) + + if overwrite { overwritten, err := querier.GetBundlePathIfExists(ctx, img.Bundle.Name) if err != nil { if err == registry.ErrBundleImageNotInDatabase { @@ -155,57 +155,18 @@ func populate(ctx context.Context, loader registry.Load, graphLoader registry.Gr if overwritten == "" { return fmt.Errorf("index add --overwrite-latest is only supported when using bundle images") } - // get all bundle paths for that package - we will re-add these to regenerate the graph - bundles, err := querier.GetBundlesForPackage(ctx, img.Bundle.Package) - if err != nil { - return err - } - type unpackedImage struct { - to image.Reference - from string - cleanup func() - err error - } - unpacked := make(chan unpackedImage) - for bundle := range bundles { - // parallelize image pulls - go func(bundle registry.BundleKey, img *registry.ImageInput) { - if bundle.CsvName != img.Bundle.Name { - to, from, cleanup, err := unpackImage(ctx, reg, image.SimpleReference(bundle.BundlePath)) - unpacked <- unpackedImage{to: to, from: from, cleanup: cleanup, err: err} - } else { - unpacked <- unpackedImage{to: to, from: from, cleanup: func() { return }, err: nil} - } - }(bundle, img) - } - if _, ok := overwriteImageMap[img.Bundle.Package]; !ok { - overwriteImageMap[img.Bundle.Package] = make(map[image.Reference]string, 0) - } - for i := 0; i < len(bundles); i++ { - unpack := <-unpacked - if unpack.err != nil { - return unpack.err - } - overwriteImageMap[img.Bundle.Package][unpack.to] = unpack.from - if _, ok := unpackedImageMap[unpack.to]; ok { - delete(unpackedImageMap, unpack.to) - } - defer unpack.cleanup() - } + overwrittenBundles[img.Bundle.Package] = append(overwrittenBundles[img.Bundle.Package], img.Bundle.Name) } } - populator := registry.NewDirectoryPopulator(loader, graphLoader, querier, unpackedImageMap, overwriteImageMap, overwrite) + populator := registry.NewDirectoryPopulator(loader, graphLoader, querier, unpackedImageMap, overwrittenBundles) + if err := populator.Populate(mode); err != nil { + return err - } - for _, imgMap := range overwriteImageMap { - for to, from := range imgMap { - unpackedImageMap[to] = from - } } - return checkForBundles(ctx, querier.(*sqlite.SQLQuerier), graphLoader, unpackedImageMap) + return checkForBundles(ctx, querier.(*sqlite.SQLQuerier), graphLoader, imagesToAdd) } type DeleteFromRegistryRequest struct { @@ -248,6 +209,9 @@ func (r RegistryUpdater) DeleteFromRegistry(request DeleteFromRegistryRequest) e return fmt.Errorf("error removing stranded packages from database: %s", err) } + if _, err := db.Exec("VACUUM"); err != nil { + return err + } return nil } @@ -275,6 +239,9 @@ func (r RegistryUpdater) PruneStrandedFromRegistry(request PruneStrandedFromRegi return fmt.Errorf("error removing stranded packages from database: %s", err) } + if _, err := db.Exec("VACUUM"); err != nil { + return err + } return nil } @@ -291,7 +258,7 @@ func (r RegistryUpdater) PruneFromRegistry(request PruneFromRegistryRequest) err } defer db.Close() - dbLoader, err := sqlite.NewSQLLiteLoader(db) + dbLoader, err := sqlite.NewDeprecationAwareLoader(db) if err != nil { return err } @@ -327,6 +294,9 @@ func (r RegistryUpdater) PruneFromRegistry(request PruneFromRegistryRequest) err } } + if _, err := db.Exec("VACUUM"); err != nil { + return err + } return nil } @@ -344,7 +314,7 @@ func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequ } defer db.Close() - dbLoader, err := sqlite.NewSQLLiteLoader(db) + dbLoader, err := sqlite.NewDeprecationAwareLoader(db) if err != nil { return err } @@ -393,6 +363,9 @@ func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequ r.Logger.WithError(err).Warn("permissive mode enabled") } + if _, err := db.Exec("VACUUM"); err != nil { + return err + } return nil } @@ -430,96 +403,40 @@ func checkForBundlePaths(querier registry.GRPCQuery, bundlePaths []string) ([]st return found, missing, nil } -// packagesFromUnpackedRefs creates packages from a set of unpacked ref dirs without their upgrade edges. -func packagesFromUnpackedRefs(bundles map[image.Reference]string) (map[string]registry.Package, error) { - graph := map[string]registry.Package{} - for to, from := range bundles { - b, err := registry.NewImageInput(to, from) - if err != nil { - return nil, fmt.Errorf("failed to parse unpacked bundle image %s: %v", to, err) - } - v, err := b.Bundle.Version() - if err != nil { - return nil, fmt.Errorf("failed to parse version for %s (%s): %v", b.Bundle.Name, b.Bundle.BundleImage, err) - } - key := registry.BundleKey{ - CsvName: b.Bundle.Name, - Version: v, - BundlePath: b.Bundle.BundleImage, - } - if _, ok := graph[b.Bundle.Package]; !ok { - graph[b.Bundle.Package] = registry.Package{ - Name: b.Bundle.Package, - Channels: map[string]registry.Channel{}, - } - } - for _, c := range b.Bundle.Channels { - if _, ok := graph[b.Bundle.Package].Channels[c]; !ok { - graph[b.Bundle.Package].Channels[c] = registry.Channel{ - Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{}, - } - } - graph[b.Bundle.Package].Channels[c].Nodes[key] = nil - } - } - - return graph, nil -} - // replaces mode selects highest version as channel head and // prunes any bundles in the upgrade chain after the channel head. -// check for the presence of all bundles after a replaces-mode add. -func checkForBundles(ctx context.Context, q *sqlite.SQLQuerier, g registry.GraphLoader, bundles map[image.Reference]string) error { - if len(bundles) == 0 { - return nil - } - - required, err := packagesFromUnpackedRefs(bundles) - if err != nil { - return err - } - +// check for the presence of newly added bundles after a replaces-mode add. +func checkForBundles(ctx context.Context, q *sqlite.SQLQuerier, g registry.GraphLoader, required []*registry.Bundle) error { var errs []error - for _, pkg := range required { - graph, err := g.Generate(pkg.Name) + for _, bundle := range required { + graph, err := g.Generate(bundle.Package) if err != nil { - errs = append(errs, fmt.Errorf("unable to verify added bundles for package %s: %v", pkg.Name, err)) + errs = append(errs, fmt.Errorf("unable to verify added bundles for package %s: %v", bundle.Package, err)) continue } - for channel, missing := range pkg.Channels { - // trace replaces chain for reachable bundles + for _, channel := range bundle.Channels { + var foundImage bool for next := []registry.BundleKey{graph.Channels[channel].Head}; len(next) > 0; next = next[1:] { - delete(missing.Nodes, next[0]) + if next[0].BundlePath == bundle.BundleImage { + foundImage = true + break + } for edge := range graph.Channels[channel].Nodes[next[0]] { next = append(next, edge) } } - for bundle := range missing.Nodes { - // check if bundle is deprecated. Bundles readded after deprecation should not be present in index and can be ignored. - deprecated, err := isDeprecated(ctx, q, bundle) - if err != nil { - errs = append(errs, fmt.Errorf("could not validate pruned bundle %s (%s) as deprecated: %v", bundle.CsvName, bundle.BundlePath, err)) - } - if !deprecated { - errs = append(errs, fmt.Errorf("added bundle %s pruned from package %s, channel %s: this may be due to incorrect channel head (%s)", bundle.BundlePath, pkg.Name, channel, graph.Channels[channel].Head.CsvName)) - } + if foundImage { + continue } - } - } - return utilerrors.NewAggregate(errs) -} -func isDeprecated(ctx context.Context, q *sqlite.SQLQuerier, bundle registry.BundleKey) (bool, error) { - props, err := q.GetPropertiesForBundle(ctx, bundle.CsvName, bundle.Version, bundle.BundlePath) - if err != nil { - return false, err - } - for _, prop := range props { - if prop.Type == registry.DeprecatedType { - return true, nil + var headSkips []string + for b := range graph.Channels[channel].Nodes[graph.Channels[channel].Head] { + headSkips = append(headSkips, b.CsvName) + } + errs = append(errs, fmt.Errorf("add prunes bundle %s (%s) from package %s, channel %s: this may be due to incorrect channel head (%s, skips/replaces %v)", bundle.Name, bundle.BundleImage, bundle.Package, channel, graph.Channels[channel].Head.CsvName, headSkips)) } } - return false, nil + return utilerrors.NewAggregate(errs) } diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/lib/semver/semver.go b/vendor/github.com/operator-framework/operator-registry/pkg/lib/semver/semver.go index 033bb5419e..6875566d08 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/lib/semver/semver.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/lib/semver/semver.go @@ -3,7 +3,7 @@ package semver import ( "fmt" - "github.com/blang/semver" + "github.com/blang/semver/v4" ) // BuildIdCompare compares two versions and returns negative one if the first arg is less than the second arg, positive one if it is larger, and zero if they are equal. diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/registry/bundlegraphloader.go b/vendor/github.com/operator-framework/operator-registry/pkg/registry/bundlegraphloader.go index 05d8abf064..e8664c4e84 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/registry/bundlegraphloader.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/registry/bundlegraphloader.go @@ -3,7 +3,7 @@ package registry import ( "fmt" - "github.com/blang/semver" + "github.com/blang/semver/v4" ) // BundleGraphLoader generates updated graphs by adding bundles to them, updating diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/registry/csv.go b/vendor/github.com/operator-framework/operator-registry/pkg/registry/csv.go index a3b5ebc191..ec6f3e3239 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/registry/csv.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/registry/csv.go @@ -50,7 +50,7 @@ const ( description = "description" // The yaml attribute that specifies the version of the ClusterServiceVersion - // expected to be semver and parseable by blang/semver + // expected to be semver and parseable by blang/semver/v4 version = "version" // The yaml attribute that specifies the related images of the ClusterServiceVersion diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/registry/decode.go b/vendor/github.com/operator-framework/operator-registry/pkg/registry/decode.go index 392d5dd887..0a9587d092 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/registry/decode.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/registry/decode.go @@ -6,6 +6,7 @@ import ( "io" "io/fs" + "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/yaml" ) @@ -44,7 +45,7 @@ func DecodePackageManifest(reader io.Reader) (manifest *PackageManifest, err err return } -func decodeFileFS(root fs.FS, path string, into interface{}) error { +func decodeFileFS(root fs.FS, path string, into interface{}, log *logrus.Entry) error { fileReader, err := root.Open(path) if err != nil { return fmt.Errorf("unable to read file %s: %s", path, err) @@ -53,5 +54,16 @@ func decodeFileFS(root fs.FS, path string, into interface{}) error { decoder := yaml.NewYAMLOrJSONDecoder(fileReader, 30) - return decoder.Decode(into) + errRet := decoder.Decode(into) + + if errRet == nil { + // Look for and warn about extra documents + extraDocument := &map[string]interface{}{} + err = decoder.Decode(extraDocument) + if err == nil && log != nil { + log.Warnf("found more than one document inside %s, using only the first one", path) + } + } + + return errRet } diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/registry/directoryGraphLoader.go b/vendor/github.com/operator-framework/operator-registry/pkg/registry/directoryGraphLoader.go index 03efa4d63f..b639c08b5e 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/registry/directoryGraphLoader.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/registry/directoryGraphLoader.go @@ -8,7 +8,7 @@ import ( "path/filepath" "sort" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/onsi/gomega/gstruct/errors" ) diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/registry/interface.go b/vendor/github.com/operator-framework/operator-registry/pkg/registry/interface.go index f6a5f843b7..93df40c61a 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/registry/interface.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/registry/interface.go @@ -104,3 +104,7 @@ type GraphLoader interface { type RegistryPopulator interface { Populate() error } + +type HeadOverwriter interface { + RemoveOverwrittenChannelHead(pkg, bundle string) error +} diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/registry/parse.go b/vendor/github.com/operator-framework/operator-registry/pkg/registry/parse.go index 984517cc04..4b13ef7673 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/registry/parse.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/registry/parse.go @@ -6,10 +6,9 @@ import ( "io/fs" "strings" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" ) type bundleParser struct { @@ -74,7 +73,7 @@ func (b *bundleParser) addManifests(manifests fs.FS, bundle *Bundle) error { } obj := &unstructured.Unstructured{} - if err = decodeFileFS(manifests, name, obj); err != nil { + if err = decodeFileFS(manifests, name, obj, b.log); err != nil { b.log.Warnf("failed to decode: %s", err) continue } @@ -128,7 +127,7 @@ func (b *bundleParser) addMetadata(metadata fs.FS, bundle *Bundle) error { name := f.Name() if af == nil { decoded := AnnotationsFile{} - if err = decodeFileFS(metadata, name, &decoded); err == nil { + if err = decodeFileFS(metadata, name, &decoded, b.log); err == nil { if decoded != (AnnotationsFile{}) { af = &decoded } @@ -136,7 +135,7 @@ func (b *bundleParser) addMetadata(metadata fs.FS, bundle *Bundle) error { } if df == nil { decoded := DependenciesFile{} - if err = decodeFileFS(metadata, name, &decoded); err == nil { + if err = decodeFileFS(metadata, name, &decoded, b.log); err == nil { if len(decoded.Dependencies) > 0 { df = &decoded } @@ -144,7 +143,7 @@ func (b *bundleParser) addMetadata(metadata fs.FS, bundle *Bundle) error { } if pf == nil { decoded := PropertiesFile{} - if err = decodeFileFS(metadata, name, &decoded); err == nil { + if err = decodeFileFS(metadata, name, &decoded, b.log); err == nil { if len(decoded.Properties) > 0 { pf = &decoded } diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/registry/populator.go b/vendor/github.com/operator-framework/operator-registry/pkg/registry/populator.go index ad65e78b2d..4a236da793 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/registry/populator.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/registry/populator.go @@ -5,8 +5,9 @@ import ( "errors" "fmt" "os" + "sort" - "github.com/blang/semver" + "github.com/blang/semver/v4" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/yaml" @@ -20,22 +21,20 @@ type Dependencies struct { // DirectoryPopulator loads an unpacked operator bundle from a directory into the database. type DirectoryPopulator struct { - loader Load - graphLoader GraphLoader - querier Query - imageDirMap map[image.Reference]string - overwriteDirMap map[string]map[image.Reference]string - overwrite bool + loader Load + graphLoader GraphLoader + querier Query + imageDirMap map[image.Reference]string + overwrittenImages map[string][]string } -func NewDirectoryPopulator(loader Load, graphLoader GraphLoader, querier Query, imageDirMap map[image.Reference]string, overwriteDirMap map[string]map[image.Reference]string, overwrite bool) *DirectoryPopulator { +func NewDirectoryPopulator(loader Load, graphLoader GraphLoader, querier Query, imageDirMap map[image.Reference]string, overwrittenImages map[string][]string) *DirectoryPopulator { return &DirectoryPopulator{ - loader: loader, - graphLoader: graphLoader, - querier: querier, - imageDirMap: imageDirMap, - overwriteDirMap: overwriteDirMap, - overwrite: overwrite, + loader: loader, + graphLoader: graphLoader, + querier: querier, + imageDirMap: imageDirMap, + overwrittenImages: overwrittenImages, } } @@ -52,24 +51,11 @@ func (i *DirectoryPopulator) Populate(mode Mode) error { imagesToAdd = append(imagesToAdd, imageInput) } - imagesToReAdd := make([]*ImageInput, 0) - for pkg := range i.overwriteDirMap { - for to, from := range i.overwriteDirMap[pkg] { - imageInput, err := NewImageInput(to, from) - if err != nil { - errs = append(errs, err) - continue - } - - imagesToReAdd = append(imagesToReAdd, imageInput) - } - } - if len(errs) > 0 { return utilerrors.NewAggregate(errs) } - err := i.loadManifests(imagesToAdd, imagesToReAdd, mode) + err := i.loadManifests(imagesToAdd, mode) if err != nil { return err } @@ -78,6 +64,7 @@ func (i *DirectoryPopulator) Populate(mode Mode) error { } func (i *DirectoryPopulator) globalSanityCheck(imagesToAdd []*ImageInput) error { + overwrite := len(i.overwrittenImages) > 0 var errs []error images := make(map[string]struct{}) for _, image := range imagesToAdd { @@ -111,7 +98,7 @@ func (i *DirectoryPopulator) globalSanityCheck(imagesToAdd []*ImageInput) error continue } if bundle != nil { - if !i.overwrite { + if !overwrite { // raise error that this package + channel + csv combo is already in the db errs = append(errs, PackageVersionAlreadyAddedErr{ErrorString: "Bundle already added that provides package and csv"}) break @@ -142,10 +129,13 @@ func (i *DirectoryPopulator) globalSanityCheck(imagesToAdd []*ImageInput) error } } + if err := i.ValidateEdgeBundlePackage(imagesToAdd); err != nil { + errs = append(errs, err) + } return utilerrors.NewAggregate(errs) } -func (i *DirectoryPopulator) loadManifests(imagesToAdd []*ImageInput, imagesToReAdd []*ImageInput, mode Mode) error { +func (i *DirectoryPopulator) loadManifests(imagesToAdd []*ImageInput, mode Mode) error { // global sanity checks before insertion if err := i.globalSanityCheck(imagesToAdd); err != nil { return err @@ -153,17 +143,30 @@ func (i *DirectoryPopulator) loadManifests(imagesToAdd []*ImageInput, imagesToRe switch mode { case ReplacesMode: - for pkg := range i.overwriteDirMap { - // TODO: If this succeeds but the add fails there will be a disconnect between - // the registry and the index. Loading the bundles in a single transactions as - // described above would allow us to do the removable in that same transaction - // and ensure that rollback is possible. - if err := i.loader.RemovePackage(pkg); err != nil { - return err + // TODO: If this succeeds but the add fails there will be a disconnect between + // the registry and the index. Loading the bundles in a single transactions as + // described above would allow us to do the removable in that same transaction + // and ensure that rollback is possible. + + // globalSanityCheck should have verified this to be a head without anything replacing it + // and that we have a single overwrite per package + + if len(i.overwrittenImages) > 0 { + if overwriter, ok := i.loader.(HeadOverwriter); ok { + // Assume loader has some way to handle overwritten heads if HeadOverwriter isn't implemented explicitly + for pkg, imgToDelete := range i.overwrittenImages { + if len(imgToDelete) == 0 { + continue + } + // delete old head bundle and swap it with the previous real bundle in its replaces chain + if err := overwriter.RemoveOverwrittenChannelHead(pkg, imgToDelete[0]); err != nil { + return err + } + } } } - return i.loadManifestsReplaces(append(imagesToAdd, imagesToReAdd...)) + return i.loadManifestsReplaces(imagesToAdd) case SemVerMode: for _, image := range imagesToAdd { if err := i.loadManifestsSemver(image.Bundle, false); err != nil { @@ -408,3 +411,54 @@ func DecodeFile(path string, into interface{}) error { return decoder.Decode(into) } + +// ValidateEdgeBundlePackage ensures that all bundles in the input will only skip or replace bundles in the same package. +func (i *DirectoryPopulator) ValidateEdgeBundlePackage(images []*ImageInput) error { + // track packages for encountered bundles + expectedBundlePackages := map[string]string{} + for _, b := range images { + r, err := b.Bundle.Replaces() + if err != nil { + return fmt.Errorf("failed to validate replaces for bundle %s(%s): %v", b.Bundle.Name, b.Bundle.BundleImage, err) + } + + skipped, err := b.Bundle.Skips() + if err != nil { + return fmt.Errorf("failed to validate skipped entries for bundle %s(%s): %v", b.Bundle.Name, b.Bundle.BundleImage, err) + } + + for _, bndl := range append(skipped, r, b.Bundle.Name) { + if len(bndl) == 0 { + continue + } + + if pkg, ok := expectedBundlePackages[bndl]; ok && pkg != b.Bundle.Package { + pkgs := []string{pkg, b.Bundle.Package} + sort.Strings(pkgs) + return fmt.Errorf("bundle %s must belong to exactly one package, found on: %v", bndl, pkgs) + } + expectedBundlePackages[bndl] = b.Bundle.Package + } + } + if len(expectedBundlePackages) == 0 { + return nil + } + + pkgs, err := i.querier.ListPackages(context.TODO()) + if err != nil { + return fmt.Errorf("unable to verify bundle packages: %v", err) + } + for _, pkg := range pkgs { + entries, err := i.querier.GetChannelEntriesFromPackage(context.TODO(), pkg) + if err != nil { + return fmt.Errorf("unable to verify bundles for package %v", err) + } + for _, b := range entries { + if bundlePkg, ok := expectedBundlePackages[b.BundleName]; ok && bundlePkg != b.PackageName { + return fmt.Errorf("bundle %s belongs to package %s on index, cannot be added as an edge for package %s", b.BundleName, b.PackageName, bundlePkg) + } + } + } + + return nil +} diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/registry/query.go b/vendor/github.com/operator-framework/operator-registry/pkg/registry/query.go index 00b654b99a..4ccf7eba4f 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/registry/query.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/registry/query.go @@ -2,7 +2,10 @@ package registry import ( "context" + "encoding/json" "fmt" + "os" + "path/filepath" "sort" "github.com/operator-framework/operator-registry/alpha/model" @@ -11,6 +14,19 @@ import ( type Querier struct { pkgs model.Model + + tmpDir string + apiBundles map[apiBundleKey]string +} + +func (q Querier) Close() error { + return os.RemoveAll(q.tmpDir) +} + +type apiBundleKey struct { + pkgName string + chName string + name string } type SliceBundleSender []*api.Bundle @@ -23,10 +39,60 @@ func (s *SliceBundleSender) Send(b *api.Bundle) error { var _ GRPCQuery = &Querier{} -func NewQuerier(packages model.Model) *Querier { - return &Querier{ - pkgs: packages, +func NewQuerier(packages model.Model) (*Querier, error) { + q := &Querier{} + + tmpDir, err := os.MkdirTemp("", "opm-registry-querier-") + if err != nil { + return nil, err } + q.tmpDir = tmpDir + + q.apiBundles = map[apiBundleKey]string{} + for _, pkg := range packages { + for _, ch := range pkg.Channels { + for _, b := range ch.Bundles { + apiBundle, err := api.ConvertModelBundleToAPIBundle(*b) + if err != nil { + return q, err + } + jsonBundle, err := json.Marshal(apiBundle) + if err != nil { + return q, err + } + filename := filepath.Join(tmpDir, fmt.Sprintf("%s_%s_%s.json", pkg.Name, ch.Name, b.Name)) + if err := os.WriteFile(filename, jsonBundle, 0666); err != nil { + return q, err + } + q.apiBundles[apiBundleKey{pkg.Name, ch.Name, b.Name}] = filename + packages[pkg.Name].Channels[ch.Name].Bundles[b.Name] = &model.Bundle{ + Package: pkg, + Channel: ch, + Name: b.Name, + Replaces: b.Replaces, + Skips: b.Skips, + } + } + } + } + q.pkgs = packages + return q, nil +} + +func (q Querier) loadAPIBundle(k apiBundleKey) (*api.Bundle, error) { + filename, ok := q.apiBundles[k] + if !ok { + return nil, fmt.Errorf("package %q, channel %q, bundle %q not found", k.pkgName, k.chName, k.name) + } + d, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + var b api.Bundle + if err := json.Unmarshal(d, &b); err != nil { + return nil, err + } + return &b, nil } func (q Querier) ListPackages(_ context.Context) ([]string, error) { @@ -52,7 +118,7 @@ func (q Querier) SendBundles(_ context.Context, s BundleSender) error { for _, pkg := range q.pkgs { for _, ch := range pkg.Channels { for _, b := range ch.Bundles { - apiBundle, err := api.ConvertModelBundleToAPIBundle(*b) + apiBundle, err := q.loadAPIBundle(apiBundleKey{pkg.Name, ch.Name, b.Name}) if err != nil { return fmt.Errorf("convert bundle %q: %v", b.Name, err) } @@ -110,7 +176,7 @@ func (q Querier) GetBundle(_ context.Context, pkgName, channelName, csvName stri if !ok { return nil, fmt.Errorf("package %q, channel %q, bundle %q not found", pkgName, channelName, csvName) } - apiBundle, err := api.ConvertModelBundleToAPIBundle(*b) + apiBundle, err := q.loadAPIBundle(apiBundleKey{pkg.Name, ch.Name, b.Name}) if err != nil { return nil, fmt.Errorf("convert bundle %q: %v", b.Name, err) } @@ -134,7 +200,7 @@ func (q Querier) GetBundleForChannel(_ context.Context, pkgName string, channelN if err != nil { return nil, fmt.Errorf("package %q, channel %q has invalid head: %v", pkgName, channelName, err) } - apiBundle, err := api.ConvertModelBundleToAPIBundle(*head) + apiBundle, err := q.loadAPIBundle(apiBundleKey{pkg.Name, ch.Name, head.Name}) if err != nil { return nil, fmt.Errorf("convert bundle %q: %v", head.Name, err) } @@ -177,7 +243,7 @@ func (q Querier) GetBundleThatReplaces(_ context.Context, name, pkgName, channel // implementation to be non-deterministic as well. for _, b := range ch.Bundles { if bundleReplaces(*b, name) { - apiBundle, err := api.ConvertModelBundleToAPIBundle(*b) + apiBundle, err := q.loadAPIBundle(apiBundleKey{pkg.Name, ch.Name, b.Name}) if err != nil { return nil, fmt.Errorf("convert bundle %q: %v", b.Name, err) } @@ -197,7 +263,7 @@ func (q Querier) GetChannelEntriesThatProvide(_ context.Context, group, version, for _, pkg := range q.pkgs { for _, ch := range pkg.Channels { for _, b := range ch.Bundles { - provides, err := doesModelBundleProvide(*b, group, version, kind) + provides, err := q.doesModelBundleProvide(*b, group, version, kind) if err != nil { return nil, err } @@ -236,7 +302,7 @@ func (q Querier) GetLatestChannelEntriesThatProvide(_ context.Context, group, ve return nil, fmt.Errorf("package %q, channel %q has invalid head: %v", pkg.Name, ch.Name, err) } - provides, err := doesModelBundleProvide(*b, group, version, kind) + provides, err := q.doesModelBundleProvide(*b, group, version, kind) if err != nil { return nil, err } @@ -278,8 +344,8 @@ func (q Querier) GetBundleThatProvides(ctx context.Context, group, version, kind return nil, fmt.Errorf("no entry found that provides group:%q version:%q kind:%q", group, version, kind) } -func doesModelBundleProvide(b model.Bundle, group, version, kind string) (bool, error) { - apiBundle, err := api.ConvertModelBundleToAPIBundle(b) +func (q Querier) doesModelBundleProvide(b model.Bundle, group, version, kind string) (bool, error) { + apiBundle, err := q.loadAPIBundle(apiBundleKey{b.Package.Name, b.Channel.Name, b.Name}) if err != nil { return false, fmt.Errorf("convert bundle %q: %v", b.Name, err) } diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/registry/registry_to_model.go b/vendor/github.com/operator-framework/operator-registry/pkg/registry/registry_to_model.go index 2deba2c15c..b45fdf8828 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/registry/registry_to_model.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/registry/registry_to_model.go @@ -4,90 +4,11 @@ import ( "encoding/json" "fmt" "sort" - "strings" - "github.com/operator-framework/operator-registry/alpha/model" "github.com/operator-framework/operator-registry/alpha/property" ) -func ConvertRegistryBundleToModelBundles(b *Bundle) ([]model.Bundle, error) { - var bundles []model.Bundle - desc, err := b.csv.GetDescription() - if err != nil { - return nil, fmt.Errorf("Could not get description from bundle CSV:%s", err) - } - - i, err := b.csv.GetIcons() - if err != nil { - return nil, fmt.Errorf("Could not get icon from bundle CSV:%s", err) - } - mIcon := &model.Icon{ - MediaType: "", - Data: []byte{}, - } - if len(i) > 0 { - mIcon.MediaType = i[0].MediaType - mIcon.Data = []byte(i[0].Base64data) - } - - pkg := &model.Package{ - Name: b.Annotations.PackageName, - Description: desc, - Icon: mIcon, - Channels: make(map[string]*model.Channel), - } - - mb, err := registryBundleToModelBundle(b) - mb.Package = pkg - if err != nil { - return nil, err - } - - for _, ch := range extractChannels(b.Annotations.Channels) { - newCh := &model.Channel{ - Name: ch, - } - chBundle := mb - chBundle.Channel = newCh - bundles = append(bundles, *chBundle) - } - return bundles, nil -} - -func registryBundleToModelBundle(b *Bundle) (*model.Bundle, error) { - bundleProps, err := PropertiesFromBundle(b) - if err != nil { - return nil, fmt.Errorf("error converting properties for internal model: %v", err) - } - - csv, err := b.ClusterServiceVersion() - if err != nil { - return nil, fmt.Errorf("Could not get CVS for bundle: %s", err) - } - replaces, err := csv.GetReplaces() - if err != nil { - return nil, fmt.Errorf("Could not get Replaces from CSV for bundle: %s", err) - } - skips, err := csv.GetSkips() - if err != nil { - return nil, fmt.Errorf("Could not get Skips from CSV for bundle: %s", err) - } - relatedImages, err := convertToModelRelatedImages(csv) - if err != nil { - return nil, fmt.Errorf("Could not get Related images from bundle: %v", err) - } - - return &model.Bundle{ - Name: csv.Name, - Image: b.BundleImage, - Replaces: replaces, - Skips: skips, - Properties: bundleProps, - RelatedImages: relatedImages, - }, nil -} - -func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { +func ObjectsAndPropertiesFromBundle(b *Bundle) ([]string, []property.Property, error) { providedGVKs := map[property.GVK]struct{}{} requiredGVKs := map[property.GVKRequired]struct{}{} @@ -99,14 +20,14 @@ func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { case property.TypeGVK: var v property.GVK if err := json.Unmarshal(p.Value, &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + return nil, nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} } k := property.GVK{Group: v.Group, Kind: v.Kind, Version: v.Version} providedGVKs[k] = struct{}{} case property.TypePackage: var v property.Package if err := json.Unmarshal(p.Value, &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + return nil, nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} } p := property.MustBuildPackage(v.PackageName, v.Version) packageProvidedProperty = &p @@ -124,14 +45,14 @@ func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { case property.TypeGVK: var v property.GVK if err := json.Unmarshal(p.Value, &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + return nil, nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} } k := property.GVKRequired{Group: v.Group, Kind: v.Kind, Version: v.Version} requiredGVKs[k] = struct{}{} case property.TypePackage: var v property.Package if err := json.Unmarshal(p.Value, &v); err != nil { - return nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} + return nil, nil, property.ParseError{Idx: i, Typ: p.Type, Err: err} } packageRequiredProps = append(packageRequiredProps, property.MustBuildPackageRequired(v.PackageName, v.Version)) } @@ -139,12 +60,12 @@ func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { version, err := b.Version() if err != nil { - return nil, fmt.Errorf("get version: %v", err) + return nil, nil, fmt.Errorf("get version: %v", err) } providedApis, err := b.ProvidedAPIs() if err != nil { - return nil, fmt.Errorf("get provided apis: %v", err) + return nil, nil, fmt.Errorf("get provided apis: %v", err) } for p := range providedApis { @@ -155,7 +76,7 @@ func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { } requiredApis, err := b.RequiredAPIs() if err != nil { - return nil, fmt.Errorf("get required apis: %v", err) + return nil, nil, fmt.Errorf("get required apis: %v", err) } for p := range requiredApis { k := property.GVKRequired{Group: p.Group, Kind: p.Kind, Version: p.Version} @@ -164,67 +85,42 @@ func PropertiesFromBundle(b *Bundle) ([]property.Property, error) { } } - var out []property.Property + var ( + props []property.Property + objects []string + ) + for _, obj := range b.Objects { + objData, err := json.Marshal(obj) + if err != nil { + return nil, nil, fmt.Errorf("marshal object %s/%s (%s) to json: %v", obj.GetName(), obj.GetNamespace(), obj.GroupVersionKind(), err) + } + props = append(props, property.MustBuildBundleObjectData(objData)) + objects = append(objects, string(objData)) + } + if packageProvidedProperty == nil { p := property.MustBuildPackage(b.Package, version) packageProvidedProperty = &p } - out = append(out, *packageProvidedProperty) + props = append(props, *packageProvidedProperty) for p := range providedGVKs { - out = append(out, property.MustBuildGVK(p.Group, p.Version, p.Kind)) + props = append(props, property.MustBuildGVK(p.Group, p.Version, p.Kind)) } for p := range requiredGVKs { - out = append(out, property.MustBuildGVKRequired(p.Group, p.Version, p.Kind)) + props = append(props, property.MustBuildGVKRequired(p.Group, p.Version, p.Kind)) } - out = append(out, packageRequiredProps...) - out = append(out, otherProps...) + props = append(props, packageRequiredProps...) + props = append(props, otherProps...) - sort.Slice(out, func(i, j int) bool { - if out[i].Type != out[j].Type { - return out[i].Type < out[j].Type + sort.Slice(props, func(i, j int) bool { + if props[i].Type != props[j].Type { + return props[i].Type < props[j].Type } - return string(out[i].Value) < string(out[j].Value) + return string(props[i].Value) < string(props[j].Value) }) - return out, nil -} - -func convertToModelRelatedImages(csv *ClusterServiceVersion) ([]model.RelatedImage, error) { - var objmap map[string]*json.RawMessage - if err := json.Unmarshal(csv.Spec, &objmap); err != nil { - return nil, err - } - - rawValue, ok := objmap[relatedImages] - if !ok || rawValue == nil { - return nil, nil - } - - type relatedImage struct { - Name string `json:"name"` - Ref string `json:"image"` - } - var relatedImages []relatedImage - if err := json.Unmarshal(*rawValue, &relatedImages); err != nil { - return nil, err - } - mrelatedImages := []model.RelatedImage{} - for _, img := range relatedImages { - mrelatedImages = append(mrelatedImages, model.RelatedImage{Name: img.Name, Image: img.Ref}) - } - return mrelatedImages, nil -} - -func extractChannels(annotationChannels string) []string { - var channels []string - for _, ch := range strings.Split(annotationChannels, ",") { - c := strings.TrimSpace(ch) - if c != "" { - channels = append(channels, ch) - } - } - return channels + return objects, props, nil } diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/registry/types.go b/vendor/github.com/operator-framework/operator-registry/pkg/registry/types.go index 719127cfba..d7d2585520 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/registry/types.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/registry/types.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/blang/semver" + "github.com/blang/semver/v4" ) var ( diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/load.go b/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/load.go index 65185ee656..47540b1811 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/load.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/load.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "github.com/blang/semver" + "github.com/blang/semver/v4" _ "github.com/mattn/go-sqlite3" utilerrors "k8s.io/apimachinery/pkg/util/errors" @@ -28,6 +28,12 @@ type MigratableLoader interface { var _ MigratableLoader = &sqlLoader{} +// startDepth is the depth that channel heads should be assigned +// in the channel_entry table. This const exists so that all +// add modes (replaces, semver, and semver-skippatch) are +// consistent. +const startDepth = 0 + func newSQLLoader(db *sql.DB, opts ...DbOption) (*sqlLoader, error) { options := defaultDBOptions() for _, o := range opts { @@ -424,7 +430,7 @@ func (s *sqlLoader) AddPackageChannelsFromGraph(graph *registry.Package) error { // update each channel's graph for channelName, channel := range graph.Channels { currentNode := channel.Head - depth := 1 + depth := startDepth var previousNodeID int64 @@ -495,7 +501,12 @@ func (s *sqlLoader) AddPackageChannelsFromGraph(graph *registry.Package) error { // we got to the end of the channel graph if nextNode.IsEmpty() { - if len(channel.Nodes) != depth { + // expectedDepth is: + // + - 1 + // For example, if the number of nodes is 3 and the startDepth is 0, the expected depth is 2 (0, 1, 2) + // If the number of nodes is 5 and the startDepth is 3, the expected depth is 7 (3, 4, 5, 6, 7) + expectedDepth := len(channel.Nodes) + startDepth - 1 + if expectedDepth != depth { err := fmt.Errorf("Invalid graph: some (non-bottom) nodes defined in the graph were not mentioned as replacements of any node") errs = append(errs, err) } @@ -608,7 +619,7 @@ func (s *sqlLoader) addPackageChannels(tx *sql.Tx, manifest registry.PackageMani } for _, c := range channels { - res, err := addChannelEntry.Exec(c.Name, manifest.PackageName, c.CurrentCSVName, 0) + res, err := addChannelEntry.Exec(c.Name, manifest.PackageName, c.CurrentCSVName, startDepth) if err != nil { errs = append(errs, fmt.Errorf("failed to add channel %q in package %q: %s", c.Name, manifest.PackageName, err.Error())) continue @@ -620,7 +631,10 @@ func (s *sqlLoader) addPackageChannels(tx *sql.Tx, manifest registry.PackageMani } channelEntryCSVName := c.CurrentCSVName - depth := 1 + + // depth is set to `startDepth + 1` here because we already added the channel head + // with depth `startDepth` above. + depth := startDepth + 1 // Since this loop depends on following 'replaces', keep track of where it's been replaceCycle := map[string]bool{channelEntryCSVName: true} @@ -644,6 +658,16 @@ func (s *sqlLoader) addPackageChannels(tx *sql.Tx, manifest registry.PackageMani break } + deprecated, err := s.deprecated(tx, channelEntryCSVName) + if err != nil { + errs = append(errs, err) + break + } + if deprecated { + // The package is truncated below this point, we're done! + break + } + for _, skip := range skips { // add dummy channel entry for the skipped version skippedChannelEntry, err := addChannelEntry.Exec(c.Name, manifest.PackageName, skip, depth) @@ -707,15 +731,6 @@ func (s *sqlLoader) addPackageChannels(tx *sql.Tx, manifest registry.PackageMani errs = append(errs, err) break } - deprecated, err := s.deprecated(tx, channelEntryCSVName) - if err != nil { - errs = append(errs, err) - break - } - if deprecated { - // The package is truncated below this point, we're done! - break - } if _, _, _, err := s.getBundleSkipsReplacesVersion(tx, replaces); err != nil { errs = append(errs, fmt.Errorf("Invalid bundle %s, replaces nonexistent bundle %s", c.CurrentCSVName, replaces)) break @@ -967,6 +982,7 @@ func (s *sqlLoader) RemovePackage(packageName string) error { if _, err := deleteChannel.Exec(packageName); err != nil { return err } + return tx.Commit() }(); err != nil { return err @@ -1279,38 +1295,35 @@ func (s *sqlLoader) addBundleProperties(tx *sql.Tx, bundle *registry.Bundle) err return nil } -func (s *sqlLoader) rmChannelEntry(tx *sql.Tx, csvName string) error { - rows, err := tx.Query(`SELECT entry_id FROM channel_entry WHERE operatorbundle_name=?`, csvName) - if err != nil { - return err - } - - var entryIDs []int64 - for rows.Next() { - var entryID sql.NullInt64 - rows.Scan(&entryID) - entryIDs = append(entryIDs, entryID.Int64) - } - if err := rows.Close(); err != nil { - return err +func (s *sqlLoader) rmSharedChannelEntry(tx *sql.Tx, csvName, unsharedCsv string) error { + if len(unsharedCsv) == 0 { + return nil } - updateChannelEntry, err := tx.Prepare(`UPDATE channel_entry SET replaces=NULL WHERE replaces=?`) - if err != nil { - return err - } - for _, id := range entryIDs { - if _, err := updateChannelEntry.Exec(id); err != nil { - updateChannelEntry.Close() - return err - } - } - err = updateChannelEntry.Close() + // remove any edges that replace bundle to be removed on the channels of the unsharedCsv + _, err := tx.Exec(` + UPDATE channel_entry + SET replaces=NULL + WHERE replaces IN ( + SELECT entry_id FROM channel_entry + WHERE operatorbundle_name = ? + AND channel_name IN ( + SELECT channel_name FROM channel_entry + WHERE operatorbundle_name = ? + ))`, csvName, unsharedCsv) if err != nil { return err } - _, err = tx.Exec(`DELETE FROM channel WHERE head_operatorbundle_name=?`, csvName) + // delete the channel entries on the shared channel list + _, err = tx.Exec(` + DELETE FROM channel_entry + WHERE operatorbundle_name = ? + AND channel_name IN ( + SELECT channel_name + FROM channel_entry + WHERE operatorbundle_name = ? + )`, csvName, unsharedCsv) if err != nil { return err } @@ -1318,82 +1331,124 @@ func (s *sqlLoader) rmChannelEntry(tx *sql.Tx, csvName string) error { return nil } -func getTailFromBundle(tx *sql.Tx, head string) (bundles []string, err error) { - getReplacesSkips := `SELECT replaces, skips FROM operatorbundle WHERE name=?` - isDefaultChannelHead := `SELECT head_operatorbundle_name FROM channel - INNER JOIN package ON channel.name = package.default_channel - WHERE channel.head_operatorbundle_name = ?` +type tailBundle struct { + name string + version string + bundlepath string + channels []string + replaces []string // in addition to the replaces chain, there may also be real skipped entries + replacedBy []string // to handle any chain where a skipped entry may be a part of another channel that should not be truncated +} - visited := map[string]struct{}{} - next := []string{head} +func getTailFromBundle(tx *sql.Tx, head string) (bundles map[string]tailBundle, err error) { + // traverse replaces chain and collect channel list for each bundle. + // This assumes that replaces chain for a bundle is the same across channels. + // only real bundles with entries in the operator_bundle table are returned. + getReplacesChain := ` + WITH RECURSIVE + replaces_entry (operatorbundle_name, replaces, replaced_by, channel_name, package_name) AS ( + SELECT channel_entry.operatorbundle_name, replaces.operatorbundle_name, replaced_by.operatorbundle_name, channel_entry.channel_name, channel_entry.package_name + FROM channel_entry + LEFT OUTER JOIN channel_entry AS replaces + ON replaces.entry_id = channel_entry.replaces + LEFT OUTER JOIN channel_entry AS replaced_by + ON channel_entry.entry_id = replaced_by.replaces + WHERE channel_entry.operatorbundle_name = ? + UNION + SELECT channel_entry.operatorbundle_name, replaces.operatorbundle_name, replaced_by.operatorbundle_name, channel_entry.channel_name, channel_entry.package_name + FROM channel_entry + JOIN replaces_entry + ON replaces_entry.replaces = channel_entry.operatorbundle_name + LEFT OUTER JOIN channel_entry AS replaces + ON channel_entry.replaces = replaces.entry_id + LEFT OUTER JOIN channel_entry AS replaced_by + ON channel_entry.entry_id = replaced_by.replaces + ) + SELECT + replaces_entry.operatorbundle_name, + operatorbundle.version, + operatorbundle.bundlepath, + GROUP_CONCAT(DISTINCT replaces_entry.channel_name), + GROUP_CONCAT(DISTINCT replaces_entry.replaces), + GROUP_CONCAT(DISTINCT replaces_entry.replaced_by) + FROM replaces_entry + LEFT OUTER JOIN operatorbundle + ON operatorbundle.name = replaces_entry.operatorbundle_name + GROUP BY replaces_entry.operatorbundle_name, replaces_entry.package_name + ORDER BY replaces_entry.channel_name, replaces_entry.replaces, replaces_entry.replaced_by` - for len(next) > 0 { - // Pop the next bundle off of the queue - bundle := next[0] - next = next[1:] // Potentially inefficient queue implementation, but this function is only used when deprecate is called + getDefaultChannelHead := ` + SELECT head_operatorbundle_name FROM channel + INNER JOIN package ON channel.name = package.default_channel AND channel.package_name = package.name + INNER JOIN channel_entry on channel.package_name = channel_entry.package_name + WHERE channel_entry.operatorbundle_name = ? + LIMIT 1` - // Check if next is the head of the defaultChannel - // If it is, the defaultChannel would be removed -- this is not allowed because we cannot know which channel to promote as the new default - var err error - if row := tx.QueryRow(isDefaultChannelHead, bundle); row != nil { - err = row.Scan(&sql.NullString{}) - } - if err == nil { - // A nil error indicates that next is the default channel head - return nil, registry.ErrRemovingDefaultChannelDuringDeprecation - } else if err != sql.ErrNoRows { - return nil, err - } - - rows, err := tx.QueryContext(context.TODO(), getReplacesSkips, bundle) - if err != nil { - return nil, err - } + row := tx.QueryRow(getDefaultChannelHead, head) + if row == nil { + return nil, fmt.Errorf("could not find default channel head for %s", head) + } + var defaultChannelHead sql.NullString + err = row.Scan(&defaultChannelHead) + if err != nil { + return nil, fmt.Errorf("error getting default channel head for %s: %v", head, err) + } + if !defaultChannelHead.Valid || len(defaultChannelHead.String) == 0 { + return nil, fmt.Errorf("invalid default channel head '%s' for %s", defaultChannelHead.String, head) + } + rows, err := tx.QueryContext(context.TODO(), getReplacesChain, head) + if err != nil { + return nil, err + } + replacesChain := map[string]tailBundle{} + for rows.Next() { var ( - replaces sql.NullString - skips sql.NullString + bundle sql.NullString + version sql.NullString + bundlepath sql.NullString + channels sql.NullString + replaces sql.NullString + replacedBy sql.NullString ) - if rows.Next() { - if err := rows.Scan(&replaces, &skips); err != nil { - if nerr := rows.Close(); nerr != nil { - return nil, nerr - } - return nil, err + if err := rows.Scan(&bundle, &version, &bundlepath, &channels, &replaces, &replacedBy); err != nil { + if nerr := rows.Close(); nerr != nil { + return nil, nerr } - } - if err := rows.Close(); err != nil { return nil, err } - if skips.Valid && skips.String != "" { - for _, skip := range strings.Split(skips.String, ",") { - if _, ok := visited[skip]; ok { - // We've already visited this bundle's subgraph - continue - } - visited[skip] = struct{}{} - next = append(next, skip) - } + if !bundle.Valid || len(bundle.String) == 0 { + return nil, fmt.Errorf("invalid tail bundle %v for %s", bundle, head) } - if replaces.Valid && replaces.String != "" { - r := replaces.String - if _, ok := visited[r]; ok { - // We've already visited this bundle's subgraph - continue - } - visited[r] = struct{}{} - next = append(next, r) + + if bundle.String == defaultChannelHead.String { + // A nil error indicates that next is the default channel head + return nil, registry.ErrRemovingDefaultChannelDuringDeprecation + } + var channelList, replacesList, replacedList []string + if channels.Valid && len(channels.String) > 0 { + channelList = strings.Split(channels.String, ",") + } + if replaces.Valid && len(replaces.String) > 0 { + replacesList = strings.Split(replaces.String, ",") + } + if replacedBy.Valid && len(replacedBy.String) > 0 { + replacedList = strings.Split(replacedBy.String, ",") } - } - // The tail is exactly the set of bundles we visited while traversing the graph from head - var tail []string - for v := range visited { - tail = append(tail, v) + replacesChain[bundle.String] = tailBundle{ + name: bundle.String, + version: version.String, + bundlepath: bundlepath.String, + channels: channelList, + replaces: replacesList, + replacedBy: replacedList, + } } - - return tail, nil - + if err := rows.Close(); err != nil { + return nil, err + } + return replacesChain, nil } func getBundleNameAndVersionForImage(tx *sql.Tx, path string) (string, string, error) { @@ -1435,17 +1490,73 @@ func (s *sqlLoader) DeprecateBundle(path string) error { return err } - for _, bundle := range tailBundles { - if err := s.rmChannelEntry(tx, bundle); err != nil { + // track bundles that have already been added to removeOrDeprecate + removeOrDeprecate := []string{name} + seen := map[string]bool{name: true} + + headChannelsMap := map[string]struct{}{} + if _, ok := tailBundles[name]; ok { + for _, c := range tailBundles[name].channels { + headChannelsMap[c] = struct{}{} + } + } + + // Traverse replaces chain, removing bundles from all channels the initial deprecated bundle belongs to. + // If a bundle is removed from all its channels, it is truncated. +deprecate: + for ; len(removeOrDeprecate) > 0; removeOrDeprecate = removeOrDeprecate[1:] { + bundle := removeOrDeprecate[0] + if _, ok := tailBundles[bundle]; !ok { + continue + } + for _, b := range tailBundles[bundle].replaces { + if !seen[b] { + removeOrDeprecate = append(removeOrDeprecate, b) + seen[b] = true + } + } + if bundle == name { + // head bundle gets deprecated separately + continue + } + + // remove all channel_entries for bundle with same channel as the deprecated one + if err := s.rmSharedChannelEntry(tx, bundle, name); err != nil { return err } + + //rm channel entries for channels + if len(tailBundles[bundle].channels) > len(headChannelsMap) { + // bundle belongs to some channel that the initial deprecated bundle does not - we can't truncate this + continue + } + for _, c := range tailBundles[bundle].channels { + if _, ok := headChannelsMap[c]; !ok { + // bundle belongs to some channel that the initial deprecated bundle does not - we can't truncate this + continue deprecate + } + } + for _, b := range tailBundles[bundle].replacedBy { + if _, ok := tailBundles[b]; !ok { + // bundle is a replaces edge for some csv that isn't in the deprecated tail, can't be replaced safely. + continue deprecate + } + } + + // Remove bundle if err := s.rmBundle(tx, bundle); err != nil { return err } + + } + // remove links to deprecated/truncated bundles to avoid regenerating these on add/overwrite + _, err = tx.Exec(`UPDATE channel_entry SET replaces=NULL WHERE operatorbundle_name=?`, name) + if err != nil { + return err } - // Remove any channels that start with the deprecated bundle - _, err = tx.Exec(fmt.Sprintf(`DELETE FROM channel WHERE head_operatorbundle_name="%s"`, name)) + // a channel with a deprecated head is still visible on the console unless the channel_entry table has no entries for it + _, err = tx.Exec(`DELETE FROM channel WHERE head_operatorbundle_name=?`, name) if err != nil { return err } @@ -1466,13 +1577,9 @@ func (s *sqlLoader) DeprecateBundle(path string) error { return err } - // Clean up the deprecated table by dropping all truncated bundles - // (see pkg/sqlite/migrations/013_rm_truncated_deprecations.go for more details) - _, err = tx.Exec(`DELETE FROM deprecated WHERE deprecated.operatorbundle_name NOT IN (SELECT DISTINCT deprecated.operatorbundle_name FROM (deprecated INNER JOIN channel_entry ON deprecated.operatorbundle_name = channel_entry.operatorbundle_name))`) - if err != nil { + if err := s.rmStrandedDeprecated(tx); err != nil { return err } - return tx.Commit() } @@ -1489,11 +1596,73 @@ func (s *sqlLoader) RemoveStrandedBundles() error { return err } + if err := s.rmStrandedDeprecated(tx); err != nil { + return err + } return tx.Commit() } func (s *sqlLoader) rmStrandedBundles(tx *sql.Tx) error { - _, err := tx.Exec("DELETE FROM operatorbundle WHERE name NOT IN(select operatorbundle_name from channel_entry)") + // Remove everything without a channel_entry except deprecated channel heads + _, err := tx.Exec("DELETE FROM operatorbundle WHERE name NOT IN(select operatorbundle_name from channel_entry) AND name NOT IN (SELECT operatorbundle_name FROM deprecated)") + return err +} + +func (s *sqlLoader) rmStrandedDeprecated(tx *sql.Tx) error { + // Remove any deprecated channel heads which have no entries in the channel/channel_entry table to avoid being displayed on the console + rows, err := tx.Query("SELECT DISTINCT name FROM package") + if err != nil { + return err + } + defer rows.Close() + knownPackages := map[string]struct{}{} + for rows.Next() { + var pkg sql.NullString + if err := rows.Scan(&pkg); err != nil { + return err + } + if !pkg.Valid || len(pkg.String) == 0 { + return fmt.Errorf("invalid package %v", pkg) + } + knownPackages[pkg.String] = struct{}{} + } + + packagePropertiesQuery := `select distinct operatorbundle_name, value from properties where type = ?` + pRows, err := tx.Query(packagePropertiesQuery, registry.PackageType) + if err != nil { + return err + } + defer pRows.Close() + + for pRows.Next() { + var bundle, value sql.NullString + if err := pRows.Scan(&bundle, &value); err != nil { + return err + } + + if !bundle.Valid || len(bundle.String) == 0 { + return fmt.Errorf("invalid bundle %v", bundle) + } + + if !value.Valid || len(value.String) == 0 { + return fmt.Errorf("invalid package property on %v: %v", bundle, value) + } + + var prop registry.PackageProperty + if err := json.Unmarshal([]byte(value.String), &prop); err != nil { + return err + } + + if _, ok := knownPackages[prop.PackageName]; !ok { + if err := s.rmBundle(tx, bundle.String); err != nil { + return err + } + } + } + + // Clean up the deprecated table by dropping all truncated bundles + // (see pkg/sqlite/migrations/013_rm_truncated_deprecations.go for more details) + _, err = tx.Exec(`DELETE FROM deprecated WHERE deprecated.operatorbundle_name NOT IN (SELECT DISTINCT name FROM operatorbundle)`) return err } @@ -1590,3 +1759,97 @@ func (d *DeprecationAwareLoader) RemovePackage(pkg string) error { return d.sqlLoader.RemovePackage(pkg) } + +// RemoveOverwrittenChannelHead removes a bundle if it is the channel head and has nothing replacing it +func (s sqlLoader) RemoveOverwrittenChannelHead(pkg, bundle string) error { + tx, err := s.db.Begin() + if err != nil { + return err + } + defer func() { + tx.Rollback() + }() + // check if bundle has anything that replaces it + getBundlesThatReplaceHeadQuery := `SELECT DISTINCT operatorbundle.name AS replaces, channel_entry.channel_name + FROM channel_entry + LEFT OUTER JOIN channel_entry replaces + ON replaces.replaces = channel_entry.entry_id + INNER JOIN operatorbundle + ON replaces.operatorbundle_name = operatorbundle.name + WHERE channel_entry.package_name = ? + AND channel_entry.operatorbundle_name = ? + LIMIT 1` + + rows, err := tx.QueryContext(context.TODO(), getBundlesThatReplaceHeadQuery, pkg, bundle) + if err != nil { + return err + } + defer rows.Close() + if rows != nil { + for rows.Next() { + var replaces, channel sql.NullString + if err := rows.Scan(&replaces, &channel); err != nil { + return err + } + // This is not a head bundle for all channels it is a member of. Cannot remove + return fmt.Errorf("cannot overwrite bundle %s from package %s: replaced by %s on channel %s", bundle, pkg, replaces.String, channel.String) + } + } + + getReplacingBundlesQuery := ` + SELECT replaces.name as replaces, channel_entry.channel_name, min(depth) + from channel_entry + LEFT JOIN ( + SELECT entry_id, name FROM channel_entry + INNER JOIN operatorbundle + ON channel_entry.operatorbundle_name = operatorbundle.name + ) AS replaces + ON channel_entry.replaces = replaces.entry_id + WHERE channel_entry.package_name = ? + AND channel_entry.operatorbundle_name = ? + GROUP BY channel_name + ` + + pRows, err := tx.QueryContext(context.TODO(), getReplacingBundlesQuery, pkg, bundle) + if err != nil { + return err + } + defer pRows.Close() + + channelHeadUpdateQuery := `UPDATE channel SET head_operatorbundle_name = ? WHERE package_name = ? AND name = ? AND head_operatorbundle_name = ?` + for pRows.Next() { + var replaces, channel sql.NullString + var depth sql.NullInt64 + if err := pRows.Scan(&replaces, &channel, &depth); err != nil { + return err + } + + if !channel.Valid { + return fmt.Errorf("channel name column corrupt for bundle %s", bundle) + } + if replaces.Valid && len(replaces.String) != 0 { + // replace any valid entries as channel heads to avoid rmBundle from truncating the entire channel + if _, err = tx.Exec(channelHeadUpdateQuery, replaces, pkg, channel, bundle); err != nil { + return err + } + } else { + // NULL default channel before dropping to let packagemanifest detect default channel + if _, err := tx.Exec(`UPDATE channel SET head_operatorbundle_name = NULL WHERE name = ? AND package_name = ? AND name IN (SELECT default_channel FROM package WHERE name = ?)`, channel, pkg, pkg); err != nil { + return err + } + } + } + + if err := s.rmBundle(tx, bundle); err != nil { + return err + } + // remove from deprecated + if _, err = tx.Exec(`DELETE FROM deprecated WHERE deprecated.operatorbundle_name = ?`, bundle); err != nil { + return err + } + err = tx.Commit() + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/migrations/013_rm_truncated_deprecations.go b/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/migrations/013_rm_truncated_deprecations.go index 33ac71b765..75ad315179 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/migrations/013_rm_truncated_deprecations.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/migrations/013_rm_truncated_deprecations.go @@ -16,11 +16,11 @@ var rmTruncatedDeprecationsMigration = &Migration{ Id: RmTruncatedDeprecationsMigrationKey, Up: func(ctx context.Context, tx *sql.Tx) error { - // Delete deprecation history for all bundles that no longer exist in the channel_entries table + // Delete deprecation history for all bundles that no longer exist in the operatorbundle table // These bundles have been truncated by more recent deprecations and would only confuse future operations on an index; // e.g. adding a previously truncated bundle to a package removed via `opm index|registry rm` would lead to that bundle // being deprecated - _, err := tx.ExecContext(ctx, `DELETE FROM deprecated WHERE deprecated.operatorbundle_name NOT IN (SELECT DISTINCT deprecated.operatorbundle_name FROM (deprecated INNER JOIN channel_entry ON deprecated.operatorbundle_name = channel_entry.operatorbundle_name))`) + _, err := tx.ExecContext(ctx, `DELETE FROM deprecated WHERE deprecated.operatorbundle_name NOT IN (SELECT DISTINCT deprecated.operatorbundle_name FROM (deprecated INNER JOIN operatorbundle ON deprecated.operatorbundle_name = operatorbundle.name))`) return err }, diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/query.go b/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/query.go index 432b998112..6d46d544c2 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/query.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/query.go @@ -1342,9 +1342,10 @@ func (s *SQLQuerier) listBundleChannels(ctx context.Context, bundleName string) // PackageFromDefaultChannelHeadBundle returns the package name if the provided bundle is the head of its default channel. func (s *SQLQuerier) PackageFromDefaultChannelHeadBundle(ctx context.Context, bundle string) (string, error) { packageFromDefaultChannelHeadBundle := ` - SELECT package_name FROM package - INNER JOIN channel ON channel.name = package.default_channel - WHERE channel.head_operatorbundle_name = (SELECT name FROM operatorbundle WHERE bundlepath=? LIMIT 1) ` + SELECT package_name FROM package, channel + WHERE channel.package_name == package.name + AND package.default_channel == channel.name + AND channel.head_operatorbundle_name = (SELECT name FROM operatorbundle WHERE bundlepath=? LIMIT 1) ` rows, err := s.db.QueryContext(ctx, packageFromDefaultChannelHeadBundle, bundle) if err != nil { diff --git a/vendor/modules.txt b/vendor/modules.txt index d16816708a..ab4cad621c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -74,7 +74,7 @@ github.com/blang/semver/v4 github.com/cespare/xxhash/v2 # github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 github.com/containerd/cgroups/stats/v1 -# github.com/containerd/containerd v1.4.8 => github.com/ecordell/containerd v1.3.1-0.20200629153125-0ff1a1be2fa5 +# github.com/containerd/containerd v1.4.11 => github.com/ecordell/containerd v1.3.1-0.20200629153125-0ff1a1be2fa5 github.com/containerd/containerd/archive github.com/containerd/containerd/archive/compression github.com/containerd/containerd/containers @@ -601,6 +601,7 @@ github.com/operator-framework/operator-registry/cmd/opm/alpha/generate github.com/operator-framework/operator-registry/cmd/opm/alpha/list github.com/operator-framework/operator-registry/cmd/opm/index github.com/operator-framework/operator-registry/cmd/opm/init +github.com/operator-framework/operator-registry/cmd/opm/migrate github.com/operator-framework/operator-registry/cmd/opm/registry github.com/operator-framework/operator-registry/cmd/opm/render github.com/operator-framework/operator-registry/cmd/opm/root