diff --git a/api/v1/clusterobjectset_types.go b/api/v1/clusterobjectset_types.go index c4044012d0..97f0457609 100644 --- a/api/v1/clusterobjectset_types.go +++ b/api/v1/clusterobjectset_types.go @@ -664,19 +664,14 @@ type ObjectStatus struct { // +optional Group string `json:"group,omitempty"` - // version is the API version of the object. Must be between 1 and - // 63 characters. - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:MaxLength=63 - // +required - Version string `json:"version,omitempty"` - - // kind is the kind of the object. Must be between 1 and 63 + // resource is the lowercase, plural resource type of the object + // (for example, "pods" or "configmaps"). Must be between 1 and 63 // characters. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:XValidation:rule=`!format.dns1035Label().validate(self).hasValue()`,message="the value must consist of only lowercase alphanumeric characters and hyphens, must start with a letter, and must end with an alphanumeric character." // +required - Kind string `json:"kind,omitempty"` + Resource string `json:"resource,omitempty"` // namespace is the namespace of the object. Empty for cluster-scoped // resources. At most 253 characters (DNS subdomain max). diff --git a/applyconfigurations/api/v1/objectstatus.go b/applyconfigurations/api/v1/objectstatus.go index f0714db81c..8a148bfe15 100644 --- a/applyconfigurations/api/v1/objectstatus.go +++ b/applyconfigurations/api/v1/objectstatus.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Code generated by controller-gen-v0.21. DO NOT EDIT. +// Code generated by controller-gen. DO NOT EDIT. package v1 @@ -26,12 +26,10 @@ type ObjectStatusApplyConfiguration struct { // group is the API group of the object (empty string for core // resources). At most 253 characters (DNS subdomain max). Group *string `json:"group,omitempty"` - // version is the API version of the object. Must be between 1 and - // 63 characters. - Version *string `json:"version,omitempty"` - // kind is the kind of the object. Must be between 1 and 63 + // resource is the lowercase, plural resource type of the object + // (for example, "pods" or "configmaps"). Must be between 1 and 63 // characters. - Kind *string `json:"kind,omitempty"` + Resource *string `json:"resource,omitempty"` // namespace is the namespace of the object. Empty for cluster-scoped // resources. At most 253 characters (DNS subdomain max). Namespace *string `json:"namespace,omitempty"` @@ -60,19 +58,11 @@ func (b *ObjectStatusApplyConfiguration) WithGroup(value string) *ObjectStatusAp return b } -// WithVersion sets the Version field in the declarative configuration to the given value +// WithResource sets the Resource field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Version field is set to the value of the last call. -func (b *ObjectStatusApplyConfiguration) WithVersion(value string) *ObjectStatusApplyConfiguration { - b.Version = &value - return b -} - -// WithKind sets the Kind field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Kind field is set to the value of the last call. -func (b *ObjectStatusApplyConfiguration) WithKind(value string) *ObjectStatusApplyConfiguration { - b.Kind = &value +// If called multiple times, the Resource field is set to the value of the last call. +func (b *ObjectStatusApplyConfiguration) WithResource(value string) *ObjectStatusApplyConfiguration { + b.Resource = &value return b } diff --git a/applyconfigurations/internal/internal.go b/applyconfigurations/internal/internal.go index 8f564a4eda..c7832e9be1 100644 --- a/applyconfigurations/internal/internal.go +++ b/applyconfigurations/internal/internal.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Code generated by controller-gen-v0.21. DO NOT EDIT. +// Code generated by controller-gen. DO NOT EDIT. package internal @@ -509,9 +509,6 @@ var schemaYAML = typed.YAMLObject(`types: - name: group type: scalar: string - - name: kind - type: - scalar: string - name: messages type: list: @@ -524,7 +521,7 @@ var schemaYAML = typed.YAMLObject(`types: - name: namespace type: scalar: string - - name: version + - name: resource type: scalar: string - name: com.github.operator-framework.operator-controller.api.v1.ObservedPhase diff --git a/helm/olmv1/base/object-controller/crd/experimental/olm.operatorframework.io_clusterobjectsets.yaml b/helm/olmv1/base/object-controller/crd/experimental/olm.operatorframework.io_clusterobjectsets.yaml index 3e7fc64e85..1f7eedf655 100644 --- a/helm/olmv1/base/object-controller/crd/experimental/olm.operatorframework.io_clusterobjectsets.yaml +++ b/helm/olmv1/base/object-controller/crd/experimental/olm.operatorframework.io_clusterobjectsets.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.22.0 + controller-gen.kubebuilder.io/version: v0.23.0 olm.operatorframework.io/generator: experimental name: clusterobjectsets.olm.operatorframework.io spec: @@ -828,13 +828,6 @@ spec: resources). At most 253 characters (DNS subdomain max). maxLength: 253 type: string - kind: - description: |- - kind is the kind of the object. Must be between 1 and 63 - characters. - maxLength: 63 - minLength: 1 - type: string messages: description: |- messages lists the failure reasons for this object. The maximum @@ -861,17 +854,22 @@ spec: resources. At most 253 characters (DNS subdomain max). maxLength: 253 type: string - version: + resource: description: |- - version is the API version of the object. Must be between 1 and - 63 characters. + resource is the lowercase, plural resource type of the object + (for example, "pods" or "configmaps"). Must be between 1 and 63 + characters. maxLength: 63 minLength: 1 type: string + x-kubernetes-validations: + - message: the value must consist of only lowercase alphanumeric + characters and hyphens, must start with a letter, + and must end with an alphanumeric character. + rule: '!format.dns1035Label().validate(self).hasValue()' required: - - kind - name - - version + - resource type: object maxItems: 50 type: array diff --git a/internal/object-controller/controllers/clusterobjectset_controller.go b/internal/object-controller/controllers/clusterobjectset_controller.go index c2612e0947..ef1eb83d8c 100644 --- a/internal/object-controller/controllers/clusterobjectset_controller.go +++ b/internal/object-controller/controllers/clusterobjectset_controller.go @@ -243,7 +243,7 @@ func (c *ClusterObjectSetReconciler) reconcile(ctx context.Context, cos *ocv1.Cl reconcileTime := metav1.NewTime(c.Clock.Now()) // Set object counts and phase status // TODO: refactor and move condition setting operations into status pkg - status.UpdateObservedPhasesForReconcile(cos, rres, reconcileTime) + status.UpdateObservedPhasesForReconcile(c.Client.RESTMapper(), cos, rres, reconcileTime) // Report revision-level preflight validation failures and re-check on a flat timer: // the dry-run can be rejected for reasons that clear without a spec change (e.g. RBAC @@ -415,7 +415,7 @@ func (c *ClusterObjectSetReconciler) archive(ctx context.Context, revisionEngine return ctrl.Result{}, fmt.Errorf("error archiving revision: %v", err) } teardownTime := metav1.NewTime(c.Clock.Now()) - status.UpdateObservedPhasesForTeardown(cos, tdres, teardownTime) + status.UpdateObservedPhasesForTeardown(c.Client.RESTMapper(), cos, tdres, teardownTime) if tdres != nil && !tdres.IsComplete() { markAsArchiving(cos) return ctrl.Result{RequeueAfter: 5 * time.Second}, nil diff --git a/internal/object-controller/status/status.go b/internal/object-controller/status/status.go index 6e886ff191..ae723980fb 100644 --- a/internal/object-controller/status/status.go +++ b/internal/object-controller/status/status.go @@ -43,7 +43,7 @@ func sumObjectCounts(phases []ocv1.ObservedPhase) ocv1.ObjectCounts { return counts } -func UpdateObservedPhasesForReconcile(objSet *ocv1.ClusterObjectSet, rres machinery.RevisionResult, now metav1.Time) { +func UpdateObservedPhasesForReconcile(mapper apimeta.RESTMapper, objSet *ocv1.ClusterObjectSet, rres machinery.RevisionResult, now metav1.Time) { if objSet == nil || rres == nil { return } @@ -165,7 +165,7 @@ func UpdateObservedPhasesForReconcile(objSet *ocv1.ClusterObjectSet, rres machin if obj.IsComplete() || obj.Action() == machinery.ActionProgressed { continue } - observedPhase.ObjectDetails = append(observedPhase.ObjectDetails, objectStatus(obj)) + observedPhase.ObjectDetails = append(observedPhase.ObjectDetails, objectStatus(mapper, obj)) } } updateObservedPhaseStatus(objSet, observedPhase, now) @@ -173,7 +173,7 @@ func UpdateObservedPhasesForReconcile(objSet *ocv1.ClusterObjectSet, rres machin objSet.Status.ObjectCounts = sumObjectCounts(objSet.Status.ObservedPhases) } -func UpdateObservedPhasesForTeardown(objSet *ocv1.ClusterObjectSet, tres machinery.RevisionTeardownResult, now metav1.Time) { +func UpdateObservedPhasesForTeardown(mapper apimeta.RESTMapper, objSet *ocv1.ClusterObjectSet, tres machinery.RevisionTeardownResult, now metav1.Time) { if objSet == nil || tres == nil { return } @@ -207,8 +207,7 @@ func UpdateObservedPhasesForTeardown(objSet *ocv1.ClusterObjectSet, tres machine for _, ref := range pres.Waiting() { observedPhase.ObjectDetails = append(observedPhase.ObjectDetails, ocv1.ObjectStatus{ Group: ref.Group, - Version: ref.Version, - Kind: ref.Kind, + Resource: resourceFor(mapper, ref), Namespace: ref.Namespace, Name: ref.Name, Messages: []string{"awaiting deletion"}, @@ -269,13 +268,32 @@ func HasSupersededObject(pres machinery.PhaseResult) bool { return false } +// resourceFor returns the lowercase, plural resource name for ref's GroupVersionKind, +// as required by ObjectStatus.resource. It resolves the resource via the RESTMapper so +// that, e.g., a ConfigMap is reported as "configmaps" rather than the "ConfigMap" kind +// (which would fail the field's dns1035 label validation and cause the status update to +// be rejected). When the type is not yet known to the mapper (for example a CRD applied +// by an earlier phase whose discovery has not propagated), it falls back to the +// lowercased kind, which is still a dns1035-valid identifier for the type. +func resourceFor(mapper apimeta.RESTMapper, ref types.ObjectRef) string { + if mapper != nil { + var versions []string + if ref.Version != "" { + versions = []string{ref.Version} + } + if mapping, err := mapper.RESTMapping(ref.GroupKind(), versions...); err == nil { + return mapping.Resource.Resource + } + } + return strings.ToLower(ref.Kind) +} + // objectStatus returns details describing why obj is not yet complete. -func objectStatus(obj machinery.ObjectResult) ocv1.ObjectStatus { +func objectStatus(mapper apimeta.RESTMapper, obj machinery.ObjectResult) ocv1.ObjectStatus { ref := types.ToObjectRef(obj.Object()) objStatus := ocv1.ObjectStatus{ Group: ref.Group, - Version: ref.Version, - Kind: ref.Kind, + Resource: resourceFor(mapper, ref), Namespace: ref.Namespace, Name: ref.Name, } diff --git a/internal/object-controller/status/status_test.go b/internal/object-controller/status/status_test.go index 92b94cff1c..419ccdc04c 100644 --- a/internal/object-controller/status/status_test.go +++ b/internal/object-controller/status/status_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" 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" "k8s.io/apimachinery/pkg/runtime/schema" @@ -23,6 +24,17 @@ import ( ocv1 "github.com/operator-framework/operator-controller/api/v1" ) +// testMapper resolves the GroupVersionKinds exercised by these tests to their plural +// resource names, mirroring how the controller's real RESTMapper populates +// ObjectStatus.resource. Types it does not know fall back to the lowercased kind. +var testMapper = func() apimeta.RESTMapper { + m := apimeta.NewDefaultRESTMapper(nil) + m.Add(corev1.SchemeGroupVersion.WithKind("ConfigMap"), apimeta.RESTScopeNamespace) + m.Add(schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, apimeta.RESTScopeNamespace) + m.Add(schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, apimeta.RESTScopeRoot) + return m +}() + func TestUpdateObservedPhasesForReconcile(t *testing.T) { now := metav1.Now() @@ -42,7 +54,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { &fakePhaseResult{name: "p1", complete: true}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) // phases must not be updated when there is a revision-level validation error assert.Equal(t, ocv1.PhaseStatusReconciling, cos.Status.ObservedPhases[0].Status) }) @@ -58,7 +70,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { &fakePhaseResult{name: "p1", complete: true, validationError: ve}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusInvalidSpecification, cos.Status.ObservedPhases[0].Status) assert.Equal(t, int64(1), *cos.Status.ObservedPhases[0].ObjectCounts.Total) }) @@ -89,7 +101,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { &fakePhaseResult{name: "p1", complete: false, validationError: ve}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusPreconditionsNotMet, cos.Status.ObservedPhases[0].Status) }) @@ -117,7 +129,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { &fakePhaseResult{name: "p1", complete: false, validationError: ve}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusInvalidSpecification, cos.Status.ObservedPhases[0].Status) }) @@ -143,7 +155,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { &fakePhaseResult{name: "p2", complete: false, validationError: ve}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusPending, cos.Status.ObservedPhases[1].Status) assert.Equal(t, int64(1), *cos.Status.ObservedPhases[1].ObjectCounts.Total) }) @@ -172,7 +184,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) require.Len(t, cos.Status.ObservedPhases, 2) assert.Equal(t, ocv1.PhaseStatusSuperseded, cos.Status.ObservedPhases[0].Status) assert.Equal(t, int64(2), *cos.Status.ObservedPhases[0].ObjectCounts.Total) @@ -192,7 +204,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { &fakePhaseResult{name: "p1", complete: true, progressed: true}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusReady, cos.Status.ObservedPhases[0].Status) }) @@ -212,7 +224,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusSuperseded, cos.Status.ObservedPhases[0].Status) }) @@ -230,7 +242,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) require.NotNil(t, cos.Status.ObservedPhases) assert.Equal(t, ocv1.ObjectCounts{Total: ptr.To(int64(1)), Present: ptr.To(int64(0)), Synced: ptr.To(int64(0)), Ready: ptr.To(int64(0))}, cos.Status.ObservedPhases[0].ObjectCounts) assert.Equal(t, ocv1.PhaseStatusReconciling, cos.Status.ObservedPhases[0].Status) @@ -249,7 +261,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusReady, cos.Status.ObservedPhases[0].Status) assert.Equal(t, ocv1.ObjectCounts{Total: ptr.To(int64(2)), Present: ptr.To(int64(2)), Synced: ptr.To(int64(2)), Ready: ptr.To(int64(2))}, cos.Status.ObservedPhases[0].ObjectCounts) }) @@ -267,7 +279,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusStabilizing, cos.Status.ObservedPhases[0].Status) assert.Equal(t, ocv1.ObjectCounts{Total: ptr.To(int64(2)), Present: ptr.To(int64(2)), Synced: ptr.To(int64(2)), Ready: ptr.To(int64(1))}, cos.Status.ObservedPhases[0].ObjectCounts) }) @@ -288,7 +300,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusStabilizing, cos.Status.ObservedPhases[0].Status) assert.Equal(t, ocv1.ObjectCounts{Total: ptr.To(int64(3)), Present: ptr.To(int64(3)), Synced: ptr.To(int64(3)), Ready: ptr.To(int64(0))}, cos.Status.ObservedPhases[0].ObjectCounts) }) @@ -309,7 +321,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) // isPaused=true → default case → Pending (read-only while earlier phase is rolling out) assert.Equal(t, ocv1.PhaseStatusPending, cos.Status.ObservedPhases[0].Status) // ActionUpdated+paused: Present; ActionRecovered+paused: Present; ActionCreated+paused: nothing @@ -340,7 +352,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) require.Len(t, cos.Status.ObservedPhases, 2) assert.Equal(t, ocv1.PhaseStatusPending, cos.Status.ObservedPhases[1].Status) assert.Equal(t, ocv1.ObjectCounts{Total: ptr.To(int64(3)), Present: ptr.To(int64(2)), Synced: ptr.To(int64(1)), Ready: ptr.To(int64(1))}, cos.Status.ObservedPhases[1].ObjectCounts) @@ -360,7 +372,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusReady, cos.Status.ObservedPhases[0].Status) assert.Empty(t, cos.Status.ObservedPhases[0].ObjectDetails) }) @@ -378,7 +390,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusSuperseded, cos.Status.ObservedPhases[0].Status) assert.Empty(t, cos.Status.ObservedPhases[0].ObjectDetails) }) @@ -396,7 +408,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusStabilizing, cos.Status.ObservedPhases[0].Status) assert.Len(t, cos.Status.ObservedPhases[0].ObjectDetails, 1) }) @@ -415,7 +427,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) assert.Equal(t, ocv1.PhaseStatusReconciling, cos.Status.ObservedPhases[0].Status) assert.Len(t, cos.Status.ObservedPhases[0].ObjectDetails, 1) }) @@ -435,7 +447,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) require.Len(t, cos.Status.ObservedPhases[0].ObjectDetails, 1) assert.Equal(t, "my-cm", cos.Status.ObservedPhases[0].ObjectDetails[0].Name) }) @@ -462,7 +474,7 @@ func TestUpdateObservedPhasesForReconcile(t *testing.T) { }}, }, } - UpdateObservedPhasesForReconcile(&cos, result, now) + UpdateObservedPhasesForReconcile(testMapper, &cos, result, now) // p1: Total=2,Present=2,Synced=2,Ready=2 — p2: Total=3,Present=1,Synced=1,Ready=0 assert.Equal(t, ocv1.ObjectCounts{Total: ptr.To(int64(5)), Present: ptr.To(int64(3)), Synced: ptr.To(int64(3)), Ready: ptr.To(int64(2))}, cos.Status.ObjectCounts) }) @@ -482,7 +494,7 @@ func TestUpdateObservedPhasesForTeardown(t *testing.T) { &fakePhaseTeardownResult{name: "p1", waiting: []boxcuttertypes.ObjectRef{{}, {}}}, }, } - UpdateObservedPhasesForTeardown(&cos, result, now) + UpdateObservedPhasesForTeardown(testMapper, &cos, result, now) require.Len(t, cos.Status.ObservedPhases, 1) phase := cos.Status.ObservedPhases[0] assert.Equal(t, ocv1.PhaseStatusArchiving, phase.Status) @@ -508,12 +520,11 @@ func TestUpdateObservedPhasesForTeardown(t *testing.T) { &fakePhaseTeardownResult{name: "p1", waiting: []boxcuttertypes.ObjectRef{ref}}, }, } - UpdateObservedPhasesForTeardown(&cos, result, now) + UpdateObservedPhasesForTeardown(testMapper, &cos, result, now) require.Len(t, cos.Status.ObservedPhases[0].ObjectDetails, 1) detail := cos.Status.ObservedPhases[0].ObjectDetails[0] assert.Equal(t, corev1.SchemeGroupVersion.Group, detail.Group) - assert.Equal(t, corev1.SchemeGroupVersion.Version, detail.Version) - assert.Equal(t, "ConfigMap", detail.Kind) + assert.Equal(t, "configmaps", detail.Resource) assert.Equal(t, "default", detail.Namespace) assert.Equal(t, "my-cm", detail.Name) assert.Equal(t, []string{"awaiting deletion"}, detail.Messages) @@ -530,7 +541,7 @@ func TestUpdateObservedPhasesForTeardown(t *testing.T) { &fakePhaseTeardownResult{name: "p1"}, }, } - UpdateObservedPhasesForTeardown(&cos, result, now) + UpdateObservedPhasesForTeardown(testMapper, &cos, result, now) require.Len(t, cos.Status.ObservedPhases, 1) phase := cos.Status.ObservedPhases[0] assert.Equal(t, ocv1.PhaseStatusArchived, phase.Status) @@ -563,7 +574,7 @@ func TestUpdateObservedPhasesForTeardown(t *testing.T) { &fakePhaseTeardownResult{name: "p1", waiting: []boxcuttertypes.ObjectRef{{}, {}}}, }, } - UpdateObservedPhasesForTeardown(&cos, result, now) + UpdateObservedPhasesForTeardown(testMapper, &cos, result, now) require.Len(t, cos.Status.ObservedPhases, 3) phases := cos.Status.ObservedPhases @@ -610,7 +621,7 @@ func TestUpdateObservedPhasesForTeardown(t *testing.T) { &fakePhaseTeardownResult{name: "p2", waiting: []boxcuttertypes.ObjectRef{{}}}, }, } - UpdateObservedPhasesForTeardown(&cos, result, now) + UpdateObservedPhasesForTeardown(testMapper, &cos, result, now) require.Len(t, cos.Status.ObservedPhases, 2) // p1 not in GetPhases — must not be touched assert.Equal(t, ocv1.PhaseStatusReconciling, cos.Status.ObservedPhases[0].Status) @@ -627,7 +638,7 @@ func TestUpdateObservedPhasesForTeardown(t *testing.T) { &fakePhaseTeardownResult{name: "p1", waiting: []boxcuttertypes.ObjectRef{{}}}, }, } - UpdateObservedPhasesForTeardown(&cos, result, now) + UpdateObservedPhasesForTeardown(testMapper, &cos, result, now) assert.True(t, cos.Status.ObservedPhases[0].CompletedAt.IsZero()) }) @@ -644,7 +655,7 @@ func TestUpdateObservedPhasesForTeardown(t *testing.T) { &fakePhaseTeardownResult{name: "p1", waiting: []boxcuttertypes.ObjectRef{{}}}, }, } - UpdateObservedPhasesForTeardown(&cos, result, now) + UpdateObservedPhasesForTeardown(testMapper, &cos, result, now) require.NotNil(t, cos.Status.ObservedPhases[0].CompletedAt) assert.Equal(t, earlier, cos.Status.ObservedPhases[0].CompletedAt) }) @@ -756,23 +767,22 @@ func TestObjectStatus(t *testing.T) { obj.SetNamespace("default") t.Run("populates object identity from GVK and object ref", func(t *testing.T) { - result := objectStatus(&fakeObjectResult{obj: obj, probes: boxcuttertypes.ProbeResultContainer{}}) + result := objectStatus(testMapper, &fakeObjectResult{obj: obj, probes: boxcuttertypes.ProbeResultContainer{}}) assert.Equal(t, corev1.SchemeGroupVersion.Group, result.Group) - assert.Equal(t, corev1.SchemeGroupVersion.Version, result.Version) - assert.Equal(t, "ConfigMap", result.Kind) + assert.Equal(t, "configmaps", result.Resource) assert.Equal(t, "default", result.Namespace) assert.Equal(t, "my-cm", result.Name) }) t.Run("collision returns ownership collision message", func(t *testing.T) { - result := objectStatus(&fakeCollisionResult{ + result := objectStatus(testMapper, &fakeCollisionResult{ fakeObjectResult: fakeObjectResult{obj: obj, probes: boxcuttertypes.ProbeResultContainer{}}, }) assert.Equal(t, []string{"object ownership collision"}, result.Messages) }) t.Run("failing probe with message", func(t *testing.T) { - result := objectStatus(&fakeObjectResult{ + result := objectStatus(testMapper, &fakeObjectResult{ obj: obj, probes: boxcuttertypes.ProbeResultContainer{ "readiness": {Status: boxcuttertypes.ProbeStatusFalse, Messages: []string{"not ready"}}, @@ -782,7 +792,7 @@ func TestObjectStatus(t *testing.T) { }) t.Run("failing probe without message shows status", func(t *testing.T) { - result := objectStatus(&fakeObjectResult{ + result := objectStatus(testMapper, &fakeObjectResult{ obj: obj, probes: boxcuttertypes.ProbeResultContainer{ "readiness": {Status: boxcuttertypes.ProbeStatusFalse}, @@ -792,7 +802,7 @@ func TestObjectStatus(t *testing.T) { }) t.Run("passing probe is omitted and falls back to not yet complete", func(t *testing.T) { - result := objectStatus(&fakeObjectResult{ + result := objectStatus(testMapper, &fakeObjectResult{ obj: obj, probes: boxcuttertypes.ProbeResultContainer{ "readiness": {Status: boxcuttertypes.ProbeStatusTrue}, @@ -802,7 +812,7 @@ func TestObjectStatus(t *testing.T) { }) t.Run("paused non-idle delegates to pausedObjectMessages", func(t *testing.T) { - result := objectStatus(&fakeObjectResult{ + result := objectStatus(testMapper, &fakeObjectResult{ obj: obj, paused: true, action: machinery.ActionCreated, @@ -812,7 +822,7 @@ func TestObjectStatus(t *testing.T) { }) t.Run("no issues returns not yet complete", func(t *testing.T) { - result := objectStatus(&fakeObjectResult{ + result := objectStatus(testMapper, &fakeObjectResult{ obj: obj, action: machinery.ActionIdle, probes: boxcuttertypes.ProbeResultContainer{}, @@ -828,7 +838,7 @@ func TestObjectStatus(t *testing.T) { Messages: []string{"failed"}, } } - result := objectStatus(&fakeObjectResult{obj: obj, probes: probes}) + result := objectStatus(testMapper, &fakeObjectResult{obj: obj, probes: probes}) require.Len(t, result.Messages, 18) assert.Equal(t, "additional objectDetails messages were removed for size constraints", result.Messages[17]) }) diff --git a/manifests/experimental-e2e.yaml b/manifests/experimental-e2e.yaml index 02a58c730c..678f128f34 100644 --- a/manifests/experimental-e2e.yaml +++ b/manifests/experimental-e2e.yaml @@ -1995,7 +1995,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.22.0 + controller-gen.kubebuilder.io/version: v0.23.0 olm.operatorframework.io/generator: experimental name: clusterobjectsets.olm.operatorframework.io spec: @@ -2820,13 +2820,6 @@ spec: resources). At most 253 characters (DNS subdomain max). maxLength: 253 type: string - kind: - description: |- - kind is the kind of the object. Must be between 1 and 63 - characters. - maxLength: 63 - minLength: 1 - type: string messages: description: |- messages lists the failure reasons for this object. The maximum @@ -2853,17 +2846,22 @@ spec: resources. At most 253 characters (DNS subdomain max). maxLength: 253 type: string - version: + resource: description: |- - version is the API version of the object. Must be between 1 and - 63 characters. + resource is the lowercase, plural resource type of the object + (for example, "pods" or "configmaps"). Must be between 1 and 63 + characters. maxLength: 63 minLength: 1 type: string + x-kubernetes-validations: + - message: the value must consist of only lowercase alphanumeric + characters and hyphens, must start with a letter, + and must end with an alphanumeric character. + rule: '!format.dns1035Label().validate(self).hasValue()' required: - - kind - name - - version + - resource type: object maxItems: 50 type: array diff --git a/manifests/experimental.yaml b/manifests/experimental.yaml index 72b24b71b6..95fc64fe2a 100644 --- a/manifests/experimental.yaml +++ b/manifests/experimental.yaml @@ -1956,7 +1956,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.22.0 + controller-gen.kubebuilder.io/version: v0.23.0 olm.operatorframework.io/generator: experimental name: clusterobjectsets.olm.operatorframework.io spec: @@ -2781,13 +2781,6 @@ spec: resources). At most 253 characters (DNS subdomain max). maxLength: 253 type: string - kind: - description: |- - kind is the kind of the object. Must be between 1 and 63 - characters. - maxLength: 63 - minLength: 1 - type: string messages: description: |- messages lists the failure reasons for this object. The maximum @@ -2814,17 +2807,22 @@ spec: resources. At most 253 characters (DNS subdomain max). maxLength: 253 type: string - version: + resource: description: |- - version is the API version of the object. Must be between 1 and - 63 characters. + resource is the lowercase, plural resource type of the object + (for example, "pods" or "configmaps"). Must be between 1 and 63 + characters. maxLength: 63 minLength: 1 type: string + x-kubernetes-validations: + - message: the value must consist of only lowercase alphanumeric + characters and hyphens, must start with a letter, + and must end with an alphanumeric character. + rule: '!format.dns1035Label().validate(self).hasValue()' required: - - kind - name - - version + - resource type: object maxItems: 50 type: array