-
Notifications
You must be signed in to change notification settings - Fork 107
Add update-target-stage gate annotation #1913
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ import ( | |
| "fmt" | ||
| "os" | ||
| "reflect" | ||
| "strings" | ||
|
|
||
| apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
| "k8s.io/apimachinery/pkg/runtime/schema" | ||
|
|
@@ -59,7 +60,6 @@ func (r *OpenStackVersion) Default() { | |
| // ValidateCreate validates the OpenStackVersion on creation | ||
| func (r *OpenStackVersion) ValidateCreate(ctx context.Context, c goClient.Client) (admission.Warnings, error) { | ||
|
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. While we wouldn't be expecting a user to create an |
||
| openstackversionlog.Info("validate create", "name", r.Name) | ||
|
|
||
| if r.Spec.TargetVersion != openstackVersionDefaults.AvailableVersion { | ||
| return nil, apierrors.NewForbidden( | ||
| schema.GroupResource{ | ||
|
|
@@ -114,6 +114,10 @@ func (r *OpenStackVersion) ValidateCreate(ctx context.Context, c goClient.Client | |
| func (r *OpenStackVersion) ValidateUpdate(ctx context.Context, old runtime.Object, c goClient.Client) (admission.Warnings, error) { | ||
| openstackversionlog.Info("validate update", "name", r.Name) | ||
|
|
||
| if err := validateMinorUpdateTargetStageAnnotation(r.Annotations, r.GetName()); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| _, ok := r.Status.ContainerImageVersionDefaults[r.Spec.TargetVersion] | ||
| if r.Spec.TargetVersion != openstackVersionDefaults.AvailableVersion && !ok { | ||
| return nil, apierrors.NewForbidden( | ||
|
|
@@ -135,6 +139,11 @@ func (r *OpenStackVersion) ValidateUpdate(ctx context.Context, old runtime.Objec | |
| return nil, apierrors.NewInternalError(fmt.Errorf("failed to convert old object to OpenStackVersion")) | ||
| } | ||
|
|
||
| // Validate that the target stage annotation is not from earlier stage while a minor update is in progress | ||
| if err := validateMinorUpdateTargetStageAnnotationProgress(oldVersion, r); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // Check if targetVersion is changing and this is a minor update | ||
| if oldVersion.Spec.TargetVersion != r.Spec.TargetVersion && oldVersion.Status.DeployedVersion != nil { | ||
| // Check if the skip annotation is present | ||
|
|
@@ -174,6 +183,124 @@ func (r *OpenStackVersion) ValidateUpdate(ctx context.Context, old runtime.Objec | |
| return nil, nil | ||
| } | ||
|
|
||
| func validateMinorUpdateTargetStageAnnotation(annotations map[string]string, resourceName string) error { | ||
| if annotations == nil { | ||
| return nil | ||
| } | ||
| stage, ok := annotations[MinorUpdateTargetStageAnnotation] | ||
| if !ok { | ||
| return nil | ||
| } | ||
| annotationField := "metadata.annotations[" + MinorUpdateTargetStageAnnotation + "]" | ||
| if stage == "" { | ||
| return apierrors.NewForbidden( | ||
| schema.GroupResource{ | ||
| Group: GroupVersion.WithKind("OpenStackVersion").Group, | ||
| Resource: GroupVersion.WithKind("OpenStackVersion").Kind, | ||
| }, resourceName, &field.Error{ | ||
| Type: field.ErrorTypeForbidden, | ||
| Field: annotationField, | ||
| BadValue: stage, | ||
| Detail: "Annotation value must not be empty. Remove the annotation or set a valid stage name", | ||
| }, | ||
| ) | ||
| } | ||
| if !IsValidMinorUpdateTargetStage(stage) { | ||
| return apierrors.NewForbidden( | ||
| schema.GroupResource{ | ||
| Group: GroupVersion.WithKind("OpenStackVersion").Group, | ||
| Resource: GroupVersion.WithKind("OpenStackVersion").Kind, | ||
| }, resourceName, &field.Error{ | ||
| Type: field.ErrorTypeForbidden, | ||
| Field: annotationField, | ||
| BadValue: stage, | ||
| Detail: fmt.Sprintf( | ||
| "Invalid target stage %q. Must be one of: %s", | ||
| stage, | ||
| strings.Join(ValidMinorUpdateTargetStages(), ", "), | ||
| ), | ||
| }, | ||
| ) | ||
| } | ||
| return nil | ||
| } | ||
|
Comment on lines
+194
to
+226
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. This could perhaps be simplified to just use L208-224, changing the error message to |
||
|
|
||
| func minorUpdateInProgress(v *OpenStackVersion) bool { | ||
| if v.Status.DeployedVersion == nil { | ||
| return false | ||
| } | ||
| return v.Spec.TargetVersion != *v.Status.DeployedVersion | ||
| } | ||
|
|
||
| func minorUpdateTargetStageFromAnnotations(annotations map[string]string) (string, bool) { | ||
|
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. This is duplicate logic found in [1]. We should just use/call [1] instead and get rid of this function. |
||
| if annotations == nil { | ||
| return "", false | ||
| } | ||
| stage, ok := annotations[MinorUpdateTargetStageAnnotation] | ||
| if !ok || !IsValidMinorUpdateTargetStage(stage) { | ||
| return "", false | ||
| } | ||
| return stage, true | ||
| } | ||
|
|
||
| // validateMinorUpdateTargetStageAnnotationProgress rejects moving the target-stage | ||
| // annotation to an earlier rollout stage while a minor update is in progress, and rejects | ||
| // adding the annotation behind stages already completed when it was absent at update start. | ||
| func validateMinorUpdateTargetStageAnnotationProgress(old, new *OpenStackVersion) error { | ||
| if !minorUpdateInProgress(new) { | ||
| return nil | ||
| } | ||
| oldStage, oldOK := minorUpdateTargetStageFromAnnotations(old.Annotations) | ||
| newStage, newOK := minorUpdateTargetStageFromAnnotations(new.Annotations) | ||
| if !newOK { | ||
| return nil | ||
| } | ||
| newIdx, okNew := MinorUpdateTargetStageIndex(newStage) | ||
| if !okNew { | ||
| return nil | ||
| } | ||
| annotationField := "metadata.annotations[" + MinorUpdateTargetStageAnnotation + "]" | ||
| gr := schema.GroupResource{ | ||
| Group: GroupVersion.WithKind("OpenStackVersion").Group, | ||
| Resource: GroupVersion.WithKind("OpenStackVersion").Kind, | ||
| } | ||
|
|
||
| if !oldOK { | ||
| latest := LatestCompletedMinorUpdateTargetStageIndex(old.Status) | ||
| if latest >= 0 && newIdx < latest { | ||
| completedStage := validMinorUpdateTargetStagesOrdered[latest] | ||
| return apierrors.NewForbidden( | ||
| gr, new.GetName(), &field.Error{ | ||
| Type: field.ErrorTypeForbidden, | ||
| Field: annotationField, | ||
| BadValue: newStage, | ||
| Detail: fmt.Sprintf( | ||
| "Cannot set update target stage to %q while minor update is in progress: update has already completed stage %q (targetVersion %q, deployedVersion %q); choose a further stage", | ||
| newStage, completedStage, new.Spec.TargetVersion, *new.Status.DeployedVersion, | ||
| ), | ||
| }, | ||
| ) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| oldIdx, _ := MinorUpdateTargetStageIndex(oldStage) | ||
| if newIdx >= oldIdx { | ||
| return nil | ||
| } | ||
| return apierrors.NewForbidden( | ||
| gr, new.GetName(), &field.Error{ | ||
| Type: field.ErrorTypeForbidden, | ||
| Field: annotationField, | ||
| BadValue: newStage, | ||
| Detail: fmt.Sprintf( | ||
| "Cannot move update target stage from %q to earlier stage %q while minor update is in progress (targetVersion %q, deployedVersion %q); remove the annotation or set a further stage", | ||
| oldStage, newStage, new.Spec.TargetVersion, *new.Status.DeployedVersion, | ||
| ), | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| // hasAnyCustomImage checks if any image field in CustomContainerImages is set | ||
| func hasAnyCustomImage(images CustomContainerImages) bool { | ||
| // Check CinderVolumeImages map | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.