diff --git a/api/v1/clusterobjectdeployment_types.go b/api/v1/clusterobjectdeployment_types.go new file mode 100644 index 0000000000..c6a7d68d55 --- /dev/null +++ b/api/v1/clusterobjectdeployment_types.go @@ -0,0 +1,256 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +const ( + ClusterObjectDeploymentKind = "ClusterObjectDeployment" + + // Condition types. + ClusterObjectDeploymentTypeAvailable = "Available" + ClusterObjectDeploymentTypeProgressing = "Progressing" + + // Condition reasons. + ClusterObjectDeploymentReasonAvailable = "Available" + ClusterObjectDeploymentReasonNotAvailable = "NotAvailable" + ClusterObjectDeploymentReasonProgressing = "Progressing" + ClusterObjectDeploymentReasonNewClusterObjectSetProgressed = "NewClusterObjectSetProgressed" + ClusterObjectDeploymentReasonNewClusterObjectSetProgressing = "NewClusterObjectSetProgressing" + ClusterObjectDeploymentReasonProgressDeadlineExceeded = "ProgressDeadlineExceeded" +) + +// ClusterObjectDeploymentSpec defines the mutable desired state of a deployment. +type ClusterObjectDeploymentSpec struct { + // template is the desired metadata and content of newly created ClusterObjectSets. + // Changes to template create a new revision; existing revisions remain immutable. + // +required + Template ClusterObjectDeploymentTemplate `json:"template,omitzero"` + + // progressDeadlineMinutes is the maximum number of minutes without phase progress + // before an incomplete rollout reports ProgressDeadlineExceeded. Omission disables + // the deadline. A supplied value must be positive. Deadline changes do not create + // revisions, and this value is not copied into the legacy ClusterObjectSet deadline. + // +optional + // +kubebuilder:validation:Minimum=1 + ProgressDeadlineMinutes *int32 `json:"progressDeadlineMinutes,omitempty"` //nolint:kubeapilinter // A pointer distinguishes an omitted deadline from an invalid explicit zero. +} + +// ClusterObjectDeploymentTemplate describes the caller-controlled part of a revision. +type ClusterObjectDeploymentTemplate struct { + // metadata contains labels and annotations copied onto each ClusterObjectSet, + // rather than onto the objects it manages. Root deployment metadata is not copied. + // +optional + Metadata ClusterObjectDeploymentTemplateMetadata `json:"metadata,omitzero"` //nolint:kubeapilinter // OpenShift APIs use value structs with omitzero for optional metadata. + + // spec contains the desired revision content. Group, revision, lifecycle state, + // and ownership are assigned by the controller and cannot be set here. + // +required + Spec ClusterObjectDeploymentTemplateSpec `json:"spec,omitzero"` +} + +// ClusterObjectDeploymentTemplateMetadata contains bounded revision metadata. +type ClusterObjectDeploymentTemplateMetadata struct { + // labels contains up to 32 Kubernetes labels copied onto each revision. + // The olm.operatorframework.io/template-hash label is reserved for the controller. + // +optional + // +kubebuilder:validation:MaxProperties=32 + // +kubebuilder:validation:XValidation:rule="self.all(k, !format.qualifiedName().validate(k).hasValue())",message="label keys must be valid Kubernetes qualified names" + // +kubebuilder:validation:XValidation:rule="self.all(k, !format.labelValue().validate(self[k]).hasValue())",message="label values must be valid Kubernetes label values" + // +kubebuilder:validation:XValidation:rule="!('olm.operatorframework.io/template-hash' in self)",message="olm.operatorframework.io/template-hash is reserved for the controller" + Labels map[string]ClusterObjectDeploymentLabelValue `json:"labels,omitempty"` + + // annotations contains up to 32 Kubernetes annotations copied onto each revision. + // Each value and the aggregate size of all keys and values must fit within 256 KiB. + // Any controller-added annotations must also fit in that aggregate budget. + // +optional + // +kubebuilder:validation:MaxProperties=32 + // +kubebuilder:validation:XValidation:rule="self.all(k, !format.qualifiedName().validate(k.lowerAscii()).hasValue())",message="annotation keys must be valid Kubernetes qualified names" + // +kubebuilder:validation:XValidation:rule="self.all(k, bytes(self[k]).size() <= 262144)",message="annotation values must not exceed 256 KiB" + // +kubebuilder:validation:XValidation:rule="self.map(k, bytes(k).size() + bytes(self[k]).size()).sum() <= 262144",message="annotation keys and values must not exceed 256 KiB in total" + Annotations map[string]ClusterObjectDeploymentAnnotationValue `json:"annotations,omitempty"` +} + +// ClusterObjectDeploymentLabelValue is a bounded Kubernetes label value. +// Its length bounds the cost of label validation. +// +kubebuilder:validation:MaxLength=63 +type ClusterObjectDeploymentLabelValue string + +// ClusterObjectDeploymentAnnotationValue is a bounded Kubernetes annotation value. +// The aggregate byte limit is validated on the containing annotations map. +// +kubebuilder:validation:MaxLength=262144 +type ClusterObjectDeploymentAnnotationValue string + +// ClusterObjectDeploymentTemplateSpec contains mutable ClusterObjectSet content. +type ClusterObjectDeploymentTemplateSpec struct { + // phases is an ordered list of 1 to 20 uniquely named phases. Each phase contains + // at most 50 objects using the existing inline object or immutable Secret reference + // representation. Phase, object, and probe order participate in template identity. + // +required + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=20 + // +listType=map + // +listMapKey=name + Phases []ClusterObjectSetPhase `json:"phases"` //nolint:kubeapilinter // These are ordered groups of objects, not an enum describing lifecycle state. + + // progressionProbes supplies custom readiness probes for objects in every phase. + // The existing ClusterObjectSet selector and assertion semantics apply. + // +optional + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=20 + // +listType=atomic + ProgressionProbes []ProgressionProbe `json:"progressionProbes,omitempty"` + + // collisionProtection is the default strategy for objects in the revision. + // Objects and phases may override it. When omitted the controller uses Prevent + // in the generated ClusterObjectSet. Omission remains distinct from explicit + // Prevent for template identity; admission does not default this field. + // +optional + // +kubebuilder:validation:Enum=Prevent;IfNoController;None + CollisionProtection CollisionProtection `json:"collisionProtection,omitempty"` +} + +// ClusterObjectDeploymentStatus summarizes owned active revisions and rollout progress. +type ClusterObjectDeploymentStatus struct { + // conditions describes installation health and progress for the observed generation. + // Available is True for one Ready active revision, False for none or one unready + // revision, and Unknown with reason Progressing for multiple active revisions. + // Progressing is True with reason NewClusterObjectSetProgressing while the desired + // rollout is incomplete, or False with reason NewClusterObjectSetProgressed after + // completion or ProgressDeadlineExceeded after a deadline expires. Completion + // remains recorded even if the completed revision later becomes unhealthy. + // +optional + // +listType=map + // +listMapKey=type + // +patchStrategy=merge + // +patchMergeKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` //nolint:kubeapilinter // This CRD uses structural list-map validation, not protobuf serialization. + + // activeRevisions summarizes owned active, non-deleting ClusterObjectSets, + // including a newly created revision, ordered by increasing int64 revision. + // +optional + // +listType=map + // +listMapKey=name + ActiveRevisions []ClusterObjectDeploymentRevision `json:"activeRevisions,omitempty"` + + // objectCounts reports counts from the highest owned active revision, rather than + // summing overlapping revisions. Omitted when that revision has not reported + // counts or no active revision exists. Available corresponds to the child's ready count. + // +optional + ObjectCounts ClusterObjectDeploymentObjectCounts `json:"objectCounts,omitzero"` +} + +// ClusterObjectDeploymentRevision summarizes the current conditions of an active revision. +type ClusterObjectDeploymentRevision struct { + // name is the name of the ClusterObjectSet. It may contain the full positive int64 + // revision suffix; it is not restricted to a uint32 suffix or a 63-character label. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` + Name string `json:"name,omitempty"` + + // conditions contains the revision's current status conditions. + // +optional + // +listType=map + // +listMapKey=type + // +patchStrategy=merge + // +patchMergeKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` //nolint:kubeapilinter // This CRD uses structural list-map validation, not protobuf serialization. +} + +// ClusterObjectDeploymentObjectCounts reports object states in the highest active revision. +type ClusterObjectDeploymentObjectCounts struct { + // total is the total number of objects in the revision. Its presence distinguishes + // reported zero counts from an omitted objectCounts summary. + // +required + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Total *int64 `json:"total,omitempty"` + + // present is the number of objects found or created on the cluster. + // +optional + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Present int64 `json:"present,omitempty"` //nolint:kubeapilinter // Omission and zero both mean no objects are present. + + // synced is the number of objects matching their desired state. + // +optional + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Synced int64 `json:"synced,omitempty"` //nolint:kubeapilinter // Omission and zero both mean no objects are synced. + + // available is the number of synced objects passing their readiness assertions. + // +optional + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Available int64 `json:"available,omitempty"` //nolint:kubeapilinter // Omission and zero both mean no objects are available. +} + +// +genclient +// +genclient:nonNamespaced +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster,shortName=cod +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name=Available,type=string,JSONPath=`.status.conditions[?(@.type=='Available')].status` +// +kubebuilder:printcolumn:name=Progressing,type=string,JSONPath=`.status.conditions[?(@.type=='Progressing')].status` +// +kubebuilder:printcolumn:name=Age,type=date,JSONPath=`.metadata.creationTimestamp` +// +kubebuilder:validation:XValidation:rule="size(self.metadata.name) <= 52 && self.metadata.name.matches('^[a-z]([a-z0-9-]*[a-z0-9])?$')",message="name must be 1 to 52 lowercase alphanumeric or hyphen characters, starting with a letter and ending with an alphanumeric character" + +// ClusterObjectDeployment manages mutable desired content through immutable +// ClusterObjectSet revisions. It is an experimental, cluster-scoped API. +type ClusterObjectDeployment struct { + metav1.TypeMeta `json:",inline"` + + // metadata is the standard object's metadata. Names are valid COS groups of at + // most 52 characters; generated child names also include an int64 revision suffix. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec defines the desired deployment template and progress deadline. + // +required + Spec ClusterObjectDeploymentSpec `json:"spec,omitzero"` + + // status defines the observed state and is written through the status subresource. + // +optional + Status ClusterObjectDeploymentStatus `json:"status,omitzero"` //nolint:kubeapilinter // OpenShift APIs use value structs with omitzero for optional status. +} + +// +kubebuilder:object:root=true + +// ClusterObjectDeploymentList contains ClusterObjectDeployment objects. +type ClusterObjectDeploymentList struct { + metav1.TypeMeta `json:",inline"` + + // metadata is the standard list metadata. + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items is the list of deployments. + // +required + Items []ClusterObjectDeployment `json:"items"` +} + +func init() { + SchemeBuilder.Register(func(s *runtime.Scheme) error { + s.AddKnownTypes(GroupVersion, &ClusterObjectDeployment{}, &ClusterObjectDeploymentList{}) + return nil + }) +} diff --git a/api/v1/clusterobjectdeployment_types_test.go b/api/v1/clusterobjectdeployment_types_test.go new file mode 100644 index 0000000000..bb2757dcd9 --- /dev/null +++ b/api/v1/clusterobjectdeployment_types_test.go @@ -0,0 +1,510 @@ +package v1 + +import ( + "fmt" + "math" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + apiequality "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestClusterObjectDeploymentValidity(t *testing.T) { + c := newClient(t) + for i, tc := range []struct { + name string + mutate func(*ClusterObjectDeployment) + valid bool + }{ + {name: "minimal template", valid: true}, + {name: "one character name", mutate: func(cod *ClusterObjectDeployment) { cod.Name = "a" }, valid: true}, + {name: "52 character name", mutate: func(cod *ClusterObjectDeployment) { cod.Name = strings.Repeat("a", 52) }, valid: true}, + {name: "53 character name", mutate: func(cod *ClusterObjectDeployment) { cod.Name = strings.Repeat("a", 53) }}, + {name: "name starts with a digit", mutate: func(cod *ClusterObjectDeployment) { cod.Name = "1-deployment" }}, + {name: "name contains a dot", mutate: func(cod *ClusterObjectDeployment) { cod.Name = "deployment.example" }}, + {name: "name contains uppercase", mutate: func(cod *ClusterObjectDeployment) { cod.Name = "Deployment" }}, + {name: "name ends with a hyphen", mutate: func(cod *ClusterObjectDeployment) { cod.Name = "deployment-" }}, + {name: "deadline one", mutate: codDeadline(1), valid: true}, + {name: "deadline maximum int32", mutate: codDeadline(math.MaxInt32), valid: true}, + {name: "deadline zero", mutate: codDeadline(0)}, + {name: "deadline negative", mutate: codDeadline(-1)}, + {name: "collision protection Prevent", mutate: codCollisionProtection(CollisionProtectionPrevent), valid: true}, + {name: "collision protection IfNoController", mutate: codCollisionProtection(CollisionProtectionIfNoController), valid: true}, + {name: "collision protection None", mutate: codCollisionProtection(CollisionProtectionNone), valid: true}, + {name: "collision protection invalid", mutate: codCollisionProtection("Invalid")}, + {name: "phases omitted", mutate: func(cod *ClusterObjectDeployment) { cod.Spec.Template.Spec.Phases = nil }}, + {name: "phases empty", mutate: func(cod *ClusterObjectDeployment) { cod.Spec.Template.Spec.Phases = []ClusterObjectSetPhase{} }}, + {name: "20 phases", mutate: codPhases(20), valid: true}, + {name: "21 phases", mutate: codPhases(21)}, + {name: "duplicate phase names", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Spec.Phases = append(cod.Spec.Template.Spec.Phases, cod.Spec.Template.Spec.Phases[0]) + }}, + {name: "phase name empty", mutate: codPhaseName("")}, + {name: "phase name uppercase", mutate: codPhaseName("Deploy")}, + {name: "phase name contains dot", mutate: codPhaseName("deploy.example")}, + {name: "phase name ends with hyphen", mutate: codPhaseName("deploy-")}, + {name: "phase name 63 characters", mutate: codPhaseName(strings.Repeat("a", 63)), valid: true}, + {name: "phase name 64 characters", mutate: codPhaseName(strings.Repeat("a", 64))}, + {name: "phase with 50 objects", mutate: codPhaseObjects(50), valid: true}, + {name: "phase with 51 objects", mutate: codPhaseObjects(51)}, + {name: "phase collision protection invalid", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Spec.Phases[0].CollisionProtection = "Invalid" + }}, + {name: "object collision protection invalid", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Spec.Phases[0].Objects[0].CollisionProtection = "Invalid" + }}, + {name: "object reference", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Spec.Phases[0].Objects[0] = ClusterObjectSetObject{Ref: ObjectSourceRef{Name: "objects", Key: "configmap"}} + }, valid: true}, + {name: "both inline object and reference", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Spec.Phases[0].Objects[0].Ref = ObjectSourceRef{Name: "objects", Key: "configmap"} + }}, + {name: "neither inline object nor reference", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Spec.Phases[0].Objects[0] = ClusterObjectSetObject{} + }}, + {name: "reference key missing", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Spec.Phases[0].Objects[0] = ClusterObjectSetObject{Ref: ObjectSourceRef{Name: "objects"}} + }}, + {name: "inline object kind missing", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Spec.Phases[0].Objects[0].Object.SetKind("") + }}, + {name: "progression probe", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Spec.ProgressionProbes = []ProgressionProbe{codProbe("ready")} + }, valid: true}, + {name: "progression probe selector union invalid", mutate: func(cod *ClusterObjectDeployment) { + probe := codProbe("ready") + probe.Selector.Label = metav1.LabelSelector{MatchLabels: map[string]string{"app": "example"}} + cod.Spec.Template.Spec.ProgressionProbes = []ProgressionProbe{probe} + }}, + {name: "progression probe assertion union invalid", mutate: func(cod *ClusterObjectDeployment) { + probe := codProbe("ready") + probe.Assertions[0].ConditionEqual = ConditionEqualProbe{Type: "Ready", Status: "True"} + cod.Spec.Template.Spec.ProgressionProbes = []ProgressionProbe{probe} + }}, + {name: "progression probe assertions missing", mutate: func(cod *ClusterObjectDeployment) { + probe := codProbe("ready") + probe.Assertions = nil + cod.Spec.Template.Spec.ProgressionProbes = []ProgressionProbe{probe} + }}, + } { + t.Run(tc.name, func(t *testing.T) { + cod := newTestClusterObjectDeployment(fmt.Sprintf("cod-validity-%d", i)) + if tc.mutate != nil { + tc.mutate(cod) + } + err := c.Create(t.Context(), cod) + if tc.valid { + require.NoError(t, err) + } else { + require.True(t, apierrors.IsInvalid(err), "expected invalid deployment, got: %v", err) + } + }) + } +} + +func TestClusterObjectDeploymentTemplateMetadata(t *testing.T) { + c := newClient(t) + for i, tc := range []struct { + name string + labels map[string]string + annotations map[string]string + valid bool + }{ + {name: "metadata omitted", valid: true}, + {name: "metadata empty", labels: map[string]string{}, annotations: map[string]string{}, valid: true}, + {name: "qualified label", labels: map[string]string{"app.kubernetes.io/name": "example_1.0"}, valid: true}, + {name: "empty label value", labels: map[string]string{"app": ""}, valid: true}, + {name: "32 labels", labels: codMetadataEntries(32), valid: true}, + {name: "33 labels", labels: codMetadataEntries(33)}, + {name: "empty label key", labels: map[string]string{"": "example"}}, + {name: "invalid label key", labels: map[string]string{"example.com/a/b": "example"}}, + {name: "uppercase label prefix", labels: map[string]string{"EXAMPLE.COM/name": "example"}}, + {name: "label value 63 characters", labels: map[string]string{"app": strings.Repeat("a", 63)}, valid: true}, + {name: "label value 64 characters", labels: map[string]string{"app": strings.Repeat("a", 64)}}, + {name: "label value contains spaces", labels: map[string]string{"app": "invalid value"}}, + {name: "reserved template hash label", labels: map[string]string{"olm.operatorframework.io/template-hash": "example"}}, + {name: "qualified annotation", annotations: map[string]string{"example.com/description": "arbitrary annotation text\nwith unicode: é"}, valid: true}, + {name: "uppercase annotation prefix", annotations: map[string]string{"EXAMPLE.COM/description": "example"}, valid: true}, + {name: "32 annotations", annotations: codMetadataEntries(32), valid: true}, + {name: "33 annotations", annotations: codMetadataEntries(33)}, + {name: "empty annotation key", annotations: map[string]string{"": "example"}}, + {name: "invalid annotation key", annotations: map[string]string{"example.com/a/b": "example"}}, + {name: "annotation aggregate exactly 256 KiB", annotations: map[string]string{"note": strings.Repeat("a", 262140)}, valid: true}, + {name: "annotation aggregate exceeds 256 KiB", annotations: map[string]string{"note": strings.Repeat("a", 262141)}}, + {name: "annotation values collectively exceed budget", annotations: map[string]string{ + "first": strings.Repeat("a", 131068), "second": strings.Repeat("a", 131068), + }}, + {name: "unicode annotation exactly 256 KiB", annotations: map[string]string{"note": strings.Repeat("é", 131070)}, valid: true}, + {name: "unicode annotation exceeds byte budget", annotations: map[string]string{"note": strings.Repeat("é", 131071)}}, + } { + t.Run(tc.name, func(t *testing.T) { + cod := newTestClusterObjectDeployment(fmt.Sprintf("cod-metadata-%d", i)) + cod.Spec.Template.Metadata.Labels = codLabels(tc.labels) + cod.Spec.Template.Metadata.Annotations = codAnnotations(tc.annotations) + err := c.Create(t.Context(), cod) + if tc.valid { + require.NoError(t, err) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + if len(tc.labels) != 0 { + require.Equal(t, codLabels(tc.labels), cod.Spec.Template.Metadata.Labels) + } + if len(tc.annotations) != 0 { + require.Equal(t, codAnnotations(tc.annotations), cod.Spec.Template.Metadata.Annotations) + } + } else { + require.True(t, apierrors.IsInvalid(err), "expected invalid template metadata, got: %v", err) + } + }) + } +} + +func TestClusterObjectDeploymentDoesNotDefaultTemplate(t *testing.T) { + c := newClient(t) + cod := newTestClusterObjectDeployment("cod-no-defaults") + require.NoError(t, c.Create(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Nil(t, cod.Spec.ProgressDeadlineMinutes) + require.Empty(t, cod.Spec.Template.Spec.CollisionProtection) + require.Empty(t, cod.Spec.Template.Spec.Phases[0].CollisionProtection) + require.Empty(t, cod.Spec.Template.Spec.Phases[0].Objects[0].CollisionProtection) + + // Omitted and explicitly supplied Prevent remain distinguishable templates. + cod.Spec.Template.Spec.CollisionProtection = CollisionProtectionPrevent + require.NoError(t, c.Update(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Equal(t, CollisionProtectionPrevent, cod.Spec.Template.Spec.CollisionProtection) + cod.Spec.Template.Spec.CollisionProtection = "" + require.NoError(t, c.Update(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Empty(t, cod.Spec.Template.Spec.CollisionProtection) +} + +func TestClusterObjectDeploymentTemplateIsMutable(t *testing.T) { + c := newClient(t) + cod := newTestClusterObjectDeployment("cod-mutable-template") + require.NoError(t, c.Create(t.Context(), cod)) + initialGeneration := cod.Generation + + secondObject := configMap() + secondObject.SetName("second-cm") + cod.Spec.Template.Spec.Phases = append(cod.Spec.Template.Spec.Phases, ClusterObjectSetPhase{ + Name: "second", Objects: []ClusterObjectSetObject{{Object: secondObject}}, + }) + cod.Spec.Template.Spec.CollisionProtection = CollisionProtectionIfNoController + cod.Spec.Template.Spec.ProgressionProbes = []ProgressionProbe{codProbe("first"), codProbe("second")} + cod.Spec.Template.Metadata.Labels = codLabels(map[string]string{"app": "example"}) + cod.Spec.Template.Metadata.Annotations = codAnnotations(map[string]string{"note": "first"}) + codDeadline(1)(cod) + require.NoError(t, c.Update(t.Context(), cod)) + require.Greater(t, cod.Generation, initialGeneration) + + // COS immutability rules must not apply to the mutable deployment template. + cod.Spec.Template.Spec.Phases[0], cod.Spec.Template.Spec.Phases[1] = cod.Spec.Template.Spec.Phases[1], cod.Spec.Template.Spec.Phases[0] + cod.Spec.Template.Spec.Phases[0].Name = "renamed" + cod.Spec.Template.Spec.Phases[0].Objects[0].Object.Object["data"] = map[string]interface{}{"mode": "updated"} + cod.Spec.Template.Spec.CollisionProtection = CollisionProtectionNone + cod.Spec.Template.Spec.ProgressionProbes[0], cod.Spec.Template.Spec.ProgressionProbes[1] = cod.Spec.Template.Spec.ProgressionProbes[1], cod.Spec.Template.Spec.ProgressionProbes[0] + cod.Spec.Template.Spec.ProgressionProbes[0].Assertions[0].FieldValue.Value = "updated" + cod.Spec.Template.Metadata.Labels["app"] = "updated" + cod.Spec.Template.Metadata.Annotations["note"] = "updated" + codDeadline(2)(cod) + expected := cod.Spec.DeepCopy() + require.NoError(t, c.Update(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Equal(t, *expected, cod.Spec) + + // Optional fields can also be removed after being set. + cod.Spec.ProgressDeadlineMinutes = nil + cod.Spec.Template.Metadata = ClusterObjectDeploymentTemplateMetadata{} + cod.Spec.Template.Spec.ProgressionProbes = nil + cod.Spec.Template.Spec.CollisionProtection = "" + require.NoError(t, c.Update(t.Context(), cod)) +} + +func TestClusterObjectDeploymentUpdateValidation(t *testing.T) { + c := newClient(t) + for i, tc := range []struct { + name string + mutate func(*ClusterObjectDeployment) + }{ + {name: "zero deadline", mutate: codDeadline(0)}, + {name: "no phases", mutate: func(cod *ClusterObjectDeployment) { cod.Spec.Template.Spec.Phases = nil }}, + {name: "invalid collision protection", mutate: codCollisionProtection("Invalid")}, + {name: "reserved template label", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Metadata.Labels = codLabels(map[string]string{"olm.operatorframework.io/template-hash": "example"}) + }}, + {name: "annotation budget exceeded", mutate: func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Metadata.Annotations = codAnnotations(map[string]string{"note": strings.Repeat("é", 131071)}) + }}, + } { + t.Run(tc.name, func(t *testing.T) { + cod := newTestClusterObjectDeployment(fmt.Sprintf("cod-update-validity-%d", i)) + require.NoError(t, c.Create(t.Context(), cod)) + expected := cod.Spec.DeepCopy() + tc.mutate(cod) + err := c.Update(t.Context(), cod) + require.True(t, apierrors.IsInvalid(err), "expected invalid deployment update, got: %v", err) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Equal(t, *expected, cod.Spec) + }) + } +} + +func TestClusterObjectDeploymentStatusSubresource(t *testing.T) { + c := newClient(t) + cod := newTestClusterObjectDeployment("cod-status-subresource") + cod.Status = codTestStatus() + require.NoError(t, c.Create(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Equal(t, ClusterObjectDeploymentStatus{}, cod.Status, "create must discard status") + + cod.Status = codTestStatus() + require.NoError(t, c.Update(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Equal(t, ClusterObjectDeploymentStatus{}, cod.Status, "ordinary update must preserve existing status") + + expectedSpec := cod.Spec.DeepCopy() + cod.Spec.Template.Spec.CollisionProtection = CollisionProtectionNone + cod.Status = codTestStatus() + expectedStatus := cod.Status.DeepCopy() + require.NoError(t, c.Status().Update(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Equal(t, *expectedSpec, cod.Spec, "status update must preserve spec") + require.True(t, apiequality.Semantic.DeepEqual(*expectedStatus, cod.Status), "status must survive storage") + + cod.Status = ClusterObjectDeploymentStatus{} + cod.Spec.Template.Spec.CollisionProtection = CollisionProtectionPrevent + require.NoError(t, c.Update(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.True(t, apiequality.Semantic.DeepEqual(*expectedStatus, cod.Status), "ordinary update cannot clear status") + require.Equal(t, CollisionProtectionPrevent, cod.Spec.Template.Spec.CollisionProtection) +} + +func TestClusterObjectDeploymentTemplateSchema(t *testing.T) { + c := newClient(t) + t.Run("deadline exceeds int32", func(t *testing.T) { + cod := newTestClusterObjectDeployment("cod-deadline-overflow") + raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cod) + require.NoError(t, err) + require.NoError(t, unstructured.SetNestedField(raw, int64(math.MaxInt32)+1, "spec", "progressDeadlineMinutes")) + err = c.Create(t.Context(), &unstructured.Unstructured{Object: raw}) + require.True(t, apierrors.IsInvalid(err), "expected deadline outside int32 range to be invalid, got: %v", err) + }) + + for i, path := range [][]string{ + {"spec"}, + {"spec", "template"}, + {"spec", "template", "spec"}, + {"spec", "template", "spec", "phases"}, + } { + t.Run("required "+strings.Join(path, "."), func(t *testing.T) { + cod := newTestClusterObjectDeployment(fmt.Sprintf("cod-required-%d", i)) + raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cod) + require.NoError(t, err) + unstructured.RemoveNestedField(raw, path...) + err = c.Create(t.Context(), &unstructured.Unstructured{Object: raw}) + require.True(t, apierrors.IsInvalid(err), "expected missing required field to be invalid, got: %v", err) + }) + } + + t.Run("controller assigned fields are absent", func(t *testing.T) { + cod := newTestClusterObjectDeployment("cod-template-pruning") + raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cod) + require.NoError(t, err) + for field, value := range map[string]interface{}{ + "group": "example", "revision": int64(42), "lifecycleState": "Active", "progressDeadlineMinutes": int64(10), + } { + require.NoError(t, unstructured.SetNestedField(raw, value, "spec", "template", "spec", field)) + } + obj := &unstructured.Unstructured{Object: raw} + require.NoError(t, c.Create(t.Context(), obj)) + require.NoError(t, c.Get(t.Context(), client.ObjectKeyFromObject(obj), obj)) + for _, field := range []string{"group", "revision", "lifecycleState", "progressDeadlineMinutes"} { + _, found, err := unstructured.NestedFieldNoCopy(obj.Object, "spec", "template", "spec", field) + require.NoError(t, err) + require.False(t, found, "template must not expose %s", field) + } + }) +} + +func TestClusterObjectDeploymentStatusValidation(t *testing.T) { + c := newClient(t) + for i, tc := range []struct { + name string + mutate func(*ClusterObjectDeploymentStatus) + valid bool + }{ + {name: "full int64 revision suffix", mutate: func(status *ClusterObjectDeploymentStatus) { + status.ActiveRevisions[0].Name = strings.Repeat("a", 52) + "-9223372036854775807" + }, valid: true}, + {name: "invalid revision name", mutate: func(status *ClusterObjectDeploymentStatus) { + status.ActiveRevisions[0].Name = "invalid/name" + }}, + {name: "duplicate revisions", mutate: func(status *ClusterObjectDeploymentStatus) { + status.ActiveRevisions = append(status.ActiveRevisions, status.ActiveRevisions[0]) + }}, + {name: "duplicate deployment conditions", mutate: func(status *ClusterObjectDeploymentStatus) { + status.Conditions = append(status.Conditions, status.Conditions[0]) + }}, + {name: "duplicate revision conditions", mutate: func(status *ClusterObjectDeploymentStatus) { + status.ActiveRevisions[0].Conditions = append(status.ActiveRevisions[0].Conditions, status.ActiveRevisions[0].Conditions[0]) + }}, + {name: "negative total", mutate: func(status *ClusterObjectDeploymentStatus) { status.ObjectCounts.Total = ptr.To(int64(-1)) }}, + {name: "total missing", mutate: func(status *ClusterObjectDeploymentStatus) { status.ObjectCounts.Total = nil }}, + {name: "negative present", mutate: func(status *ClusterObjectDeploymentStatus) { status.ObjectCounts.Present = -1 }}, + {name: "negative synced", mutate: func(status *ClusterObjectDeploymentStatus) { status.ObjectCounts.Synced = -1 }}, + {name: "negative available", mutate: func(status *ClusterObjectDeploymentStatus) { status.ObjectCounts.Available = -1 }}, + {name: "maximum counts", mutate: func(status *ClusterObjectDeploymentStatus) { + status.ObjectCounts = ClusterObjectDeploymentObjectCounts{Total: ptr.To(int64(1000)), Present: 1000, Synced: 1000, Available: 1000} + }, valid: true}, + {name: "total exceeds object limit", mutate: func(status *ClusterObjectDeploymentStatus) { status.ObjectCounts.Total = ptr.To(int64(1001)) }}, + } { + t.Run(tc.name, func(t *testing.T) { + cod := newTestClusterObjectDeployment(fmt.Sprintf("cod-status-validity-%d", i)) + require.NoError(t, c.Create(t.Context(), cod)) + cod.Status = codTestStatus() + tc.mutate(&cod.Status) + err := c.Status().Update(t.Context(), cod) + if tc.valid { + require.NoError(t, err) + } else { + require.True(t, apierrors.IsInvalid(err), "expected invalid deployment status, got: %v", err) + } + }) + } +} + +func TestClusterObjectDeploymentObjectCountsPresence(t *testing.T) { + c := newClient(t) + cod := newTestClusterObjectDeployment("cod-zero-object-counts") + require.NoError(t, c.Create(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Nil(t, cod.Status.ObjectCounts.Total, "unreported counts must remain absent") + + cod.Status.ObjectCounts = ClusterObjectDeploymentObjectCounts{Total: ptr.To(int64(0))} + require.NoError(t, c.Status().Update(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Equal(t, ClusterObjectDeploymentObjectCounts{Total: ptr.To(int64(0))}, cod.Status.ObjectCounts, + "reported zero counts must survive storage") + + cod.Status.ObjectCounts = ClusterObjectDeploymentObjectCounts{} + require.NoError(t, c.Status().Update(t.Context(), cod)) + cod = getTestClusterObjectDeployment(t, c, cod.Name) + require.Nil(t, cod.Status.ObjectCounts.Total, "previously reported counts may be omitted") +} + +func getTestClusterObjectDeployment(t *testing.T, c client.Client, name string) *ClusterObjectDeployment { + t.Helper() + cod := &ClusterObjectDeployment{} + require.NoError(t, c.Get(t.Context(), client.ObjectKey{Name: name}, cod)) + return cod +} + +func newTestClusterObjectDeployment(name string) *ClusterObjectDeployment { + return &ClusterObjectDeployment{ + TypeMeta: metav1.TypeMeta{APIVersion: GroupVersion.String(), Kind: "ClusterObjectDeployment"}, + ObjectMeta: metav1.ObjectMeta{Name: name}, + Spec: ClusterObjectDeploymentSpec{ + Template: ClusterObjectDeploymentTemplate{ + Spec: ClusterObjectDeploymentTemplateSpec{ + Phases: []ClusterObjectSetPhase{{Name: "deploy", Objects: []ClusterObjectSetObject{{Object: configMap()}}}}, + }, + }, + }, + } +} + +func codDeadline(minutes int32) func(*ClusterObjectDeployment) { + return func(cod *ClusterObjectDeployment) { cod.Spec.ProgressDeadlineMinutes = &minutes } +} + +func codCollisionProtection(value CollisionProtection) func(*ClusterObjectDeployment) { + return func(cod *ClusterObjectDeployment) { cod.Spec.Template.Spec.CollisionProtection = value } +} + +func codPhaseName(name string) func(*ClusterObjectDeployment) { + return func(cod *ClusterObjectDeployment) { cod.Spec.Template.Spec.Phases[0].Name = name } +} + +func codPhases(count int) func(*ClusterObjectDeployment) { + return func(cod *ClusterObjectDeployment) { + cod.Spec.Template.Spec.Phases = make([]ClusterObjectSetPhase, count) + for i := range cod.Spec.Template.Spec.Phases { + cod.Spec.Template.Spec.Phases[i] = ClusterObjectSetPhase{ + Name: fmt.Sprintf("phase-%d", i), Objects: []ClusterObjectSetObject{{Object: configMap()}}, + } + } + } +} + +func codPhaseObjects(count int) func(*ClusterObjectDeployment) { + return func(cod *ClusterObjectDeployment) { + objects := make([]ClusterObjectSetObject, count) + for i := range objects { + obj := configMap() + obj.SetName(fmt.Sprintf("configmap-%d", i)) + objects[i] = ClusterObjectSetObject{Object: obj} + } + cod.Spec.Template.Spec.Phases[0].Objects = objects + } +} + +func codMetadataEntries(count int) map[string]string { + entries := make(map[string]string, count) + for i := range count { + entries[fmt.Sprintf("example.com/key-%d", i)] = "example" + } + return entries +} + +func codProbe(value string) ProgressionProbe { + return ProgressionProbe{ + Selector: ObjectSelector{Type: SelectorTypeGroupKind, GroupKind: metav1.GroupKind{Kind: "ConfigMap"}}, + Assertions: []Assertion{{ + Type: ProbeTypeFieldValue, + FieldValue: FieldValueProbe{FieldPath: "data.mode", Value: value}, + }}, + } +} + +func codTestStatus() ClusterObjectDeploymentStatus { + condition := metav1.Condition{ + Type: "Available", Status: metav1.ConditionTrue, Reason: "Available", Message: "objects are available", + ObservedGeneration: 1, LastTransitionTime: metav1.NewTime(time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)), + } + return ClusterObjectDeploymentStatus{ + Conditions: []metav1.Condition{condition}, + ActiveRevisions: []ClusterObjectDeploymentRevision{{Name: "cod-status-subresource-1", Conditions: []metav1.Condition{condition}}}, + ObjectCounts: ClusterObjectDeploymentObjectCounts{Total: ptr.To(int64(3)), Present: 2, Synced: 1, Available: 1}, + } +} + +func codLabels(values map[string]string) map[string]ClusterObjectDeploymentLabelValue { + if values == nil { + return nil + } + converted := make(map[string]ClusterObjectDeploymentLabelValue, len(values)) + for key, value := range values { + converted[key] = ClusterObjectDeploymentLabelValue(value) + } + return converted +} + +func codAnnotations(values map[string]string) map[string]ClusterObjectDeploymentAnnotationValue { + if values == nil { + return nil + } + converted := make(map[string]ClusterObjectDeploymentAnnotationValue, len(values)) + for key, value := range values { + converted[key] = ClusterObjectDeploymentAnnotationValue(value) + } + return converted +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 2ddfb0da6a..df1d0fef4a 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -423,6 +423,233 @@ func (in *ClusterExtensionStatus) DeepCopy() *ClusterExtensionStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterObjectDeployment) DeepCopyInto(out *ClusterObjectDeployment) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterObjectDeployment. +func (in *ClusterObjectDeployment) DeepCopy() *ClusterObjectDeployment { + if in == nil { + return nil + } + out := new(ClusterObjectDeployment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterObjectDeployment) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterObjectDeploymentList) DeepCopyInto(out *ClusterObjectDeploymentList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterObjectDeployment, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterObjectDeploymentList. +func (in *ClusterObjectDeploymentList) DeepCopy() *ClusterObjectDeploymentList { + if in == nil { + return nil + } + out := new(ClusterObjectDeploymentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterObjectDeploymentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterObjectDeploymentObjectCounts) DeepCopyInto(out *ClusterObjectDeploymentObjectCounts) { + *out = *in + if in.Total != nil { + in, out := &in.Total, &out.Total + *out = new(int64) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterObjectDeploymentObjectCounts. +func (in *ClusterObjectDeploymentObjectCounts) DeepCopy() *ClusterObjectDeploymentObjectCounts { + if in == nil { + return nil + } + out := new(ClusterObjectDeploymentObjectCounts) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterObjectDeploymentRevision) DeepCopyInto(out *ClusterObjectDeploymentRevision) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterObjectDeploymentRevision. +func (in *ClusterObjectDeploymentRevision) DeepCopy() *ClusterObjectDeploymentRevision { + if in == nil { + return nil + } + out := new(ClusterObjectDeploymentRevision) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterObjectDeploymentSpec) DeepCopyInto(out *ClusterObjectDeploymentSpec) { + *out = *in + in.Template.DeepCopyInto(&out.Template) + if in.ProgressDeadlineMinutes != nil { + in, out := &in.ProgressDeadlineMinutes, &out.ProgressDeadlineMinutes + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterObjectDeploymentSpec. +func (in *ClusterObjectDeploymentSpec) DeepCopy() *ClusterObjectDeploymentSpec { + if in == nil { + return nil + } + out := new(ClusterObjectDeploymentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterObjectDeploymentStatus) DeepCopyInto(out *ClusterObjectDeploymentStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ActiveRevisions != nil { + in, out := &in.ActiveRevisions, &out.ActiveRevisions + *out = make([]ClusterObjectDeploymentRevision, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.ObjectCounts.DeepCopyInto(&out.ObjectCounts) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterObjectDeploymentStatus. +func (in *ClusterObjectDeploymentStatus) DeepCopy() *ClusterObjectDeploymentStatus { + if in == nil { + return nil + } + out := new(ClusterObjectDeploymentStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterObjectDeploymentTemplate) DeepCopyInto(out *ClusterObjectDeploymentTemplate) { + *out = *in + in.Metadata.DeepCopyInto(&out.Metadata) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterObjectDeploymentTemplate. +func (in *ClusterObjectDeploymentTemplate) DeepCopy() *ClusterObjectDeploymentTemplate { + if in == nil { + return nil + } + out := new(ClusterObjectDeploymentTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterObjectDeploymentTemplateMetadata) DeepCopyInto(out *ClusterObjectDeploymentTemplateMetadata) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]ClusterObjectDeploymentLabelValue, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]ClusterObjectDeploymentAnnotationValue, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterObjectDeploymentTemplateMetadata. +func (in *ClusterObjectDeploymentTemplateMetadata) DeepCopy() *ClusterObjectDeploymentTemplateMetadata { + if in == nil { + return nil + } + out := new(ClusterObjectDeploymentTemplateMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterObjectDeploymentTemplateSpec) DeepCopyInto(out *ClusterObjectDeploymentTemplateSpec) { + *out = *in + if in.Phases != nil { + in, out := &in.Phases, &out.Phases + *out = make([]ClusterObjectSetPhase, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ProgressionProbes != nil { + in, out := &in.ProgressionProbes, &out.ProgressionProbes + *out = make([]ProgressionProbe, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterObjectDeploymentTemplateSpec. +func (in *ClusterObjectDeploymentTemplateSpec) DeepCopy() *ClusterObjectDeploymentTemplateSpec { + if in == nil { + return nil + } + out := new(ClusterObjectDeploymentTemplateSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterObjectSet) DeepCopyInto(out *ClusterObjectSet) { *out = *in diff --git a/applyconfigurations/api/v1/clusterobjectdeployment.go b/applyconfigurations/api/v1/clusterobjectdeployment.go new file mode 100644 index 0000000000..a53b7824af --- /dev/null +++ b/applyconfigurations/api/v1/clusterobjectdeployment.go @@ -0,0 +1,292 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen-v0.21. DO NOT EDIT. + +package v1 + +import ( + apiv1 "github.com/operator-framework/operator-controller/api/v1" + internal "github.com/operator-framework/operator-controller/applyconfigurations/internal" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// ClusterObjectDeploymentApplyConfiguration represents a declarative configuration of the ClusterObjectDeployment type for use +// with apply. +// +// ClusterObjectDeployment manages mutable desired content through immutable +// ClusterObjectSet revisions. It is an experimental, cluster-scoped API. +type ClusterObjectDeploymentApplyConfiguration struct { + metav1.TypeMetaApplyConfiguration `json:",inline"` + // metadata is the standard object's metadata. Names are valid COS groups of at + // most 52 characters; generated child names also include an int64 revision suffix. + *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + // spec defines the desired deployment template and progress deadline. + Spec *ClusterObjectDeploymentSpecApplyConfiguration `json:"spec,omitempty"` + // status defines the observed state and is written through the status subresource. + Status *ClusterObjectDeploymentStatusApplyConfiguration `json:"status,omitempty"` +} + +// ClusterObjectDeployment constructs a declarative configuration of the ClusterObjectDeployment type for use with +// apply. +func ClusterObjectDeployment(name string) *ClusterObjectDeploymentApplyConfiguration { + b := &ClusterObjectDeploymentApplyConfiguration{} + b.WithName(name) + b.WithKind("ClusterObjectDeployment") + b.WithAPIVersion("olm.operatorframework.io/v1") + return b +} + +// ExtractClusterObjectDeploymentFrom extracts the applied configuration owned by fieldManager from +// clusterObjectDeployment for the specified subresource. Pass an empty string for subresource to extract +// the main resource. Common subresources include "status", "scale", etc. +// clusterObjectDeployment must be a unmodified ClusterObjectDeployment API object that was retrieved from the Kubernetes API. +// ExtractClusterObjectDeploymentFrom provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractClusterObjectDeploymentFrom(clusterObjectDeployment *apiv1.ClusterObjectDeployment, fieldManager string, subresource string) (*ClusterObjectDeploymentApplyConfiguration, error) { + b := &ClusterObjectDeploymentApplyConfiguration{} + err := managedfields.ExtractInto(clusterObjectDeployment, internal.Parser().Type("com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeployment"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(clusterObjectDeployment.Name) + + b.WithKind("ClusterObjectDeployment") + b.WithAPIVersion("olm.operatorframework.io/v1") + return b, nil +} + +// ExtractClusterObjectDeployment extracts the applied configuration owned by fieldManager from +// clusterObjectDeployment. If no managedFields are found in clusterObjectDeployment for fieldManager, a +// ClusterObjectDeploymentApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// clusterObjectDeployment must be a unmodified ClusterObjectDeployment API object that was retrieved from the Kubernetes API. +// ExtractClusterObjectDeployment provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func ExtractClusterObjectDeployment(clusterObjectDeployment *apiv1.ClusterObjectDeployment, fieldManager string) (*ClusterObjectDeploymentApplyConfiguration, error) { + return ExtractClusterObjectDeploymentFrom(clusterObjectDeployment, fieldManager, "") +} + +// ExtractClusterObjectDeploymentStatus extracts the applied configuration owned by fieldManager from +// clusterObjectDeployment for the status subresource. +func ExtractClusterObjectDeploymentStatus(clusterObjectDeployment *apiv1.ClusterObjectDeployment, fieldManager string) (*ClusterObjectDeploymentApplyConfiguration, error) { + return ExtractClusterObjectDeploymentFrom(clusterObjectDeployment, fieldManager, "status") +} + +func (b ClusterObjectDeploymentApplyConfiguration) IsApplyConfiguration() {} + +// 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 *ClusterObjectDeploymentApplyConfiguration) WithKind(value string) *ClusterObjectDeploymentApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion 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 APIVersion field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithAPIVersion(value string) *ClusterObjectDeploymentApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name 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 Name field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithName(value string) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName 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 GenerateName field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithGenerateName(value string) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace 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 Namespace field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithNamespace(value string) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID 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 UID field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithUID(value types.UID) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion 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 ResourceVersion field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithResourceVersion(value string) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation 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 Generation field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithGeneration(value int64) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp 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 CreationTimestamp field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithCreationTimestamp(value apismetav1.Time) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp 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 DeletionTimestamp field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithDeletionTimestamp(value apismetav1.Time) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds 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 DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ClusterObjectDeploymentApplyConfiguration) WithLabels(entries map[string]string) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ClusterObjectDeploymentApplyConfiguration) WithAnnotations(entries map[string]string) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *ClusterObjectDeploymentApplyConfiguration) WithOwnerReferences(values ...*metav1.OwnerReferenceApplyConfiguration) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *ClusterObjectDeploymentApplyConfiguration) WithFinalizers(values ...string) *ClusterObjectDeploymentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *ClusterObjectDeploymentApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &metav1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec 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 Spec field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithSpec(value *ClusterObjectDeploymentSpecApplyConfiguration) *ClusterObjectDeploymentApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status 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 Status field is set to the value of the last call. +func (b *ClusterObjectDeploymentApplyConfiguration) WithStatus(value *ClusterObjectDeploymentStatusApplyConfiguration) *ClusterObjectDeploymentApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *ClusterObjectDeploymentApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *ClusterObjectDeploymentApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *ClusterObjectDeploymentApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *ClusterObjectDeploymentApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/applyconfigurations/api/v1/clusterobjectdeploymentobjectcounts.go b/applyconfigurations/api/v1/clusterobjectdeploymentobjectcounts.go new file mode 100644 index 0000000000..0d2103cc21 --- /dev/null +++ b/applyconfigurations/api/v1/clusterobjectdeploymentobjectcounts.go @@ -0,0 +1,73 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen-v0.21. DO NOT EDIT. + +package v1 + +// ClusterObjectDeploymentObjectCountsApplyConfiguration represents a declarative configuration of the ClusterObjectDeploymentObjectCounts type for use +// with apply. +// +// ClusterObjectDeploymentObjectCounts reports object states in the highest active revision. +type ClusterObjectDeploymentObjectCountsApplyConfiguration struct { + // total is the total number of objects in the revision. Its presence distinguishes + // reported zero counts from an omitted objectCounts summary. + Total *int64 `json:"total,omitempty"` + // present is the number of objects found or created on the cluster. + Present *int64 `json:"present,omitempty"` + // synced is the number of objects matching their desired state. + Synced *int64 `json:"synced,omitempty"` + // available is the number of synced objects passing their readiness assertions. + Available *int64 `json:"available,omitempty"` +} + +// ClusterObjectDeploymentObjectCountsApplyConfiguration constructs a declarative configuration of the ClusterObjectDeploymentObjectCounts type for use with +// apply. +func ClusterObjectDeploymentObjectCounts() *ClusterObjectDeploymentObjectCountsApplyConfiguration { + return &ClusterObjectDeploymentObjectCountsApplyConfiguration{} +} + +// WithTotal sets the Total 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 Total field is set to the value of the last call. +func (b *ClusterObjectDeploymentObjectCountsApplyConfiguration) WithTotal(value int64) *ClusterObjectDeploymentObjectCountsApplyConfiguration { + b.Total = &value + return b +} + +// WithPresent sets the Present 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 Present field is set to the value of the last call. +func (b *ClusterObjectDeploymentObjectCountsApplyConfiguration) WithPresent(value int64) *ClusterObjectDeploymentObjectCountsApplyConfiguration { + b.Present = &value + return b +} + +// WithSynced sets the Synced 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 Synced field is set to the value of the last call. +func (b *ClusterObjectDeploymentObjectCountsApplyConfiguration) WithSynced(value int64) *ClusterObjectDeploymentObjectCountsApplyConfiguration { + b.Synced = &value + return b +} + +// WithAvailable sets the Available 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 Available field is set to the value of the last call. +func (b *ClusterObjectDeploymentObjectCountsApplyConfiguration) WithAvailable(value int64) *ClusterObjectDeploymentObjectCountsApplyConfiguration { + b.Available = &value + return b +} diff --git a/applyconfigurations/api/v1/clusterobjectdeploymentrevision.go b/applyconfigurations/api/v1/clusterobjectdeploymentrevision.go new file mode 100644 index 0000000000..9e7af9abf1 --- /dev/null +++ b/applyconfigurations/api/v1/clusterobjectdeploymentrevision.go @@ -0,0 +1,62 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen-v0.21. DO NOT EDIT. + +package v1 + +import ( + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// ClusterObjectDeploymentRevisionApplyConfiguration represents a declarative configuration of the ClusterObjectDeploymentRevision type for use +// with apply. +// +// ClusterObjectDeploymentRevision summarizes the current conditions of an active revision. +type ClusterObjectDeploymentRevisionApplyConfiguration struct { + // name is the name of the ClusterObjectSet. It may contain the full positive int64 + // revision suffix; it is not restricted to a uint32 suffix or a 63-character label. + Name *string `json:"name,omitempty"` + // conditions contains the revision's current status conditions. + Conditions []metav1.ConditionApplyConfiguration `json:"conditions,omitempty"` +} + +// ClusterObjectDeploymentRevisionApplyConfiguration constructs a declarative configuration of the ClusterObjectDeploymentRevision type for use with +// apply. +func ClusterObjectDeploymentRevision() *ClusterObjectDeploymentRevisionApplyConfiguration { + return &ClusterObjectDeploymentRevisionApplyConfiguration{} +} + +// WithName sets the Name 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 Name field is set to the value of the last call. +func (b *ClusterObjectDeploymentRevisionApplyConfiguration) WithName(value string) *ClusterObjectDeploymentRevisionApplyConfiguration { + b.Name = &value + return b +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *ClusterObjectDeploymentRevisionApplyConfiguration) WithConditions(values ...*metav1.ConditionApplyConfiguration) *ClusterObjectDeploymentRevisionApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} diff --git a/applyconfigurations/api/v1/clusterobjectdeploymentspec.go b/applyconfigurations/api/v1/clusterobjectdeploymentspec.go new file mode 100644 index 0000000000..8896be1908 --- /dev/null +++ b/applyconfigurations/api/v1/clusterobjectdeploymentspec.go @@ -0,0 +1,56 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen-v0.21. DO NOT EDIT. + +package v1 + +// ClusterObjectDeploymentSpecApplyConfiguration represents a declarative configuration of the ClusterObjectDeploymentSpec type for use +// with apply. +// +// ClusterObjectDeploymentSpec defines the mutable desired state of a deployment. +type ClusterObjectDeploymentSpecApplyConfiguration struct { + // template is the desired metadata and content of newly created ClusterObjectSets. + // Changes to template create a new revision; existing revisions remain immutable. + Template *ClusterObjectDeploymentTemplateApplyConfiguration `json:"template,omitempty"` + // progressDeadlineMinutes is the maximum number of minutes without phase progress + // before an incomplete rollout reports ProgressDeadlineExceeded. Omission disables + // the deadline. A supplied value must be positive. Deadline changes do not create + // revisions, and this value is not copied into the legacy ClusterObjectSet deadline. + ProgressDeadlineMinutes *int32 `json:"progressDeadlineMinutes,omitempty"` +} + +// ClusterObjectDeploymentSpecApplyConfiguration constructs a declarative configuration of the ClusterObjectDeploymentSpec type for use with +// apply. +func ClusterObjectDeploymentSpec() *ClusterObjectDeploymentSpecApplyConfiguration { + return &ClusterObjectDeploymentSpecApplyConfiguration{} +} + +// WithTemplate sets the Template 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 Template field is set to the value of the last call. +func (b *ClusterObjectDeploymentSpecApplyConfiguration) WithTemplate(value *ClusterObjectDeploymentTemplateApplyConfiguration) *ClusterObjectDeploymentSpecApplyConfiguration { + b.Template = value + return b +} + +// WithProgressDeadlineMinutes sets the ProgressDeadlineMinutes 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 ProgressDeadlineMinutes field is set to the value of the last call. +func (b *ClusterObjectDeploymentSpecApplyConfiguration) WithProgressDeadlineMinutes(value int32) *ClusterObjectDeploymentSpecApplyConfiguration { + b.ProgressDeadlineMinutes = &value + return b +} diff --git a/applyconfigurations/api/v1/clusterobjectdeploymentstatus.go b/applyconfigurations/api/v1/clusterobjectdeploymentstatus.go new file mode 100644 index 0000000000..3f5c1936b4 --- /dev/null +++ b/applyconfigurations/api/v1/clusterobjectdeploymentstatus.go @@ -0,0 +1,85 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen-v0.21. DO NOT EDIT. + +package v1 + +import ( + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// ClusterObjectDeploymentStatusApplyConfiguration represents a declarative configuration of the ClusterObjectDeploymentStatus type for use +// with apply. +// +// ClusterObjectDeploymentStatus summarizes owned active revisions and rollout progress. +type ClusterObjectDeploymentStatusApplyConfiguration struct { + // conditions describes installation health and progress for the observed generation. + // Available is True for one Ready active revision, False for none or one unready + // revision, and Unknown with reason Progressing for multiple active revisions. + // Progressing is True with reason NewClusterObjectSetProgressing while the desired + // rollout is incomplete, or False with reason NewClusterObjectSetProgressed after + // completion or ProgressDeadlineExceeded after a deadline expires. Completion + // remains recorded even if the completed revision later becomes unhealthy. + Conditions []metav1.ConditionApplyConfiguration `json:"conditions,omitempty"` + // activeRevisions summarizes owned active, non-deleting ClusterObjectSets, + // including a newly created revision, ordered by increasing int64 revision. + ActiveRevisions []ClusterObjectDeploymentRevisionApplyConfiguration `json:"activeRevisions,omitempty"` + // objectCounts reports counts from the highest owned active revision, rather than + // summing overlapping revisions. Omitted when that revision has not reported + // counts or no active revision exists. Available corresponds to the child's ready count. + ObjectCounts *ClusterObjectDeploymentObjectCountsApplyConfiguration `json:"objectCounts,omitempty"` +} + +// ClusterObjectDeploymentStatusApplyConfiguration constructs a declarative configuration of the ClusterObjectDeploymentStatus type for use with +// apply. +func ClusterObjectDeploymentStatus() *ClusterObjectDeploymentStatusApplyConfiguration { + return &ClusterObjectDeploymentStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *ClusterObjectDeploymentStatusApplyConfiguration) WithConditions(values ...*metav1.ConditionApplyConfiguration) *ClusterObjectDeploymentStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithActiveRevisions adds the given value to the ActiveRevisions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ActiveRevisions field. +func (b *ClusterObjectDeploymentStatusApplyConfiguration) WithActiveRevisions(values ...*ClusterObjectDeploymentRevisionApplyConfiguration) *ClusterObjectDeploymentStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithActiveRevisions") + } + b.ActiveRevisions = append(b.ActiveRevisions, *values[i]) + } + return b +} + +// WithObjectCounts sets the ObjectCounts 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 ObjectCounts field is set to the value of the last call. +func (b *ClusterObjectDeploymentStatusApplyConfiguration) WithObjectCounts(value *ClusterObjectDeploymentObjectCountsApplyConfiguration) *ClusterObjectDeploymentStatusApplyConfiguration { + b.ObjectCounts = value + return b +} diff --git a/applyconfigurations/api/v1/clusterobjectdeploymenttemplate.go b/applyconfigurations/api/v1/clusterobjectdeploymenttemplate.go new file mode 100644 index 0000000000..a880f933ff --- /dev/null +++ b/applyconfigurations/api/v1/clusterobjectdeploymenttemplate.go @@ -0,0 +1,54 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen-v0.21. DO NOT EDIT. + +package v1 + +// ClusterObjectDeploymentTemplateApplyConfiguration represents a declarative configuration of the ClusterObjectDeploymentTemplate type for use +// with apply. +// +// ClusterObjectDeploymentTemplate describes the caller-controlled part of a revision. +type ClusterObjectDeploymentTemplateApplyConfiguration struct { + // metadata contains labels and annotations copied onto each ClusterObjectSet, + // rather than onto the objects it manages. Root deployment metadata is not copied. + Metadata *ClusterObjectDeploymentTemplateMetadataApplyConfiguration `json:"metadata,omitempty"` + // spec contains the desired revision content. Group, revision, lifecycle state, + // and ownership are assigned by the controller and cannot be set here. + Spec *ClusterObjectDeploymentTemplateSpecApplyConfiguration `json:"spec,omitempty"` +} + +// ClusterObjectDeploymentTemplateApplyConfiguration constructs a declarative configuration of the ClusterObjectDeploymentTemplate type for use with +// apply. +func ClusterObjectDeploymentTemplate() *ClusterObjectDeploymentTemplateApplyConfiguration { + return &ClusterObjectDeploymentTemplateApplyConfiguration{} +} + +// WithMetadata sets the Metadata 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 Metadata field is set to the value of the last call. +func (b *ClusterObjectDeploymentTemplateApplyConfiguration) WithMetadata(value *ClusterObjectDeploymentTemplateMetadataApplyConfiguration) *ClusterObjectDeploymentTemplateApplyConfiguration { + b.Metadata = value + return b +} + +// WithSpec sets the Spec 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 Spec field is set to the value of the last call. +func (b *ClusterObjectDeploymentTemplateApplyConfiguration) WithSpec(value *ClusterObjectDeploymentTemplateSpecApplyConfiguration) *ClusterObjectDeploymentTemplateApplyConfiguration { + b.Spec = value + return b +} diff --git a/applyconfigurations/api/v1/clusterobjectdeploymenttemplatemetadata.go b/applyconfigurations/api/v1/clusterobjectdeploymenttemplatemetadata.go new file mode 100644 index 0000000000..21ff1f3f92 --- /dev/null +++ b/applyconfigurations/api/v1/clusterobjectdeploymenttemplatemetadata.go @@ -0,0 +1,71 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen-v0.21. DO NOT EDIT. + +package v1 + +import ( + apiv1 "github.com/operator-framework/operator-controller/api/v1" +) + +// ClusterObjectDeploymentTemplateMetadataApplyConfiguration represents a declarative configuration of the ClusterObjectDeploymentTemplateMetadata type for use +// with apply. +// +// ClusterObjectDeploymentTemplateMetadata contains bounded revision metadata. +type ClusterObjectDeploymentTemplateMetadataApplyConfiguration struct { + // labels contains up to 32 Kubernetes labels copied onto each revision. + // The olm.operatorframework.io/template-hash label is reserved for the controller. + Labels map[string]apiv1.ClusterObjectDeploymentLabelValue `json:"labels,omitempty"` + // annotations contains up to 32 Kubernetes annotations copied onto each revision. + // Each value and the aggregate size of all keys and values must fit within 256 KiB. + // Any controller-added annotations must also fit in that aggregate budget. + Annotations map[string]apiv1.ClusterObjectDeploymentAnnotationValue `json:"annotations,omitempty"` +} + +// ClusterObjectDeploymentTemplateMetadataApplyConfiguration constructs a declarative configuration of the ClusterObjectDeploymentTemplateMetadata type for use with +// apply. +func ClusterObjectDeploymentTemplateMetadata() *ClusterObjectDeploymentTemplateMetadataApplyConfiguration { + return &ClusterObjectDeploymentTemplateMetadataApplyConfiguration{} +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ClusterObjectDeploymentTemplateMetadataApplyConfiguration) WithLabels(entries map[string]apiv1.ClusterObjectDeploymentLabelValue) *ClusterObjectDeploymentTemplateMetadataApplyConfiguration { + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]apiv1.ClusterObjectDeploymentLabelValue, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ClusterObjectDeploymentTemplateMetadataApplyConfiguration) WithAnnotations(entries map[string]apiv1.ClusterObjectDeploymentAnnotationValue) *ClusterObjectDeploymentTemplateMetadataApplyConfiguration { + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]apiv1.ClusterObjectDeploymentAnnotationValue, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} diff --git a/applyconfigurations/api/v1/clusterobjectdeploymenttemplatespec.go b/applyconfigurations/api/v1/clusterobjectdeploymenttemplatespec.go new file mode 100644 index 0000000000..745f2b128e --- /dev/null +++ b/applyconfigurations/api/v1/clusterobjectdeploymenttemplatespec.go @@ -0,0 +1,82 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen-v0.21. DO NOT EDIT. + +package v1 + +import ( + apiv1 "github.com/operator-framework/operator-controller/api/v1" +) + +// ClusterObjectDeploymentTemplateSpecApplyConfiguration represents a declarative configuration of the ClusterObjectDeploymentTemplateSpec type for use +// with apply. +// +// ClusterObjectDeploymentTemplateSpec contains mutable ClusterObjectSet content. +type ClusterObjectDeploymentTemplateSpecApplyConfiguration struct { + // phases is an ordered list of 1 to 20 uniquely named phases. Each phase contains + // at most 50 objects using the existing inline object or immutable Secret reference + // representation. Phase, object, and probe order participate in template identity. + Phases []ClusterObjectSetPhaseApplyConfiguration `json:"phases,omitempty"` + // progressionProbes supplies custom readiness probes for objects in every phase. + // The existing ClusterObjectSet selector and assertion semantics apply. + ProgressionProbes []ProgressionProbeApplyConfiguration `json:"progressionProbes,omitempty"` + // collisionProtection is the default strategy for objects in the revision. + // Objects and phases may override it. When omitted the controller uses Prevent + // in the generated ClusterObjectSet. Omission remains distinct from explicit + // Prevent for template identity; admission does not default this field. + CollisionProtection *apiv1.CollisionProtection `json:"collisionProtection,omitempty"` +} + +// ClusterObjectDeploymentTemplateSpecApplyConfiguration constructs a declarative configuration of the ClusterObjectDeploymentTemplateSpec type for use with +// apply. +func ClusterObjectDeploymentTemplateSpec() *ClusterObjectDeploymentTemplateSpecApplyConfiguration { + return &ClusterObjectDeploymentTemplateSpecApplyConfiguration{} +} + +// WithPhases adds the given value to the Phases field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Phases field. +func (b *ClusterObjectDeploymentTemplateSpecApplyConfiguration) WithPhases(values ...*ClusterObjectSetPhaseApplyConfiguration) *ClusterObjectDeploymentTemplateSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithPhases") + } + b.Phases = append(b.Phases, *values[i]) + } + return b +} + +// WithProgressionProbes adds the given value to the ProgressionProbes field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ProgressionProbes field. +func (b *ClusterObjectDeploymentTemplateSpecApplyConfiguration) WithProgressionProbes(values ...*ProgressionProbeApplyConfiguration) *ClusterObjectDeploymentTemplateSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithProgressionProbes") + } + b.ProgressionProbes = append(b.ProgressionProbes, *values[i]) + } + return b +} + +// WithCollisionProtection sets the CollisionProtection 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 CollisionProtection field is set to the value of the last call. +func (b *ClusterObjectDeploymentTemplateSpecApplyConfiguration) WithCollisionProtection(value apiv1.CollisionProtection) *ClusterObjectDeploymentTemplateSpecApplyConfiguration { + b.CollisionProtection = &value + return b +} diff --git a/applyconfigurations/internal/internal.go b/applyconfigurations/internal/internal.go index 12d01d336f..4a708287a7 100644 --- a/applyconfigurations/internal/internal.go +++ b/applyconfigurations/internal/internal.go @@ -249,6 +249,130 @@ var schemaYAML = typed.YAMLObject(`types: - name: install type: namedType: com.github.operator-framework.operator-controller.api.v1.ClusterExtensionInstallStatus +- name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeployment + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + - name: spec + type: + namedType: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentSpec + - name: status + type: + namedType: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentStatus +- name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentAnnotationValue + scalar: string +- name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentLabelValue + scalar: string +- name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentObjectCounts + map: + fields: + - name: available + type: + scalar: numeric + - name: present + type: + scalar: numeric + - name: synced + type: + scalar: numeric + - name: total + type: + scalar: numeric +- name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentRevision + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type + - name: name + type: + scalar: string +- name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentSpec + map: + fields: + - name: progressDeadlineMinutes + type: + scalar: numeric + - name: template + type: + namedType: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentTemplate +- name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentStatus + map: + fields: + - name: activeRevisions + type: + list: + elementType: + namedType: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentRevision + elementRelationship: associative + keys: + - name + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type + - name: objectCounts + type: + namedType: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentObjectCounts +- name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentTemplate + map: + fields: + - name: metadata + type: + namedType: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentTemplateMetadata + - name: spec + type: + namedType: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentTemplateSpec +- name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentTemplateMetadata + map: + fields: + - name: annotations + type: + map: + elementType: + namedType: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentAnnotationValue + - name: labels + type: + map: + elementType: + namedType: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentLabelValue +- name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectDeploymentTemplateSpec + map: + fields: + - name: collisionProtection + type: + namedType: com.github.operator-framework.operator-controller.api.v1.CollisionProtection + - name: phases + type: + list: + elementType: + namedType: com.github.operator-framework.operator-controller.api.v1.ClusterObjectSetPhase + elementRelationship: associative + keys: + - name + - name: progressionProbes + type: + list: + elementType: + namedType: com.github.operator-framework.operator-controller.api.v1.ProgressionProbe + elementRelationship: atomic - name: com.github.operator-framework.operator-controller.api.v1.ClusterObjectSet map: fields: diff --git a/applyconfigurations/utils.go b/applyconfigurations/utils.go index b82ea41164..edf0d668a7 100644 --- a/applyconfigurations/utils.go +++ b/applyconfigurations/utils.go @@ -60,6 +60,22 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1.ClusterExtensionSpecApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("ClusterExtensionStatus"): return &apiv1.ClusterExtensionStatusApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ClusterObjectDeployment"): + return &apiv1.ClusterObjectDeploymentApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ClusterObjectDeploymentObjectCounts"): + return &apiv1.ClusterObjectDeploymentObjectCountsApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ClusterObjectDeploymentRevision"): + return &apiv1.ClusterObjectDeploymentRevisionApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ClusterObjectDeploymentSpec"): + return &apiv1.ClusterObjectDeploymentSpecApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ClusterObjectDeploymentStatus"): + return &apiv1.ClusterObjectDeploymentStatusApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ClusterObjectDeploymentTemplate"): + return &apiv1.ClusterObjectDeploymentTemplateApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ClusterObjectDeploymentTemplateMetadata"): + return &apiv1.ClusterObjectDeploymentTemplateMetadataApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ClusterObjectDeploymentTemplateSpec"): + return &apiv1.ClusterObjectDeploymentTemplateSpecApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("ClusterObjectSet"): return &apiv1.ClusterObjectSetApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("ClusterObjectSetObject"): diff --git a/docs/api-reference/olmv1-api-reference.md b/docs/api-reference/olmv1-api-reference.md index 5a87485a8e..079b87e992 100644 --- a/docs/api-reference/olmv1-api-reference.md +++ b/docs/api-reference/olmv1-api-reference.md @@ -13,6 +13,8 @@ Package v1 contains API Schema definitions for the olm v1 API group - [ClusterCatalogList](#clustercataloglist) - [ClusterExtension](#clusterextension) - [ClusterExtensionList](#clusterextensionlist) +- [ClusterObjectDeployment](#clusterobjectdeployment) +- [ClusterObjectDeploymentList](#clusterobjectdeploymentlist) @@ -384,6 +386,217 @@ _Appears in:_ | `activeRevisions` _[RevisionStatus](#revisionstatus) array_ | activeRevisions holds a list of currently active (non-archived) ClusterObjectSets,
including both installed and rolling out revisions.
**Experimental channel:** | | Optional: \{\}
| +#### ClusterObjectDeployment + + + +ClusterObjectDeployment manages mutable desired content through immutable +ClusterObjectSet revisions. It is an experimental, cluster-scoped API. + + + +_Appears in:_ +- [ClusterObjectDeploymentList](#clusterobjectdeploymentlist) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `olm.operatorframework.io/v1` | | | +| `kind` _string_ | `ClusterObjectDeployment` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | Optional: \{\}
| +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | Optional: \{\}
| +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[ClusterObjectDeploymentSpec](#clusterobjectdeploymentspec)_ | spec defines the desired deployment template and progress deadline. | | Required: \{\}
| +| `status` _[ClusterObjectDeploymentStatus](#clusterobjectdeploymentstatus)_ | status defines the observed state and is written through the status subresource. | | Optional: \{\}
| + + +#### ClusterObjectDeploymentAnnotationValue + +_Underlying type:_ _string_ + +ClusterObjectDeploymentAnnotationValue is a bounded Kubernetes annotation value. +The aggregate byte limit is validated on the containing annotations map. + +_Validation:_ +- MaxLength: 262144 + +_Appears in:_ +- [ClusterObjectDeploymentTemplateMetadata](#clusterobjectdeploymenttemplatemetadata) + + + +#### ClusterObjectDeploymentLabelValue + +_Underlying type:_ _string_ + +ClusterObjectDeploymentLabelValue is a bounded Kubernetes label value. +Its length bounds the cost of label validation. + +_Validation:_ +- MaxLength: 63 + +_Appears in:_ +- [ClusterObjectDeploymentTemplateMetadata](#clusterobjectdeploymenttemplatemetadata) + + + +#### ClusterObjectDeploymentList + + + +ClusterObjectDeploymentList contains ClusterObjectDeployment objects. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `olm.operatorframework.io/v1` | | | +| `kind` _string_ | `ClusterObjectDeploymentList` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | Optional: \{\}
| +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | Optional: \{\}
| +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `items` _[ClusterObjectDeployment](#clusterobjectdeployment) array_ | items is the list of deployments. | | Required: \{\}
| + + +#### ClusterObjectDeploymentObjectCounts + + + +ClusterObjectDeploymentObjectCounts reports object states in the highest active revision. + + + +_Appears in:_ +- [ClusterObjectDeploymentStatus](#clusterobjectdeploymentstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `total` _integer_ | total is the total number of objects in the revision. Its presence distinguishes
reported zero counts from an omitted objectCounts summary. | | Maximum: 1000
Minimum: 0
Required: \{\}
| +| `present` _integer_ | present is the number of objects found or created on the cluster. | | Maximum: 1000
Minimum: 0
Optional: \{\}
| +| `synced` _integer_ | synced is the number of objects matching their desired state. | | Maximum: 1000
Minimum: 0
Optional: \{\}
| +| `available` _integer_ | available is the number of synced objects passing their readiness assertions. | | Maximum: 1000
Minimum: 0
Optional: \{\}
| + + +#### ClusterObjectDeploymentRevision + + + +ClusterObjectDeploymentRevision summarizes the current conditions of an active revision. + + + +_Appears in:_ +- [ClusterObjectDeploymentStatus](#clusterobjectdeploymentstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is the name of the ClusterObjectSet. It may contain the full positive int64
revision suffix; it is not restricted to a uint32 suffix or a 63-character label. | | MaxLength: 253
MinLength: 1
Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
Required: \{\}
| +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | conditions contains the revision's current status conditions. | | Optional: \{\}
| + + +#### ClusterObjectDeploymentSpec + + + +ClusterObjectDeploymentSpec defines the mutable desired state of a deployment. + + + +_Appears in:_ +- [ClusterObjectDeployment](#clusterobjectdeployment) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `template` _[ClusterObjectDeploymentTemplate](#clusterobjectdeploymenttemplate)_ | template is the desired metadata and content of newly created ClusterObjectSets.
Changes to template create a new revision; existing revisions remain immutable. | | Required: \{\}
| +| `progressDeadlineMinutes` _integer_ | progressDeadlineMinutes is the maximum number of minutes without phase progress
before an incomplete rollout reports ProgressDeadlineExceeded. Omission disables
the deadline. A supplied value must be positive. Deadline changes do not create
revisions, and this value is not copied into the legacy ClusterObjectSet deadline. | | Minimum: 1
Optional: \{\}
| + + +#### ClusterObjectDeploymentStatus + + + +ClusterObjectDeploymentStatus summarizes owned active revisions and rollout progress. + + + +_Appears in:_ +- [ClusterObjectDeployment](#clusterobjectdeployment) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | conditions describes installation health and progress for the observed generation.
Available is True for one Ready active revision, False for none or one unready
revision, and Unknown with reason Progressing for multiple active revisions.
Progressing is True with reason NewClusterObjectSetProgressing while the desired
rollout is incomplete, or False with reason NewClusterObjectSetProgressed after
completion or ProgressDeadlineExceeded after a deadline expires. Completion
remains recorded even if the completed revision later becomes unhealthy. | | Optional: \{\}
| +| `activeRevisions` _[ClusterObjectDeploymentRevision](#clusterobjectdeploymentrevision) array_ | activeRevisions summarizes owned active, non-deleting ClusterObjectSets,
including a newly created revision, ordered by increasing int64 revision. | | Optional: \{\}
| +| `objectCounts` _[ClusterObjectDeploymentObjectCounts](#clusterobjectdeploymentobjectcounts)_ | objectCounts reports counts from the highest owned active revision, rather than
summing overlapping revisions. Omitted when that revision has not reported
counts or no active revision exists. Available corresponds to the child's ready count. | | Optional: \{\}
| + + +#### ClusterObjectDeploymentTemplate + + + +ClusterObjectDeploymentTemplate describes the caller-controlled part of a revision. + + + +_Appears in:_ +- [ClusterObjectDeploymentSpec](#clusterobjectdeploymentspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `metadata` _[ClusterObjectDeploymentTemplateMetadata](#clusterobjectdeploymenttemplatemetadata)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[ClusterObjectDeploymentTemplateSpec](#clusterobjectdeploymenttemplatespec)_ | spec contains the desired revision content. Group, revision, lifecycle state,
and ownership are assigned by the controller and cannot be set here. | | Required: \{\}
| + + +#### ClusterObjectDeploymentTemplateMetadata + + + +ClusterObjectDeploymentTemplateMetadata contains bounded revision metadata. + + + +_Appears in:_ +- [ClusterObjectDeploymentTemplate](#clusterobjectdeploymenttemplate) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `labels` _object (keys:string, values:[ClusterObjectDeploymentLabelValue](#clusterobjectdeploymentlabelvalue))_ | labels contains up to 32 Kubernetes labels copied onto each revision.
The olm.operatorframework.io/template-hash label is reserved for the controller. | | MaxProperties: 32
Optional: \{\}
| +| `annotations` _object (keys:string, values:[ClusterObjectDeploymentAnnotationValue](#clusterobjectdeploymentannotationvalue))_ | annotations contains up to 32 Kubernetes annotations copied onto each revision.
Each value and the aggregate size of all keys and values must fit within 256 KiB.
Any controller-added annotations must also fit in that aggregate budget. | | MaxProperties: 32
Optional: \{\}
| + + +#### ClusterObjectDeploymentTemplateSpec + + + +ClusterObjectDeploymentTemplateSpec contains mutable ClusterObjectSet content. + + + +_Appears in:_ +- [ClusterObjectDeploymentTemplate](#clusterobjectdeploymenttemplate) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `progressionProbes` _[ProgressionProbe](#progressionprobe) array_ | progressionProbes supplies custom readiness probes for objects in every phase.
The existing ClusterObjectSet selector and assertion semantics apply. | | MaxItems: 20
MinItems: 1
Optional: \{\}
| +| `collisionProtection` _[CollisionProtection](#collisionprotection)_ | collisionProtection is the default strategy for objects in the revision.
Objects and phases may override it. When omitted the controller uses Prevent
in the generated ClusterObjectSet. Omission remains distinct from explicit
Prevent for template identity; admission does not default this field. | | Enum: [Prevent IfNoController None]
Optional: \{\}
| + + +#### CollisionProtection + +_Underlying type:_ _string_ + +CollisionProtection specifies if and how ownership collisions are prevented. + + + +_Appears in:_ +- [ClusterObjectDeploymentTemplateSpec](#clusterobjectdeploymenttemplatespec) + +| Field | Description | +| --- | --- | +| `Prevent` | CollisionProtectionPrevent prevents owner collisions entirely
by only allowing to work with objects itself has created.
| +| `IfNoController` | CollisionProtectionIfNoController allows to patch and override
objects already present if they are not owned by another controller.
| +| `None` | CollisionProtectionNone allows to patch and override objects
already present and owned by other controllers.
Be careful! This setting may cause multiple controllers to fight over a resource,
causing load on the API server and etcd.
| #### ConditionEqualProbe @@ -513,6 +726,21 @@ _Appears in:_ | `FieldValue` | | +#### ProgressionProbe + + + +ProgressionProbe provides a custom probe definition, consisting of an object selection method and assertions. + + + +_Appears in:_ +- [ClusterObjectDeploymentTemplateSpec](#clusterobjectdeploymenttemplatespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `selector` _[ObjectSelector](#objectselector)_ | selector is a required field which defines the method by which we select objects to apply the below
assertions to. Any object which matches the defined selector will have all the associated assertions
applied against it.
If no objects within a phase are selected by the provided selector, then all assertions defined here
are considered to have succeeded.
**Experimental channel:** | | Required: \{\}
| +| `assertions` _[Assertion](#assertion) array_ | assertions is a required list of checks which will run against the objects selected by the selector. If
one or more assertions fail then the phase within which the object lives will be not be considered
'Ready', blocking rollout of all subsequent phases.
**Experimental channel:** | | MaxItems: 20
MinItems: 1
Required: \{\}
| #### ResolvedCatalogSource diff --git a/hack/tools/update-crds.sh b/hack/tools/update-crds.sh index c68dba4c15..175abe0fdd 100755 --- a/hack/tools/update-crds.sh +++ b/hack/tools/update-crds.sh @@ -8,11 +8,12 @@ set -e CE="olm.operatorframework.io_clusterextensions.yaml" CC="olm.operatorframework.io_clustercatalogs.yaml" CR="olm.operatorframework.io_clusterobjectsets.yaml" +COD="olm.operatorframework.io_clusterobjectdeployments.yaml" # order for modules and crds must match # each item in crds must be unique, and should be associated with a module -modules=("operator-controller" "catalogd" "object-controller") -crds=("${CE}" "${CC}" "${CR}") +modules=("operator-controller" "catalogd" "object-controller" "object-controller") +crds=("${CE}" "${CC}" "${CR}" "${COD}") # Channels must much those in the generator channels=("standard" "experimental") diff --git a/helm/olmv1/base/object-controller/crd/experimental/olm.operatorframework.io_clusterobjectdeployments.yaml b/helm/olmv1/base/object-controller/crd/experimental/olm.operatorframework.io_clusterobjectdeployments.yaml new file mode 100644 index 0000000000..834e484cb2 --- /dev/null +++ b/helm/olmv1/base/object-controller/crd/experimental/olm.operatorframework.io_clusterobjectdeployments.yaml @@ -0,0 +1,747 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.21.0 + olm.operatorframework.io/generator: experimental + name: clusterobjectdeployments.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterObjectDeployment + listKind: ClusterObjectDeploymentList + plural: clusterobjectdeployments + shortNames: + - cod + singular: clusterobjectdeployment + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + ClusterObjectDeployment manages mutable desired content through immutable + ClusterObjectSet revisions. It is an experimental, cluster-scoped API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired deployment template and progress + deadline. + properties: + progressDeadlineMinutes: + description: |- + progressDeadlineMinutes is the maximum number of minutes without phase progress + before an incomplete rollout reports ProgressDeadlineExceeded. Omission disables + the deadline. A supplied value must be positive. Deadline changes do not create + revisions, and this value is not copied into the legacy ClusterObjectSet deadline. + format: int32 + minimum: 1 + type: integer + template: + description: |- + template is the desired metadata and content of newly created ClusterObjectSets. + Changes to template create a new revision; existing revisions remain immutable. + properties: + metadata: + description: |- + metadata contains labels and annotations copied onto each ClusterObjectSet, + rather than onto the objects it manages. Root deployment metadata is not copied. + properties: + annotations: + additionalProperties: + description: |- + ClusterObjectDeploymentAnnotationValue is a bounded Kubernetes annotation value. + The aggregate byte limit is validated on the containing annotations map. + maxLength: 262144 + type: string + description: |- + annotations contains up to 32 Kubernetes annotations copied onto each revision. + Each value and the aggregate size of all keys and values must fit within 256 KiB. + Any controller-added annotations must also fit in that aggregate budget. + maxProperties: 32 + type: object + x-kubernetes-validations: + - message: annotation keys must be valid Kubernetes qualified + names + rule: self.all(k, !format.qualifiedName().validate(k.lowerAscii()).hasValue()) + - message: annotation values must not exceed 256 KiB + rule: self.all(k, bytes(self[k]).size() <= 262144) + - message: annotation keys and values must not exceed 256 + KiB in total + rule: self.map(k, bytes(k).size() + bytes(self[k]).size()).sum() + <= 262144 + labels: + additionalProperties: + description: |- + ClusterObjectDeploymentLabelValue is a bounded Kubernetes label value. + Its length bounds the cost of label validation. + maxLength: 63 + type: string + description: |- + labels contains up to 32 Kubernetes labels copied onto each revision. + The olm.operatorframework.io/template-hash label is reserved for the controller. + maxProperties: 32 + type: object + x-kubernetes-validations: + - message: label keys must be valid Kubernetes qualified names + rule: self.all(k, !format.qualifiedName().validate(k).hasValue()) + - message: label values must be valid Kubernetes label values + rule: self.all(k, !format.labelValue().validate(self[k]).hasValue()) + - message: olm.operatorframework.io/template-hash is reserved + for the controller + rule: '!(''olm.operatorframework.io/template-hash'' in self)' + type: object + spec: + description: |- + spec contains the desired revision content. Group, revision, lifecycle state, + and ownership are assigned by the controller and cannot be set here. + properties: + collisionProtection: + description: |- + collisionProtection is the default strategy for objects in the revision. + Objects and phases may override it. When omitted the controller uses Prevent + in the generated ClusterObjectSet. Omission remains distinct from explicit + Prevent for template identity; admission does not default this field. + enum: + - Prevent + - IfNoController + - None + type: string + phases: + description: |- + phases is an ordered list of 1 to 20 uniquely named phases. Each phase contains + at most 50 objects using the existing inline object or immutable Secret reference + representation. Phase, object, and probe order participate in template identity. + items: + description: |- + ClusterObjectSetPhase represents a group of objects that are applied together. The phase is considered + complete only after all objects pass their status probes. + properties: + collisionProtection: + description: |- + collisionProtection specifies the default collision protection strategy for all objects + in this phase. Individual objects can override this value. + + When set, this value is used as the default for any object in this phase that does not + explicitly specify its own collisionProtection. + + When omitted, we use .spec.collistionProtection as the default for any object in this phase that does not + explicitly specify its own collisionProtection. + enum: + - Prevent + - IfNoController + - None + type: string + name: + description: |- + name is a required identifier for this phase. + + phase names must follow the DNS label standard as defined in [RFC 1123]. + They must contain only lowercase alphanumeric characters or hyphens (-), + start and end with an alphanumeric character, and be no longer than 63 characters. + + Common phase names include: namespaces, policies, rbac, crds, storage, deploy, publish. + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: the value must consist of only lowercase + alphanumeric characters and hyphens, and must start + and end with an alphanumeric character. + rule: '!format.dns1123Label().validate(self).hasValue()' + objects: + description: |- + objects is a required list of all Kubernetes objects that belong to this phase. + + All objects in this list are applied to the cluster in no particular order. The maximum number of objects per phase is 50. + items: + description: |- + ClusterObjectSetObject represents a Kubernetes object to be applied as part + of a phase, along with its collision protection settings. + + Exactly one of object or ref must be set. + properties: + collisionProtection: + description: |- + collisionProtection controls whether the operator can adopt and modify objects + that already exist on the cluster. + + Allowed values are: "Prevent", "IfNoController", and "None". + + When set to "Prevent", the operator only manages objects it created itself. + This prevents ownership collisions. + + When set to "IfNoController", the operator can adopt and modify pre-existing objects + that are not owned by another controller. + This is useful for taking over management of manually-created resources. + + When set to "None", the operator can adopt and modify any pre-existing object, even if + owned by another controller. + Use this setting with extreme caution as it may cause multiple controllers to fight over + the same resource, resulting in increased load on the API server and etcd. + + When omitted, the value is inherited from the phase, then spec. + enum: + - Prevent + - IfNoController + - None + type: string + object: + description: |- + object is an optional embedded Kubernetes object to be applied. + + Exactly one of object or ref must be set. + + This object must be a valid Kubernetes resource with apiVersion, kind, and metadata fields. + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + ref: + description: |- + ref is an optional reference to a Secret that holds the serialized + object manifest. + + Exactly one of object or ref must be set. + properties: + key: + description: |- + key is the data key within the referenced Secret containing the + object manifest content. The value at this key must be a + JSON-serialized Kubernetes object manifest. + maxLength: 253 + minLength: 1 + type: string + name: + description: name is the name of the referenced + Secret. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace of the referenced Secret. + When empty, defaults to the OLM system namespace during ref resolution. + maxLength: 63 + type: string + required: + - key + - name + type: object + type: object + x-kubernetes-validations: + - message: exactly one of the fields in [object ref] + must be set + rule: '[has(self.object),has(self.ref)].filter(x,x==true).size() + == 1' + maxItems: 50 + type: array + required: + - name + - objects + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + progressionProbes: + description: |- + progressionProbes supplies custom readiness probes for objects in every phase. + The existing ClusterObjectSet selector and assertion semantics apply. + items: + description: ProgressionProbe provides a custom probe definition, + consisting of an object selection method and assertions. + properties: + assertions: + description: |- + assertions is a required list of checks which will run against the objects selected by the selector. If + one or more assertions fail then the phase within which the object lives will be not be considered + 'Ready', blocking rollout of all subsequent phases. + items: + description: Assertion is a discriminated union which + defines the probe type and definition used as an + assertion. + properties: + conditionEqual: + description: conditionEqual contains the expected + condition type and status. + properties: + status: + description: |- + status sets the expected condition status. + + Allowed values are "True" and "False". + enum: + - "True" + - "False" + type: string + type: + description: type sets the expected condition + type, i.e. "Ready". + maxLength: 200 + minLength: 1 + type: string + required: + - status + - type + type: object + fieldValue: + description: fieldValue contains the expected + field path and value found within. + properties: + fieldPath: + description: |- + fieldPath sets the field path for the field to check, i.e. "status.phase". The probe will fail + if the path does not exist. + maxLength: 200 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must contain a valid field path. + valid fields contain upper or lower-case + alphanumeric characters separated by the + "." character. + rule: self.matches('^[a-zA-Z0-9]+(?:\\.[a-zA-Z0-9]+)*$') + value: + description: value sets the expected value + found at fieldPath, i.e. "Bound". + maxLength: 200 + minLength: 1 + type: string + required: + - fieldPath + - value + type: object + fieldsEqual: + description: fieldsEqual contains the two field + paths whose values are expected to match. + properties: + fieldA: + description: |- + fieldA sets the field path for the first field, i.e. "spec.replicas". The probe will fail + if the path does not exist. + maxLength: 200 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must contain a valid field path. + valid fields contain upper or lower-case + alphanumeric characters separated by the + "." character. + rule: self.matches('^[a-zA-Z0-9]+(?:\\.[a-zA-Z0-9]+)*$') + fieldB: + description: |- + fieldB sets the field path for the second field, i.e. "status.readyReplicas". The probe will fail + if the path does not exist. + maxLength: 200 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must contain a valid field path. + valid fields contain upper or lower-case + alphanumeric characters separated by the + "." character. + rule: self.matches('^[a-zA-Z0-9]+(?:\\.[a-zA-Z0-9]+)*$') + required: + - fieldA + - fieldB + type: object + type: + description: |- + type is a required field which specifies the type of probe to use. + + The allowed probe types are "ConditionEqual", "FieldsEqual", and "FieldValue". + + When set to "ConditionEqual", the probe checks objects that have reached a condition of specified type and status. + When set to "FieldsEqual", the probe checks that the values found at two provided field paths are matching. + When set to "FieldValue", the probe checks that the value found at the provided field path matches what was specified. + enum: + - ConditionEqual + - FieldsEqual + - FieldValue + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: conditionEqual is required when type is + ConditionEqual, and forbidden otherwise + rule: 'self.type == ''ConditionEqual'' ?has(self.conditionEqual) + : !has(self.conditionEqual)' + - message: fieldsEqual is required when type is FieldsEqual, + and forbidden otherwise + rule: 'self.type == ''FieldsEqual'' ?has(self.fieldsEqual) + : !has(self.fieldsEqual)' + - message: fieldValue is required when type is FieldValue, + and forbidden otherwise + rule: 'self.type == ''FieldValue'' ?has(self.fieldValue) + : !has(self.fieldValue)' + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + selector: + description: |- + selector is a required field which defines the method by which we select objects to apply the below + assertions to. Any object which matches the defined selector will have all the associated assertions + applied against it. + + If no objects within a phase are selected by the provided selector, then all assertions defined here + are considered to have succeeded. + properties: + groupKind: + description: |- + groupKind specifies the group and kind of objects to select. + + Required when type is "GroupKind". + + Uses the Kubernetes format specified here: + https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#GroupKind + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + label: + description: |- + label is the label selector definition. + + Required when type is "Label". + + A probe using a Label selector will be executed against every object matching the labels or expressions; you must use care + when using this type of selector. For example, if multiple Kind objects are selected via labels then the probe is + likely to fail because the values of different Kind objects rarely share the same schema. + + The LabelSelector field uses the following Kubernetes format: + https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#LabelSelector + Requires exactly one of matchLabels or matchExpressions. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: exactly one of matchLabels or matchExpressions + must be set + rule: (has(self.matchExpressions) && !has(self.matchLabels)) + || (!has(self.matchExpressions) && has(self.matchLabels)) + type: + description: |- + type is a required field which specifies the type of selector to use. + + The allowed selector types are "GroupKind" and "Label". + + When set to "GroupKind", all objects which match the specified group and kind will be selected. + When set to "Label", all objects which match the specified labels and/or expressions will be selected. + enum: + - GroupKind + - Label + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: groupKind is required when type is GroupKind, + and forbidden otherwise + rule: 'self.type == ''GroupKind'' ?has(self.groupKind) + : !has(self.groupKind)' + - message: label is required when type is Label, and + forbidden otherwise + rule: 'self.type == ''Label'' ?has(self.label) : !has(self.label)' + required: + - assertions + - selector + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - phases + type: object + required: + - spec + type: object + required: + - template + type: object + status: + description: status defines the observed state and is written through + the status subresource. + properties: + activeRevisions: + description: |- + activeRevisions summarizes owned active, non-deleting ClusterObjectSets, + including a newly created revision, ordered by increasing int64 revision. + items: + description: ClusterObjectDeploymentRevision summarizes the current + conditions of an active revision. + properties: + conditions: + description: conditions contains the revision's current status + conditions. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: |- + name is the name of the ClusterObjectSet. It may contain the full positive int64 + revision suffix; it is not restricted to a uint32 suffix or a 63-character label. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + conditions: + description: |- + conditions describes installation health and progress for the observed generation. + Available is True for one Ready active revision, False for none or one unready + revision, and Unknown with reason Progressing for multiple active revisions. + Progressing is True with reason NewClusterObjectSetProgressing while the desired + rollout is incomplete, or False with reason NewClusterObjectSetProgressed after + completion or ProgressDeadlineExceeded after a deadline expires. Completion + remains recorded even if the completed revision later becomes unhealthy. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + objectCounts: + description: |- + objectCounts reports counts from the highest owned active revision, rather than + summing overlapping revisions. Omitted when that revision has not reported + counts or no active revision exists. Available corresponds to the child's ready count. + properties: + available: + description: available is the number of synced objects passing + their readiness assertions. + format: int64 + maximum: 1000 + minimum: 0 + type: integer + present: + description: present is the number of objects found or created + on the cluster. + format: int64 + maximum: 1000 + minimum: 0 + type: integer + synced: + description: synced is the number of objects matching their desired + state. + format: int64 + maximum: 1000 + minimum: 0 + type: integer + total: + description: |- + total is the total number of objects in the revision. Its presence distinguishes + reported zero counts from an omitted objectCounts summary. + format: int64 + maximum: 1000 + minimum: 0 + type: integer + required: + - total + type: object + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: name must be 1 to 52 lowercase alphanumeric or hyphen characters, + starting with a letter and ending with an alphanumeric character + rule: size(self.metadata.name) <= 52 && self.metadata.name.matches('^[a-z]([a-z0-9-]*[a-z0-9])?$') + served: true + storage: true + subresources: + status: {}