diff --git a/cmd/object-controller/main.go b/cmd/object-controller/main.go index 9dcc9e50bd..7f9e09e8ea 100644 --- a/cmd/object-controller/main.go +++ b/cmd/object-controller/main.go @@ -152,7 +152,10 @@ func newManager(cfg *config, restConfig *rest.Config) (manager.Manager, error) { RenewDeadline: ptr.To(107 * time.Second), RetryPeriod: ptr.To(26 * time.Second), Cache: cache.Options{ - ByObject: map[client.Object]cache.ByObject{&ocv1.ClusterObjectSet{}: {}}, + ByObject: map[client.Object]cache.ByObject{ + &ocv1.ClusterObjectSet{}: {}, + &ocv1.ClusterObjectDeployment{}: {}, + }, ReaderFailOnMissingInformer: true, DefaultTransform: cacheutil.StripAnnotations(), }, @@ -197,6 +200,11 @@ func newManager(cfg *config, restConfig *rest.Config) (manager.Manager, error) { }).SetupWithManager(mgr); err != nil { return nil, fmt.Errorf("setting up ClusterObjectSet controller: %w", err) } + if err := (&controllers.ClusterObjectDeploymentReconciler{ + Client: mgr.GetClient(), + }).SetupWithManager(mgr); err != nil { + return nil, fmt.Errorf("setting up ClusterObjectDeployment controller: %w", err) + } if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { return nil, err } diff --git a/helm/olmv1/templates/crds/customresourcedefinition-clusterobjectdeployments.olm.operatorframework.io.yml b/helm/olmv1/templates/crds/customresourcedefinition-clusterobjectdeployments.olm.operatorframework.io.yml new file mode 100644 index 0000000000..f89eeb4039 --- /dev/null +++ b/helm/olmv1/templates/crds/customresourcedefinition-clusterobjectdeployments.olm.operatorframework.io.yml @@ -0,0 +1,3 @@ +{{- if include "objectController.enabled" . }} +{{ tpl (.Files.Get "base/object-controller/crd/experimental/olm.operatorframework.io_clusterobjectdeployments.yaml") . }} +{{- end }} diff --git a/internal/object-controller/controllers/clusterobjectdeployment_controller.go b/internal/object-controller/controllers/clusterobjectdeployment_controller.go new file mode 100644 index 0000000000..220f74fb4f --- /dev/null +++ b/internal/object-controller/controllers/clusterobjectdeployment_controller.go @@ -0,0 +1,82 @@ +//go:build !standard + +package controllers + +import ( + "context" + + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +// ClusterObjectDeploymentReconciler reconciles ClusterObjectDeployment objects by +// managing their owned ClusterObjectSet revisions. +// +// This is a skeleton: it does not yet implement any reconciliation behavior. +type ClusterObjectDeploymentReconciler struct { + Client client.Client +} + +//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterobjectdeployments,verbs=get;list;watch;update;patch;create;delete +//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterobjectdeployments/status,verbs=update;patch +//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterobjectdeployments/finalizers,verbs=update +//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterobjectsets,verbs=get;list;watch;update;patch;create;delete + +func (c *ClusterObjectDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + l := log.FromContext(ctx).WithName("cluster-object-deployment") + ctx = log.IntoContext(ctx, l) + + existingDeployment := &ocv1.ClusterObjectDeployment{} + if err := c.Client.Get(ctx, req.NamespacedName, existingDeployment); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + l.Info("reconcile starting") + defer l.Info("reconcile ending") + + // No reconciliation behavior yet. This skeleton establishes the controller, + // its watches, and its RBAC so that behavior can be layered on in follow-ups. + + return ctrl.Result{}, nil +} + +func (c *ClusterObjectDeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For( + &ocv1.ClusterObjectDeployment{}, + builder.WithPredicates( + predicate.ResourceVersionChangedPredicate{}, + ), + ). + Owns( + &ocv1.ClusterObjectSet{}, + builder.WithPredicates( + predicate.ResourceVersionChangedPredicate{}, + ), + ). + // A ClusterObjectSet's spec.group matches the name of the ClusterObjectDeployment + // that owns (or should adopt) it. Owns() only enqueues already-owned revisions, so + // this watch additionally surfaces not-yet-adopted (orphan) ClusterObjectSets. + Watches( + &ocv1.ClusterObjectSet{}, + handler.EnqueueRequestsFromMapFunc(mapClusterObjectSetToClusterObjectDeployment), + ). + Complete(c) +} + +// mapClusterObjectSetToClusterObjectDeployment enqueues the ClusterObjectDeployment +// named by a ClusterObjectSet's spec.group. +func mapClusterObjectSetToClusterObjectDeployment(_ context.Context, obj client.Object) []ctrl.Request { + cos, ok := obj.(*ocv1.ClusterObjectSet) + if !ok || cos.Spec.Group == "" { + return nil + } + return []ctrl.Request{{NamespacedName: types.NamespacedName{Name: cos.Spec.Group}}} +} diff --git a/internal/object-controller/controllers/clusterobjectdeployment_controller_internal_test.go b/internal/object-controller/controllers/clusterobjectdeployment_controller_internal_test.go new file mode 100644 index 0000000000..6727f7ba79 --- /dev/null +++ b/internal/object-controller/controllers/clusterobjectdeployment_controller_internal_test.go @@ -0,0 +1,49 @@ +//go:build !standard + +package controllers + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +func Test_mapClusterObjectSetToClusterObjectDeployment(t *testing.T) { + for _, tc := range []struct { + name string + obj client.Object + want []ctrl.Request + }{ + { + name: "maps spec.group to a ClusterObjectDeployment request", + obj: &ocv1.ClusterObjectSet{ + ObjectMeta: metav1.ObjectMeta{Name: "cod-1-1"}, + Spec: ocv1.ClusterObjectSetSpec{Group: "cod-1"}, + }, + want: []ctrl.Request{{NamespacedName: types.NamespacedName{Name: "cod-1"}}}, + }, + { + name: "returns nil when spec.group is empty", + obj: &ocv1.ClusterObjectSet{ + ObjectMeta: metav1.ObjectMeta{Name: "cod-1-1"}, + }, + want: nil, + }, + { + name: "returns nil for a non-ClusterObjectSet object", + obj: &ocv1.ClusterObjectDeployment{ObjectMeta: metav1.ObjectMeta{Name: "cod-1"}}, + want: nil, + }, + } { + t.Run(tc.name, func(t *testing.T) { + got := mapClusterObjectSetToClusterObjectDeployment(t.Context(), tc.obj) + require.Equal(t, tc.want, got) + }) + } +} diff --git a/internal/object-controller/controllers/clusterobjectdeployment_controller_test.go b/internal/object-controller/controllers/clusterobjectdeployment_controller_test.go new file mode 100644 index 0000000000..0ac2b18198 --- /dev/null +++ b/internal/object-controller/controllers/clusterobjectdeployment_controller_test.go @@ -0,0 +1,60 @@ +//go:build !standard + +package controllers_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/object-controller/controllers" +) + +const clusterObjectDeploymentName = "test-cod-1" + +func Test_ClusterObjectDeploymentReconciler_Reconcile(t *testing.T) { + testScheme := newScheme(t) + + for _, tc := range []struct { + name string + existingObjs []client.Object + }{ + { + name: "returns no error when the ClusterObjectDeployment does not exist", + existingObjs: nil, + }, + { + name: "returns no error for an existing ClusterObjectDeployment", + existingObjs: []client.Object{ + &ocv1.ClusterObjectDeployment{ + ObjectMeta: metav1.ObjectMeta{Name: clusterObjectDeploymentName}, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + testClient := fake.NewClientBuilder(). + WithScheme(testScheme). + WithStatusSubresource(&ocv1.ClusterObjectDeployment{}). + WithObjects(tc.existingObjs...). + Build() + + result, err := (&controllers.ClusterObjectDeploymentReconciler{ + Client: testClient, + }).Reconcile(t.Context(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: clusterObjectDeploymentName, + }, + }) + + require.NoError(t, err) + require.Equal(t, ctrl.Result{}, result) + }) + } +} diff --git a/internal/object-controller/scheme/scheme.go b/internal/object-controller/scheme/scheme.go index 33a4a965d2..379f967b7f 100644 --- a/internal/object-controller/scheme/scheme.go +++ b/internal/object-controller/scheme/scheme.go @@ -16,6 +16,9 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(Scheme)) utilruntime.Must(apiextensionsv1.AddToScheme(Scheme)) // Object management does not require the ClusterExtension or ClusterCatalog APIs. - Scheme.AddKnownTypes(ocv1.GroupVersion, &ocv1.ClusterObjectSet{}, &ocv1.ClusterObjectSetList{}) + Scheme.AddKnownTypes(ocv1.GroupVersion, + &ocv1.ClusterObjectSet{}, &ocv1.ClusterObjectSetList{}, + &ocv1.ClusterObjectDeployment{}, &ocv1.ClusterObjectDeploymentList{}, + ) metav1.AddToGroupVersion(Scheme, ocv1.GroupVersion) } diff --git a/manifests/experimental-e2e.yaml b/manifests/experimental-e2e.yaml index 049642934e..355dc36eef 100644 --- a/manifests/experimental-e2e.yaml +++ b/manifests/experimental-e2e.yaml @@ -1409,6 +1409,567 @@ spec: subresources: status: {} --- +# Source: olmv1/templates/crds/customresourcedefinition-clusterobjectdeployments.olm.operatorframework.io.yml +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=='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. + properties: + template: + description: |- + template is the desired content of newly created ClusterObjectSets. + Changes to template create a new revision; existing revisions remain immutable. + properties: + 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: + conditions: + description: |- + conditions describes rollout progress for the observed generation. + Progressing is True with reason NewClusterObjectSetProgressing while the desired + rollout is incomplete, or False with reason NewClusterObjectSetProgressed after + completion. 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 + 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: {} +--- # Source: olmv1/templates/crds/customresourcedefinition-clusterobjectsets.olm.operatorframework.io.yml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/manifests/experimental.yaml b/manifests/experimental.yaml index e408924903..29028f1ec7 100644 --- a/manifests/experimental.yaml +++ b/manifests/experimental.yaml @@ -1370,6 +1370,567 @@ spec: subresources: status: {} --- +# Source: olmv1/templates/crds/customresourcedefinition-clusterobjectdeployments.olm.operatorframework.io.yml +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=='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. + properties: + template: + description: |- + template is the desired content of newly created ClusterObjectSets. + Changes to template create a new revision; existing revisions remain immutable. + properties: + 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: + conditions: + description: |- + conditions describes rollout progress for the observed generation. + Progressing is True with reason NewClusterObjectSetProgressing while the desired + rollout is incomplete, or False with reason NewClusterObjectSetProgressed after + completion. 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 + 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: {} +--- # Source: olmv1/templates/crds/customresourcedefinition-clusterobjectsets.olm.operatorframework.io.yml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition