Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 83
✨ Filter out bundle versions lower than installed#711
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -31,6 +31,19 @@ func InMastermindsSemverRange(semverRange *mmsemver.Constraints) Predicate[catal | ||
| } | ||
| } | ||
| func HigherBundleVersion(currentVersion *bsemver.Version) Predicate[catalogmetadata.Bundle] { | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good start, but the end state needs to be the same as the current behavior of the ClusterExtension, which is one of:
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I'm not sure ClusterExtension handles skips and skipRange for the second case. But it needs to there too. ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @joelanford Coming back to this after a v0 distraction and had a follow-up question here: for the second case, are we looking for bundles that do all three (replaces and skips and skipRange) or any one of the three?
| ||
| return func(bundle *catalogmetadata.Bundle) bool { | ||
| if currentVersion == nil { | ||
| return false | ||
| } | ||
| bundleVersion, err := bundle.Version() | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not for this PR, just a though. I wonder if we should validate the bundle version on creation/ingress rather than on egress (i.e. bundle.Version() shouldn't return an error). So, a bundle object is always validated and correct. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've had this thought too. If it didn't break any assumptions, it would be nice to get something into the ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that makes great sense and also would make it a lot easier to grab. Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll create a discussion upstream - see what ppl think - or would a ticket suffice? | ||
| if err != nil { | ||
| return false | ||
| } | ||
| return bundleVersion.GTE(*currentVersion) | ||
| } | ||
| } | ||
| func InBlangSemverRange(semverRange bsemver.Range) Predicate[catalogmetadata.Bundle] { | ||
| return func(bundle *catalogmetadata.Bundle) bool { | ||
| bundleVersion, err := bundle.Version() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -18,7 +18,6 @@ package controllers | ||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "sort" | ||
| "strings" | ||
| @@ -31,6 +30,7 @@ import ( | ||
| kappctrlv1alpha1 "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" | ||
| corev1 "k8s.io/api/core/v1" | ||
| "k8s.io/apimachinery/pkg/api/equality" | ||
| apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
| apimeta "k8s.io/apimachinery/pkg/api/meta" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
| @@ -55,10 +55,11 @@ import ( | ||
| type ExtensionReconciler struct { | ||
| client.Client | ||
| BundleProvider BundleProvider | ||
| HasKappApis bool | ||
| } | ||
| var errkappAPIUnavailable = errors.New("kapp-controller apis unavailable on cluster") | ||
| var ( | ||
| bundleVersionKey = "olm.operatorframework.io/bundleVersion" | ||
| ) | ||
| //+kubebuilder:rbac:groups=olm.operatorframework.io,resources=extensions,verbs=get;list;watch;create;update;patch;delete | ||
| //+kubebuilder:rbac:groups=olm.operatorframework.io,resources=extensions/status,verbs=update;patch | ||
| @@ -145,17 +146,6 @@ func (r *ExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.Ext | ||
| return ctrl.Result{}, nil | ||
| } | ||
| if !r.HasKappApis { | ||
| ext.Status.InstalledBundle = nil | ||
| setInstalledStatusConditionFailed(&ext.Status.Conditions, errkappAPIUnavailable.Error(), ext.GetGeneration()) | ||
| ext.Status.ResolvedBundle = nil | ||
| setResolvedStatusConditionUnknown(&ext.Status.Conditions, "kapp apis are unavailable", ext.GetGeneration()) | ||
| setDeprecationStatusesUnknown(&ext.Status.Conditions, "kapp apis are unavailable", ext.GetGeneration()) | ||
| return ctrl.Result{}, errkappAPIUnavailable | ||
| } | ||
| // TODO: Improve the resolution logic. | ||
| bundle, err := r.resolve(ctx, *ext) | ||
| if err != nil { | ||
| @@ -189,7 +179,13 @@ func (r *ExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.Ext | ||
| return ctrl.Result{}, nil | ||
| } | ||
| app := r.GenerateExpectedApp(*ext, bundle.Image) | ||
| app, err := r.GenerateExpectedApp(*ext, bundle) | ||
| if err != nil { | ||
| setInstalledStatusConditionUnknown(&ext.Status.Conditions, err.Error(), ext.GetGeneration()) | ||
| setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration()) | ||
| return ctrl.Result{}, err | ||
| } | ||
| if err := r.ensureApp(ctx, app); err != nil { | ||
| // originally Reason: ocv1alpha1.ReasonInstallationFailed | ||
| ext.Status.InstalledBundle = nil | ||
| @@ -402,7 +398,13 @@ func extensionRequestsForCatalog(c client.Reader, logger logr.Logger) handler.Ma | ||
| } | ||
| } | ||
| func (r *ExtensionReconciler) GenerateExpectedApp(o ocv1alpha1.Extension, bundlePath string) *unstructured.Unstructured { | ||
| func (r *ExtensionReconciler) GenerateExpectedApp(o ocv1alpha1.Extension, bundle *catalogmetadata.Bundle) (*unstructured.Unstructured, error) { | ||
| bundleVersion, err := bundle.Version() | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to generate App from Extension %q with bundle %q: %w", o.GetName(), bundle.Name, err) | ||
| } | ||
| bundlePath := bundle.Image | ||
| // We use unstructured here to avoid problems of serializing default values when sending patches to the apiserver. | ||
| // If you use a typed object, any default values from that struct get serialized into the JSON patch, which could | ||
| // cause unrelated fields to be patched back to the default value even though that isn't the intention. Using an | ||
| @@ -436,6 +438,9 @@ func (r *ExtensionReconciler) GenerateExpectedApp(o ocv1alpha1.Extension, bundle | ||
| "metadata": map[string]interface{}{ | ||
| "name": o.GetName(), | ||
| "namespace": o.GetNamespace(), | ||
| "annotations": map[string]string{ | ||
| bundleVersionKey: bundleVersion.String(), | ||
| }, | ||
| }, | ||
| "spec": spec, | ||
| }, | ||
| @@ -451,7 +456,24 @@ func (r *ExtensionReconciler) GenerateExpectedApp(o ocv1alpha1.Extension, bundle | ||
| BlockOwnerDeletion: ptr.To(true), | ||
| }, | ||
| }) | ||
| return app | ||
| return app, nil | ||
| } | ||
| func (r *ExtensionReconciler) getInstalledVersion(ctx context.Context, namespacedName types.NamespacedName) (*bsemver.Version, error) { | ||
| existingApp, err := r.existingAppUnstructured(ctx, namespacedName.Name, namespacedName.Namespace) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| existingVersion, ok := existingApp.GetAnnotations()[bundleVersionKey] | ||
| if !ok { | ||
| return nil, fmt.Errorf("existing App %q in Namespace %q missing bundle version", namespacedName.Name, namespacedName.Namespace) | ||
| } | ||
| existingVersionSemver, err := bsemver.New(existingVersion) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("could not determine bundle version of existing App %q in Namespace %q: %w", namespacedName.Name, namespacedName.Namespace, err) | ||
| } | ||
| return existingVersionSemver, nil | ||
| } | ||
| func (r *ExtensionReconciler) resolve(ctx context.Context, extension ocv1alpha1.Extension) (*catalogmetadata.Bundle, error) { | ||
| @@ -480,19 +502,33 @@ func (r *ExtensionReconciler) resolve(ctx context.Context, extension ocv1alpha1. | ||
| predicates = append(predicates, catalogfilter.InMastermindsSemverRange(vr)) | ||
| } | ||
| var installedVersion string | ||
| // Do not include bundle versions older than currently installed unless UpgradeConstraintPolicy = 'Ignore' | ||
| if extension.Spec.Source.Package.UpgradeConstraintPolicy != ocv1alpha1.UpgradeConstraintPolicyIgnore { | ||
dtfranz marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| installedVersionSemver, err := r.getInstalledVersion(ctx, types.NamespacedName{Name: extension.GetName(), Namespace: extension.GetNamespace()}) | ||
| if err != nil && !apierrors.IsNotFound(err) { | ||
| return nil, err | ||
| } | ||
| if installedVersionSemver != nil { | ||
| installedVersion = installedVersionSemver.String() | ||
| predicates = append(predicates, catalogfilter.HigherBundleVersion(installedVersionSemver)) | ||
| } | ||
| } | ||
| resultSet := catalogfilter.Filter(allBundles, catalogfilter.And(predicates...)) | ||
| if len(resultSet) == 0 { | ||
| if versionRange != "" && channelName != "" { | ||
| return nil, fmt.Errorf("no package %q matching version %q found in channel %q", packageName, versionRange, channelName) | ||
| } | ||
| var versionError, channelError, existingVersionError string | ||
| if versionRange != "" { | ||
| return nil, fmt.Errorf("no package %q matching version %q found", packageName, versionRange) | ||
| versionError = fmt.Sprintf(" matching version %q", versionRange) | ||
| } | ||
| if channelName != "" { | ||
| return nil, fmt.Errorf("no package %q found in channel %q", packageName, channelName) | ||
| channelError = fmt.Sprintf(" in channel %q", channelName) | ||
| } | ||
| if installedVersion != "" { | ||
| existingVersionError = fmt.Sprintf(" which upgrades currently installed version %q", installedVersion) | ||
| } | ||
| return nil, fmt.Errorf("no package %q found", packageName) | ||
| return nil, fmt.Errorf("no package %q%s%s%s found", packageName, versionError, channelError, existingVersionError) | ||
| } | ||
| sort.SliceStable(resultSet, func(i, j int) bool { | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change was taken from #690 , in which Varsha explained that the check is not needed due to the feature gate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, why is it in yours? 😕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That PR is undergoing a refactor to combine the tests into a test loop, which is likely going to take some time given its relative priority. This particular change is valuable though since it made doing these tests much easier.