diff --git a/openapi/generated_openapi/zz_generated.openapi.go b/openapi/generated_openapi/zz_generated.openapi.go index 8b04d7f67a1..c717725d5c1 100644 --- a/openapi/generated_openapi/zz_generated.openapi.go +++ b/openapi/generated_openapi/zz_generated.openapi.go @@ -1234,7 +1234,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/openshift/api/operator/v1alpha1.ClusterAPI": schema_openshift_api_operator_v1alpha1_ClusterAPI(ref), "github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerComponent": schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerComponent(ref), "github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerComponentImage": schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerComponentImage(ref), + "github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerComponentSource": schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerComponentSource(ref), "github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerRevision": schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerRevision(ref), + "github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerRevisionManifestSubstitution": schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerRevisionManifestSubstitution(ref), "github.com/openshift/api/operator/v1alpha1.ClusterAPIList": schema_openshift_api_operator_v1alpha1_ClusterAPIList(ref), "github.com/openshift/api/operator/v1alpha1.ClusterAPISpec": schema_openshift_api_operator_v1alpha1_ClusterAPISpec(ref), "github.com/openshift/api/operator/v1alpha1.ClusterAPIStatus": schema_openshift_api_operator_v1alpha1_ClusterAPIStatus(ref), @@ -63176,6 +63178,13 @@ func schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerComponent(ref com Description: "ClusterAPIInstallerComponent defines a component which will be installed by this revision.", Type: []string{"object"}, Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the human-readable name of the component. The value has no effect, and will not be set if the component does not define a name in its manifests. If set it must consist of alphanumeric characters, or '-', and may not exceed 255 characters.", + Type: []string{"string"}, + Format: "", + }, + }, "type": { SchemaProps: spec.SchemaProps{ Description: "type is the source type of the component. The only valid value is Image. When set to Image, the image field must be set and will define an image source for the component.\n\nPossible enum values:\n - `\"Image\"` is an image source for a component.", @@ -63240,6 +63249,49 @@ func schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerComponentImage(re } } +func schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerComponentSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClusterAPIInstallerComponentSource defines the source of a component which will be installed by this revision.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type is the source type of the component. The only valid value is Image. When set to Image, the image field must be set and will define an image source for the component.\n\nPossible enum values:\n - `\"Image\"` is an image source for a component.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Image"}, + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "image defines an image source for a component. The image must contain a /capi-operator-installer directory containing the component manifests.", + Default: map[string]interface{}{}, + Ref: ref("github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerComponentImage"), + }, + }, + }, + Required: []string{"type"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-unions": []interface{}{ + map[string]interface{}{ + "discriminator": "type", + "fields-to-discriminateBy": map[string]interface{}{ + "image": "Image", + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerComponentImage"}, + } +} + func schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerRevision(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -63287,6 +63339,28 @@ func schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerRevision(ref comm }, }, }, + "manifestSubstitutions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "key", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "manifestSubstitutions is a list of envsubst style substitutions which will be applied to manifests in the revision during rendering. If defined it must not be empty, and may not contain more than 32 items. Each manifest substitution must have a unique key.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerRevisionManifestSubstitution"), + }, + }, + }, + }, + }, "components": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -63316,7 +63390,35 @@ func schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerRevision(ref comm }, }, Dependencies: []string{ - "github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerComponent"}, + "github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerComponent", "github.com/openshift/api/operator/v1alpha1.ClusterAPIInstallerRevisionManifestSubstitution"}, + } +} + +func schema_openshift_api_operator_v1alpha1_ClusterAPIInstallerRevisionManifestSubstitution(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClusterAPIInstallerRevisionManifestSubstitution defines an envsubst style substitution which will be applied to manifests in a revision during rendering.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the name of the envsubst variable to substitute. It must be a valid envsubst variable name, consisting of letters, digits, and underscores, and must start with a letter or underscore. The key must not be empty, and must not exceed 255 characters.", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "value is the value to substitute for the envsubst variable. It may be empty, in which case the variable will be substituted with an empty string. The value must not exceed 4096 characters.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"key", "value"}, + }, + }, } } @@ -63444,6 +63546,13 @@ func schema_openshift_api_operator_v1alpha1_ClusterAPIStatus(ref common.Referenc }, }, }, + "observedRevisionGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "observedRevisionGeneration is the generation of the ClusterAPI object that was last observed by the revision controller. If specified it must be greater than or equal to 1, and less than 2^53. It may not decrease or be unset once set.", + Type: []string{"integer"}, + Format: "int64", + }, + }, }, Required: []string{"desiredRevision", "revisions"}, }, diff --git a/openapi/openapi.json b/openapi/openapi.json index d6d29a4264d..af0985b657e 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -48288,6 +48288,10 @@ "default": {}, "$ref": "#/definitions/com.github.openshift.api.operator.v1alpha1.ClusterAPIInstallerComponentImage" }, + "name": { + "description": "name is the human-readable name of the component. The value has no effect, and will not be set if the component does not define a name in its manifests. If set it must consist of alphanumeric characters, or '-', and may not exceed 255 characters.", + "type": "string" + }, "type": { "description": "type is the source type of the component. The only valid value is Image. When set to Image, the image field must be set and will define an image source for the component.\n\nPossible enum values:\n - `\"Image\"` is an image source for a component.", "type": "string", @@ -48323,6 +48327,35 @@ } } }, + "com.github.openshift.api.operator.v1alpha1.ClusterAPIInstallerComponentSource": { + "description": "ClusterAPIInstallerComponentSource defines the source of a component which will be installed by this revision.", + "type": "object", + "required": [ + "type" + ], + "properties": { + "image": { + "description": "image defines an image source for a component. The image must contain a /capi-operator-installer directory containing the component manifests.", + "default": {}, + "$ref": "#/definitions/com.github.openshift.api.operator.v1alpha1.ClusterAPIInstallerComponentImage" + }, + "type": { + "description": "type is the source type of the component. The only valid value is Image. When set to Image, the image field must be set and will define an image source for the component.\n\nPossible enum values:\n - `\"Image\"` is an image source for a component.", + "type": "string", + "enum": [ + "Image" + ] + } + }, + "x-kubernetes-unions": [ + { + "discriminator": "type", + "fields-to-discriminateBy": { + "image": "Image" + } + } + ] + }, "com.github.openshift.api.operator.v1alpha1.ClusterAPIInstallerRevision": { "type": "object", "required": [ @@ -48344,6 +48377,15 @@ "description": "contentID uniquely identifies the content of this revision. The contentID must be between 1 and 255 characters long.", "type": "string" }, + "manifestSubstitutions": { + "description": "manifestSubstitutions is a list of envsubst style substitutions which will be applied to manifests in the revision during rendering. If defined it must not be empty, and may not contain more than 32 items. Each manifest substitution must have a unique key.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/com.github.openshift.api.operator.v1alpha1.ClusterAPIInstallerRevisionManifestSubstitution" + }, + "x-kubernetes-list-type": "atomic" + }, "name": { "description": "name is the name of a revision.", "type": "string" @@ -48365,6 +48407,24 @@ }, "x-kubernetes-map-type": "atomic" }, + "com.github.openshift.api.operator.v1alpha1.ClusterAPIInstallerRevisionManifestSubstitution": { + "description": "ClusterAPIInstallerRevisionManifestSubstitution defines an envsubst style substitution which will be applied to manifests in a revision during rendering.", + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "description": "key is the name of the envsubst variable to substitute. It must be a valid envsubst variable name, consisting of letters, digits, and underscores, and must start with a letter or underscore. The key must not be empty, and must not exceed 255 characters.", + "type": "string" + }, + "value": { + "description": "value is the value to substitute for the envsubst variable. It may be empty, in which case the variable will be substituted with an empty string. The value must not exceed 4096 characters.", + "type": "string" + } + } + }, "com.github.openshift.api.operator.v1alpha1.ClusterAPIList": { "description": "ClusterAPIList contains a list of ClusterAPI configurations\n\nCompatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support.", "type": "object", @@ -48427,6 +48487,11 @@ "description": "desiredRevision is the name of the desired revision. It is written by the revision controller. It must be set to the name of the entry in the revisions list with the highest revision number.", "type": "string" }, + "observedRevisionGeneration": { + "description": "observedRevisionGeneration is the generation of the ClusterAPI object that was last observed by the revision controller. If specified it must be greater than or equal to 1. It may not decrease or be unset once set.", + "type": "integer", + "format": "int64" + }, "revisions": { "description": "revisions is a list of all currently active revisions. A revision is active until the installer controller updates currentRevision to a later revision. It is written by the revision controller.\n\nThe maximum number of revisions is 16. All revisions must have a unique name. All revisions must have a unique revision number. When adding a revision, the revision number must be greater than the highest revision number in the list. Revisions are immutable, although they can be deleted.", "type": "array", diff --git a/operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml b/operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml index 7b279a6322d..4d962612201 100644 --- a/operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml +++ b/operator/v1alpha1/tests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml @@ -649,3 +649,760 @@ tests: image: ref: quay.io/openshift/cluster-api@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb profile: default + + # ManifestSubstitution key validation tests + + - name: Should accept valid envsubst key in manifestSubstitutions + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: EXP_BOOTSTRAP_FORMAT_IGNITION + value: "true" + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: EXP_BOOTSTRAP_FORMAT_IGNITION + value: "true" + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + + - name: Should accept envsubst key starting with underscore + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: _FOO + value: bar + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: _FOO + value: bar + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + + - name: Should accept lowercase envsubst key + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: my_var + value: something + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: my_var + value: something + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + + - name: Should reject envsubst key starting with digit + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: 1BAD + value: something + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expectedStatusError: "key must start with a letter or underscore" + + - name: Should reject envsubst key with hyphens + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: my-key + value: something + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expectedStatusError: "key must start with a letter or underscore" + + - name: Should accept empty value in manifestSubstitutions + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: MY_VAR + value: "" + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: MY_VAR + value: "" + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + + # ManifestSubstitution unique key validation tests + + - name: Should accept multiple manifestSubstitutions with distinct keys + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: VAR_A + value: value-a + - key: VAR_B + value: value-b + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: VAR_A + value: value-a + - key: VAR_B + value: value-b + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + + - name: Should reject duplicate keys in manifestSubstitutions + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: MY_VAR + value: value-1 + - key: MY_VAR + value: value-2 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expectedStatusError: "status.revisions[0].manifestSubstitutions[1]: Duplicate value:" + + - name: Should reject non-adjacent duplicate keys in manifestSubstitutions + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + manifestSubstitutions: + - key: VAR_A + value: value-1 + - key: VAR_B + value: value-2 + - key: VAR_A + value: value-3 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expectedStatusError: "status.revisions[0].manifestSubstitutions[2]: Duplicate value:" + + # Component name validation tests + + - name: Should accept component name with hyphens + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - name: core-provider + type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - name: core-provider + type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + + - name: Should accept component name with uppercase letters + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - name: CoreProvider + type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - name: CoreProvider + type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + + - name: Should reject component name with underscores + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - name: core_provider + type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expectedStatusError: "name must consist of alphanumeric characters or '-'" + + - name: Should reject component name with dots + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - name: core.provider + type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expectedStatusError: "name must consist of alphanumeric characters or '-'" + + # Component union discriminator tests + + - name: Should reject component with type Image but no image field + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + expectedStatusError: "image is required when type is Image, and forbidden otherwise" + + # observedRevisionGeneration validation tests + + - name: Should reject observedGeneration less than 1 + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + observedRevisionGeneration: 0 + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expectedStatusError: "should be greater than or equal to 1" + + - name: Should accept increasing observedGeneration + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + observedRevisionGeneration: 1 + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + observedRevisionGeneration: 2 + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + observedRevisionGeneration: 2 + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + + - name: Should accept keeping observedGeneration the same + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + observedRevisionGeneration: 1 + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + observedRevisionGeneration: 1 + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + observedRevisionGeneration: 1 + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + + - name: Should reject decreasing observedGeneration + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + observedRevisionGeneration: 5 + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + observedRevisionGeneration: 3 + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expectedStatusError: "observedRevisionGeneration may not decrease" + + - name: Should reject unsetting observedGeneration once set + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + observedRevisionGeneration: 1 + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: ClusterAPI + metadata: + name: cluster + spec: {} + status: + desiredRevision: rev-1 + revisions: + - name: rev-1 + revision: 1 + contentID: content-1 + components: + - type: Image + image: + ref: quay.io/openshift/cluster-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + profile: default + expectedStatusError: "observedRevisionGeneration may not be unset once set" diff --git a/operator/v1alpha1/types_clusterapi.go b/operator/v1alpha1/types_clusterapi.go index 5816f936283..719893f8a53 100644 --- a/operator/v1alpha1/types_clusterapi.go +++ b/operator/v1alpha1/types_clusterapi.go @@ -20,6 +20,7 @@ import ( // Compatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support. // +openshift:compatibility-gen:level=4 // +kubebuilder:validation:XValidation:rule="self.metadata.name == 'cluster'",message="clusterapi is a singleton, .metadata.name must be 'cluster'" +// +kubebuilder:validation:XValidation:rule="!has(oldSelf.status) || has(self.status)",message="status may not be removed once set" type ClusterAPI struct { metav1.TypeMeta `json:",inline"` @@ -78,6 +79,7 @@ type RevisionName string // ClusterAPIStatus describes the current state of the capi-operator. // +kubebuilder:validation:XValidation:rule="self.revisions.exists(r, r.name == self.desiredRevision && self.revisions.all(s, s.revision <= r.revision))",message="desiredRevision must be the name of the revision with the highest revision number" // +kubebuilder:validation:XValidation:rule="!has(self.currentRevision) || self.revisions.exists(r, r.name == self.currentRevision)",message="currentRevision must correspond to an entry in the revisions list" +// +kubebuilder:validation:XValidation:rule="!has(oldSelf.observedRevisionGeneration) || has(self.observedRevisionGeneration)",message="observedRevisionGeneration may not be unset once set" type ClusterAPIStatus struct { // currentRevision is the name of the most recently fully applied revision. // It is written by the installer controller. If it is absent, it indicates @@ -111,6 +113,15 @@ type ClusterAPIStatus struct { // +kubebuilder:validation:XValidation:rule="self.all(new, oldSelf.exists(old, old.name == new.name) || oldSelf.all(old, new.revision > old.revision))",message="new revisions must have a revision number greater than all existing revisions" // +kubebuilder:validation:XValidation:rule="oldSelf.all(old, !self.exists(new, new.name == old.name) || self.exists(new, new == old))",message="existing revisions are immutable, but may be removed" Revisions []ClusterAPIInstallerRevision `json:"revisions,omitempty"` + + // observedRevisionGeneration is the generation of the ClusterAPI object that was last observed by the revision controller. + // If specified it must be greater than or equal to 1, and less than 2^53. It may not decrease or be unset once set. + // + // +optional + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=9007199254740991 + // +kubebuilder:validation:XValidation:rule="self >= oldSelf",message="observedRevisionGeneration may not decrease" + ObservedRevisionGeneration int64 `json:"observedRevisionGeneration,omitempty"` } // +structType=atomic @@ -144,6 +155,17 @@ type ClusterAPIInstallerRevision struct { // +optional UnmanagedCustomResourceDefinitions []string `json:"unmanagedCustomResourceDefinitions,omitempty"` + // manifestSubstitutions is a list of envsubst style substitutions which + // will be applied to manifests in the revision during rendering. If + // defined it must not be empty, and may not contain more than 32 items. + // Each manifest substitution must have a unique key. + // +optional + // +listType=map + // +listMapKey=key + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=32 + ManifestSubstitutions []ClusterAPIInstallerRevisionManifestSubstitution `json:"manifestSubstitutions,omitempty"` + // components is a list of components which will be installed by this // revision. Components will be installed in the order they are listed. If // omitted no components will be installed. @@ -157,6 +179,29 @@ type ClusterAPIInstallerRevision struct { Components []ClusterAPIInstallerComponent `json:"components,omitempty"` } +// ClusterAPIInstallerRevisionManifestSubstitution defines an envsubst style +// substitution which will be applied to manifests in a revision during +// rendering. +type ClusterAPIInstallerRevisionManifestSubstitution struct { + // key is the name of the envsubst variable to substitute. It must be a + // valid envsubst variable name, consisting of letters, digits, and + // underscores, and must start with a letter or underscore. The key must + // not be empty, and must not exceed 255 characters. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=255 + // +kubebuilder:validation:XValidation:rule="self.matches('^[A-Za-z_][A-Za-z0-9_]*$')",message="key must start with a letter or underscore, followed by letters, digits, or underscores" + Key string `json:"key,omitempty"` + + // value is the value to substitute for the envsubst variable. It may be + // empty, in which case the variable will be substituted with an empty + // string. The value must not exceed 4096 characters. + // +required + // +kubebuilder:validation:MinLength=0 + // +kubebuilder:validation:MaxLength=4096 + Value *string `json:"value,omitempty"` +} + // InstallerComponentType is the type of component to install. // +kubebuilder:validation:Enum=Image // +enum @@ -168,9 +213,24 @@ const ( ) // ClusterAPIInstallerComponent defines a component which will be installed by this revision. +type ClusterAPIInstallerComponent struct { + // name is the human-readable name of the component. The value has no + // effect, and will not be set if the component does not define a name in + // its manifests. If set it must consist of alphanumeric characters, or + // '-', and may not exceed 255 characters. + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=255 + // +kubebuilder:validation:XValidation:rule="self.matches('^[A-Za-z0-9-]+$')",message="name must consist of alphanumeric characters or '-'" + Name string `json:"name,omitempty"` + + ClusterAPIInstallerComponentSource `json:",inline"` +} + +// ClusterAPIInstallerComponentSource defines the source of a component which will be installed by this revision. // +union // +kubebuilder:validation:XValidation:rule="self.type == 'Image' ? has(self.image) : !has(self.image)",message="image is required when type is Image, and forbidden otherwise" -type ClusterAPIInstallerComponent struct { +type ClusterAPIInstallerComponentSource struct { // type is the source type of the component. // The only valid value is Image. // When set to Image, the image field must be set and will define an image source for the component. diff --git a/operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml b/operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml index 6516a3ef0dd..67d384d3eb8 100644 --- a/operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml +++ b/operator/v1alpha1/zz_generated.crd-manifests/0000_30_cluster-api_01_clusterapis.crd.yaml @@ -106,6 +106,17 @@ spec: maxLength: 255 minLength: 1 type: string + observedRevisionGeneration: + description: |- + observedRevisionGeneration is the generation of the ClusterAPI object that was last observed by the revision controller. + If specified it must be greater than or equal to 1, and less than 2^53. It may not decrease or be unset once set. + format: int64 + maximum: 9007199254740991 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: observedRevisionGeneration may not decrease + rule: self >= oldSelf revisions: description: |- revisions is a list of all currently active revisions. A revision is @@ -168,6 +179,19 @@ spec: - profile - ref type: object + name: + description: |- + name is the human-readable name of the component. The value has no + effect, and will not be set if the component does not define a name in + its manifests. If set it must consist of alphanumeric characters, or + '-', and may not exceed 255 characters. + maxLength: 255 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must consist of alphanumeric characters + or '-' + rule: self.matches('^[A-Za-z0-9-]+$') type: description: |- type is the source type of the component. @@ -194,6 +218,49 @@ spec: maxLength: 255 minLength: 1 type: string + manifestSubstitutions: + description: |- + manifestSubstitutions is a list of envsubst style substitutions which + will be applied to manifests in the revision during rendering. If + defined it must not be empty, and may not contain more than 32 items. + Each manifest substitution must have a unique key. + items: + description: |- + ClusterAPIInstallerRevisionManifestSubstitution defines an envsubst style + substitution which will be applied to manifests in a revision during + rendering. + properties: + key: + description: |- + key is the name of the envsubst variable to substitute. It must be a + valid envsubst variable name, consisting of letters, digits, and + underscores, and must start with a letter or underscore. The key must + not be empty, and must not exceed 255 characters. + maxLength: 255 + minLength: 1 + type: string + x-kubernetes-validations: + - message: key must start with a letter or underscore, + followed by letters, digits, or underscores + rule: self.matches('^[A-Za-z_][A-Za-z0-9_]*$') + value: + description: |- + value is the value to substitute for the envsubst variable. It may be + empty, in which case the variable will be substituted with an empty + string. The value must not exceed 4096 characters. + maxLength: 4096 + minLength: 0 + type: string + required: + - key + - value + type: object + maxItems: 32 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map name: description: name is the name of a revision. maxLength: 255 @@ -259,6 +326,8 @@ spec: list rule: '!has(self.currentRevision) || self.revisions.exists(r, r.name == self.currentRevision)' + - message: observedRevisionGeneration may not be unset once set + rule: '!has(oldSelf.observedRevisionGeneration) || has(self.observedRevisionGeneration)' required: - metadata - spec @@ -266,6 +335,8 @@ spec: x-kubernetes-validations: - message: clusterapi is a singleton, .metadata.name must be 'cluster' rule: self.metadata.name == 'cluster' + - message: status may not be removed once set + rule: '!has(oldSelf.status) || has(self.status)' served: true storage: true subresources: diff --git a/operator/v1alpha1/zz_generated.deepcopy.go b/operator/v1alpha1/zz_generated.deepcopy.go index 1f3fd281e15..3c3dc8e7a53 100644 --- a/operator/v1alpha1/zz_generated.deepcopy.go +++ b/operator/v1alpha1/zz_generated.deepcopy.go @@ -61,7 +61,7 @@ func (in *ClusterAPI) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterAPIInstallerComponent) DeepCopyInto(out *ClusterAPIInstallerComponent) { *out = *in - out.Image = in.Image + out.ClusterAPIInstallerComponentSource = in.ClusterAPIInstallerComponentSource return } @@ -91,6 +91,23 @@ func (in *ClusterAPIInstallerComponentImage) DeepCopy() *ClusterAPIInstallerComp return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterAPIInstallerComponentSource) DeepCopyInto(out *ClusterAPIInstallerComponentSource) { + *out = *in + out.Image = in.Image + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterAPIInstallerComponentSource. +func (in *ClusterAPIInstallerComponentSource) DeepCopy() *ClusterAPIInstallerComponentSource { + if in == nil { + return nil + } + out := new(ClusterAPIInstallerComponentSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterAPIInstallerRevision) DeepCopyInto(out *ClusterAPIInstallerRevision) { *out = *in @@ -99,6 +116,13 @@ func (in *ClusterAPIInstallerRevision) DeepCopyInto(out *ClusterAPIInstallerRevi *out = make([]string, len(*in)) copy(*out, *in) } + if in.ManifestSubstitutions != nil { + in, out := &in.ManifestSubstitutions, &out.ManifestSubstitutions + *out = make([]ClusterAPIInstallerRevisionManifestSubstitution, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.Components != nil { in, out := &in.Components, &out.Components *out = make([]ClusterAPIInstallerComponent, len(*in)) @@ -117,6 +141,27 @@ func (in *ClusterAPIInstallerRevision) DeepCopy() *ClusterAPIInstallerRevision { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterAPIInstallerRevisionManifestSubstitution) DeepCopyInto(out *ClusterAPIInstallerRevisionManifestSubstitution) { + *out = *in + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterAPIInstallerRevisionManifestSubstitution. +func (in *ClusterAPIInstallerRevisionManifestSubstitution) DeepCopy() *ClusterAPIInstallerRevisionManifestSubstitution { + if in == nil { + return nil + } + out := new(ClusterAPIInstallerRevisionManifestSubstitution) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterAPIList) DeepCopyInto(out *ClusterAPIList) { *out = *in diff --git a/operator/v1alpha1/zz_generated.featuregated-crd-manifests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml b/operator/v1alpha1/zz_generated.featuregated-crd-manifests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml index 1b2bd420876..5a43bfadd13 100644 --- a/operator/v1alpha1/zz_generated.featuregated-crd-manifests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml +++ b/operator/v1alpha1/zz_generated.featuregated-crd-manifests/clusterapis.operator.openshift.io/ClusterAPIMachineManagement.yaml @@ -107,6 +107,17 @@ spec: maxLength: 255 minLength: 1 type: string + observedRevisionGeneration: + description: |- + observedRevisionGeneration is the generation of the ClusterAPI object that was last observed by the revision controller. + If specified it must be greater than or equal to 1, and less than 2^53. It may not decrease or be unset once set. + format: int64 + maximum: 9007199254740991 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: observedRevisionGeneration may not decrease + rule: self >= oldSelf revisions: description: |- revisions is a list of all currently active revisions. A revision is @@ -169,6 +180,19 @@ spec: - profile - ref type: object + name: + description: |- + name is the human-readable name of the component. The value has no + effect, and will not be set if the component does not define a name in + its manifests. If set it must consist of alphanumeric characters, or + '-', and may not exceed 255 characters. + maxLength: 255 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must consist of alphanumeric characters + or '-' + rule: self.matches('^[A-Za-z0-9-]+$') type: description: |- type is the source type of the component. @@ -195,6 +219,49 @@ spec: maxLength: 255 minLength: 1 type: string + manifestSubstitutions: + description: |- + manifestSubstitutions is a list of envsubst style substitutions which + will be applied to manifests in the revision during rendering. If + defined it must not be empty, and may not contain more than 32 items. + Each manifest substitution must have a unique key. + items: + description: |- + ClusterAPIInstallerRevisionManifestSubstitution defines an envsubst style + substitution which will be applied to manifests in a revision during + rendering. + properties: + key: + description: |- + key is the name of the envsubst variable to substitute. It must be a + valid envsubst variable name, consisting of letters, digits, and + underscores, and must start with a letter or underscore. The key must + not be empty, and must not exceed 255 characters. + maxLength: 255 + minLength: 1 + type: string + x-kubernetes-validations: + - message: key must start with a letter or underscore, + followed by letters, digits, or underscores + rule: self.matches('^[A-Za-z_][A-Za-z0-9_]*$') + value: + description: |- + value is the value to substitute for the envsubst variable. It may be + empty, in which case the variable will be substituted with an empty + string. The value must not exceed 4096 characters. + maxLength: 4096 + minLength: 0 + type: string + required: + - key + - value + type: object + maxItems: 32 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map name: description: name is the name of a revision. maxLength: 255 @@ -260,6 +327,8 @@ spec: list rule: '!has(self.currentRevision) || self.revisions.exists(r, r.name == self.currentRevision)' + - message: observedRevisionGeneration may not be unset once set + rule: '!has(oldSelf.observedRevisionGeneration) || has(self.observedRevisionGeneration)' required: - metadata - spec @@ -267,6 +336,8 @@ spec: x-kubernetes-validations: - message: clusterapi is a singleton, .metadata.name must be 'cluster' rule: self.metadata.name == 'cluster' + - message: status may not be removed once set + rule: '!has(oldSelf.status) || has(self.status)' served: true storage: true subresources: diff --git a/operator/v1alpha1/zz_generated.swagger_doc_generated.go b/operator/v1alpha1/zz_generated.swagger_doc_generated.go index 92cef1421a6..bf4117768d1 100644 --- a/operator/v1alpha1/zz_generated.swagger_doc_generated.go +++ b/operator/v1alpha1/zz_generated.swagger_doc_generated.go @@ -147,9 +147,8 @@ func (ClusterAPI) SwaggerDoc() map[string]string { } var map_ClusterAPIInstallerComponent = map[string]string{ - "": "ClusterAPIInstallerComponent defines a component which will be installed by this revision.", - "type": "type is the source type of the component. The only valid value is Image. When set to Image, the image field must be set and will define an image source for the component.", - "image": "image defines an image source for a component. The image must contain a /capi-operator-installer directory containing the component manifests.", + "": "ClusterAPIInstallerComponent defines a component which will be installed by this revision.", + "name": "name is the human-readable name of the component. The value has no effect, and will not be set if the component does not define a name in its manifests. If set it must consist of alphanumeric characters, or '-', and may not exceed 255 characters.", } func (ClusterAPIInstallerComponent) SwaggerDoc() map[string]string { @@ -166,11 +165,22 @@ func (ClusterAPIInstallerComponentImage) SwaggerDoc() map[string]string { return map_ClusterAPIInstallerComponentImage } +var map_ClusterAPIInstallerComponentSource = map[string]string{ + "": "ClusterAPIInstallerComponentSource defines the source of a component which will be installed by this revision.", + "type": "type is the source type of the component. The only valid value is Image. When set to Image, the image field must be set and will define an image source for the component.", + "image": "image defines an image source for a component. The image must contain a /capi-operator-installer directory containing the component manifests.", +} + +func (ClusterAPIInstallerComponentSource) SwaggerDoc() map[string]string { + return map_ClusterAPIInstallerComponentSource +} + var map_ClusterAPIInstallerRevision = map[string]string{ "name": "name is the name of a revision.", "revision": "revision is a monotonically increasing number that is assigned to a revision.", "contentID": "contentID uniquely identifies the content of this revision. The contentID must be between 1 and 255 characters long.", "unmanagedCustomResourceDefinitions": "unmanagedCustomResourceDefinitions is a list of the names of ClusterResourceDefinition (CRD) objects which are included in this revision, but which should not be installed or updated. If not set, all CRDs in the revision will be managed by the CAPI operator.", + "manifestSubstitutions": "manifestSubstitutions is a list of envsubst style substitutions which will be applied to manifests in the revision during rendering. If defined it must not be empty, and may not contain more than 32 items. Each manifest substitution must have a unique key.", "components": "components is a list of components which will be installed by this revision. Components will be installed in the order they are listed. If omitted no components will be installed.\n\nThe maximum number of components is 32.", } @@ -178,6 +188,16 @@ func (ClusterAPIInstallerRevision) SwaggerDoc() map[string]string { return map_ClusterAPIInstallerRevision } +var map_ClusterAPIInstallerRevisionManifestSubstitution = map[string]string{ + "": "ClusterAPIInstallerRevisionManifestSubstitution defines an envsubst style substitution which will be applied to manifests in a revision during rendering.", + "key": "key is the name of the envsubst variable to substitute. It must be a valid envsubst variable name, consisting of letters, digits, and underscores, and must start with a letter or underscore. The key must not be empty, and must not exceed 255 characters.", + "value": "value is the value to substitute for the envsubst variable. It may be empty, in which case the variable will be substituted with an empty string. The value must not exceed 4096 characters.", +} + +func (ClusterAPIInstallerRevisionManifestSubstitution) SwaggerDoc() map[string]string { + return map_ClusterAPIInstallerRevisionManifestSubstitution +} + var map_ClusterAPIList = map[string]string{ "": "ClusterAPIList contains a list of ClusterAPI configurations\n\nCompatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support.", "metadata": "metadata is the standard list's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", @@ -198,10 +218,11 @@ func (ClusterAPISpec) SwaggerDoc() map[string]string { } var map_ClusterAPIStatus = map[string]string{ - "": "ClusterAPIStatus describes the current state of the capi-operator.", - "currentRevision": "currentRevision is the name of the most recently fully applied revision. It is written by the installer controller. If it is absent, it indicates that no revision has been fully applied yet. If set, currentRevision must correspond to an entry in the revisions list.", - "desiredRevision": "desiredRevision is the name of the desired revision. It is written by the revision controller. It must be set to the name of the entry in the revisions list with the highest revision number.", - "revisions": "revisions is a list of all currently active revisions. A revision is active until the installer controller updates currentRevision to a later revision. It is written by the revision controller.\n\nThe maximum number of revisions is 16. All revisions must have a unique name. All revisions must have a unique revision number. When adding a revision, the revision number must be greater than the highest revision number in the list. Revisions are immutable, although they can be deleted.", + "": "ClusterAPIStatus describes the current state of the capi-operator.", + "currentRevision": "currentRevision is the name of the most recently fully applied revision. It is written by the installer controller. If it is absent, it indicates that no revision has been fully applied yet. If set, currentRevision must correspond to an entry in the revisions list.", + "desiredRevision": "desiredRevision is the name of the desired revision. It is written by the revision controller. It must be set to the name of the entry in the revisions list with the highest revision number.", + "revisions": "revisions is a list of all currently active revisions. A revision is active until the installer controller updates currentRevision to a later revision. It is written by the revision controller.\n\nThe maximum number of revisions is 16. All revisions must have a unique name. All revisions must have a unique revision number. When adding a revision, the revision number must be greater than the highest revision number in the list. Revisions are immutable, although they can be deleted.", + "observedRevisionGeneration": "observedRevisionGeneration is the generation of the ClusterAPI object that was last observed by the revision controller. If specified it must be greater than or equal to 1, and less than 2^53. It may not decrease or be unset once set.", } func (ClusterAPIStatus) SwaggerDoc() map[string]string { diff --git a/payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml b/payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml index 6516a3ef0dd..67d384d3eb8 100644 --- a/payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml +++ b/payload-manifests/crds/0000_30_cluster-api_01_clusterapis.crd.yaml @@ -106,6 +106,17 @@ spec: maxLength: 255 minLength: 1 type: string + observedRevisionGeneration: + description: |- + observedRevisionGeneration is the generation of the ClusterAPI object that was last observed by the revision controller. + If specified it must be greater than or equal to 1, and less than 2^53. It may not decrease or be unset once set. + format: int64 + maximum: 9007199254740991 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: observedRevisionGeneration may not decrease + rule: self >= oldSelf revisions: description: |- revisions is a list of all currently active revisions. A revision is @@ -168,6 +179,19 @@ spec: - profile - ref type: object + name: + description: |- + name is the human-readable name of the component. The value has no + effect, and will not be set if the component does not define a name in + its manifests. If set it must consist of alphanumeric characters, or + '-', and may not exceed 255 characters. + maxLength: 255 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must consist of alphanumeric characters + or '-' + rule: self.matches('^[A-Za-z0-9-]+$') type: description: |- type is the source type of the component. @@ -194,6 +218,49 @@ spec: maxLength: 255 minLength: 1 type: string + manifestSubstitutions: + description: |- + manifestSubstitutions is a list of envsubst style substitutions which + will be applied to manifests in the revision during rendering. If + defined it must not be empty, and may not contain more than 32 items. + Each manifest substitution must have a unique key. + items: + description: |- + ClusterAPIInstallerRevisionManifestSubstitution defines an envsubst style + substitution which will be applied to manifests in a revision during + rendering. + properties: + key: + description: |- + key is the name of the envsubst variable to substitute. It must be a + valid envsubst variable name, consisting of letters, digits, and + underscores, and must start with a letter or underscore. The key must + not be empty, and must not exceed 255 characters. + maxLength: 255 + minLength: 1 + type: string + x-kubernetes-validations: + - message: key must start with a letter or underscore, + followed by letters, digits, or underscores + rule: self.matches('^[A-Za-z_][A-Za-z0-9_]*$') + value: + description: |- + value is the value to substitute for the envsubst variable. It may be + empty, in which case the variable will be substituted with an empty + string. The value must not exceed 4096 characters. + maxLength: 4096 + minLength: 0 + type: string + required: + - key + - value + type: object + maxItems: 32 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map name: description: name is the name of a revision. maxLength: 255 @@ -259,6 +326,8 @@ spec: list rule: '!has(self.currentRevision) || self.revisions.exists(r, r.name == self.currentRevision)' + - message: observedRevisionGeneration may not be unset once set + rule: '!has(oldSelf.observedRevisionGeneration) || has(self.observedRevisionGeneration)' required: - metadata - spec @@ -266,6 +335,8 @@ spec: x-kubernetes-validations: - message: clusterapi is a singleton, .metadata.name must be 'cluster' rule: self.metadata.name == 'cluster' + - message: status may not be removed once set + rule: '!has(oldSelf.status) || has(self.status)' served: true storage: true subresources: