diff --git a/api/v1/requirement_types.go b/api/v1/requirement_types.go index 30c34a6..eb5a8d0 100644 --- a/api/v1/requirement_types.go +++ b/api/v1/requirement_types.go @@ -28,18 +28,30 @@ type RequirementSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - // Foo is an example field of Requirement. Edit requirement_types.go to remove/update - Foo string `json:"foo,omitempty"` + // +kubebuilder:validation:Required + Template OperationSpec `json:"template"` + // +kubebuilder:validation:Required + EnableCache bool `json:"enableCache"` + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Pattern:=`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$` + ExpireAt string `json:"expireAt,omitempty"` } // RequirementStatus defines the observed state of Requirement. type RequirementStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file + OperationId string `json:"operationId"` + OperationName string `json:"operationName"` + CacheKey string `json:"originalCacheKey"` + Phase string `json:"phase"` + Conditions []metav1.Condition `json:"conditions"` } // +kubebuilder:object:root=true // +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=`.status.phase` +// +kubebuilder:printcolumn:name="OperationId",type="string",JSONPath=`.status.operationId` // Requirement is the Schema for the requirements API. type Requirement struct { diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 73ab98e..b4e789c 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -353,8 +353,8 @@ func (in *Requirement) DeepCopyInto(out *Requirement) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - out.Status = in.Status + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Requirement. @@ -410,6 +410,7 @@ func (in *RequirementList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RequirementSpec) DeepCopyInto(out *RequirementSpec) { *out = *in + in.Template.DeepCopyInto(&out.Template) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequirementSpec. @@ -425,6 +426,13 @@ func (in *RequirementSpec) DeepCopy() *RequirementSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RequirementStatus) DeepCopyInto(out *RequirementStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequirementStatus. diff --git a/config/crd/bases/app.github.com_requirements.yaml b/config/crd/bases/app.github.com_requirements.yaml index 93f8b6c..6f932f4 100644 --- a/config/crd/bases/app.github.com_requirements.yaml +++ b/config/crd/bases/app.github.com_requirements.yaml @@ -14,7 +14,14 @@ spec: singular: requirement scope: Namespaced versions: - - name: v1 + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .status.operationId + name: OperationId + type: string + name: v1 schema: openAPIV3Schema: properties: @@ -26,10 +33,7591 @@ spec: type: object spec: properties: - foo: + enableCache: + type: boolean + expireAt: + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$ type: string + template: + properties: + applications: + items: + properties: + dependencies: + items: + type: string + type: array + name: + type: string + provision: + properties: + activeDeadlineSeconds: + format: int64 + type: integer + backoffLimit: + format: int32 + type: integer + backoffLimitPerIndex: + format: int32 + type: integer + completionMode: + type: string + completions: + format: int32 + type: integer + managedBy: + type: string + manualSelector: + type: boolean + maxFailedIndexes: + format: int32 + type: integer + parallelism: + format: int32 + type: integer + podFailurePolicy: + properties: + rules: + items: + properties: + action: + type: string + onExitCodes: + properties: + containerName: + type: string + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + - values + type: object + onPodConditions: + items: + properties: + status: + type: string + type: + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-type: atomic + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + required: + - rules + type: object + podReplacementPolicy: + type: string + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + successPolicy: + properties: + rules: + items: + properties: + succeededCount: + format: int32 + type: integer + succeededIndexes: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + required: + - rules + type: object + suspend: + type: boolean + template: + properties: + metadata: + type: object + spec: + properties: + activeDeadlineSeconds: + format: int64 + type: integer + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + automountServiceAccountToken: + type: boolean + containers: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + dnsConfig: + properties: + nameservers: + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + type: string + enableServiceLinks: + type: boolean + ephemeralContainers: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + targetContainerName: + type: string + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + hostAliases: + items: + properties: + hostnames: + items: + type: string + type: array + x-kubernetes-list-type: atomic + ip: + type: string + required: + - ip + type: object + type: array + x-kubernetes-list-map-keys: + - ip + x-kubernetes-list-type: map + hostIPC: + type: boolean + hostNetwork: + type: boolean + hostPID: + type: boolean + hostUsers: + type: boolean + hostname: + type: string + imagePullSecrets: + items: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + initContainers: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeName: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: atomic + os: + properties: + name: + type: string + required: + - name + type: object + overhead: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + preemptionPolicy: + type: string + priority: + format: int32 + type: integer + priorityClassName: + type: string + readinessGates: + items: + properties: + conditionType: + type: string + required: + - conditionType + type: object + type: array + x-kubernetes-list-type: atomic + resourceClaims: + items: + properties: + name: + type: string + resourceClaimName: + type: string + resourceClaimTemplateName: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + runtimeClassName: + type: string + schedulerName: + type: string + schedulingGates: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccount: + type: string + serviceAccountName: + type: string + setHostnameAsFQDN: + type: boolean + shareProcessNamespace: + type: boolean + subdomain: + type: string + terminationGracePeriodSeconds: + format: int64 + type: integer + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + topologySpreadConstraints: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + format: int32 + type: integer + minDomains: + format: int32 + type: integer + nodeAffinityPolicy: + type: string + nodeTaintsPolicy: + type: string + topologyKey: + type: string + whenUnsatisfiable: + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + default: ext4 + type: string + kind: + type: string + readOnly: + default: false + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + image: + properties: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - containers + type: object + type: object + ttlSecondsAfterFinished: + format: int32 + type: integer + required: + - template + type: object + teardown: + properties: + activeDeadlineSeconds: + format: int64 + type: integer + backoffLimit: + format: int32 + type: integer + backoffLimitPerIndex: + format: int32 + type: integer + completionMode: + type: string + completions: + format: int32 + type: integer + managedBy: + type: string + manualSelector: + type: boolean + maxFailedIndexes: + format: int32 + type: integer + parallelism: + format: int32 + type: integer + podFailurePolicy: + properties: + rules: + items: + properties: + action: + type: string + onExitCodes: + properties: + containerName: + type: string + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + - values + type: object + onPodConditions: + items: + properties: + status: + type: string + type: + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-type: atomic + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + required: + - rules + type: object + podReplacementPolicy: + type: string + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + successPolicy: + properties: + rules: + items: + properties: + succeededCount: + format: int32 + type: integer + succeededIndexes: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + required: + - rules + type: object + suspend: + type: boolean + template: + properties: + metadata: + type: object + spec: + properties: + activeDeadlineSeconds: + format: int64 + type: integer + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + automountServiceAccountToken: + type: boolean + containers: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + dnsConfig: + properties: + nameservers: + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + type: string + enableServiceLinks: + type: boolean + ephemeralContainers: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + targetContainerName: + type: string + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + hostAliases: + items: + properties: + hostnames: + items: + type: string + type: array + x-kubernetes-list-type: atomic + ip: + type: string + required: + - ip + type: object + type: array + x-kubernetes-list-map-keys: + - ip + x-kubernetes-list-type: map + hostIPC: + type: boolean + hostNetwork: + type: boolean + hostPID: + type: boolean + hostUsers: + type: boolean + hostname: + type: string + imagePullSecrets: + items: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + initContainers: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeName: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: atomic + os: + properties: + name: + type: string + required: + - name + type: object + overhead: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + preemptionPolicy: + type: string + priority: + format: int32 + type: integer + priorityClassName: + type: string + readinessGates: + items: + properties: + conditionType: + type: string + required: + - conditionType + type: object + type: array + x-kubernetes-list-type: atomic + resourceClaims: + items: + properties: + name: + type: string + resourceClaimName: + type: string + resourceClaimTemplateName: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + runtimeClassName: + type: string + schedulerName: + type: string + schedulingGates: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccount: + type: string + serviceAccountName: + type: string + setHostnameAsFQDN: + type: boolean + shareProcessNamespace: + type: boolean + subdomain: + type: string + terminationGracePeriodSeconds: + format: int64 + type: integer + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + topologySpreadConstraints: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + format: int32 + type: integer + minDomains: + format: int32 + type: integer + nodeAffinityPolicy: + type: string + nodeTaintsPolicy: + type: string + topologyKey: + type: string + whenUnsatisfiable: + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + default: ext4 + type: string + kind: + type: string + readOnly: + default: false + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + image: + properties: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + 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 + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - containers + type: object + type: object + ttlSecondsAfterFinished: + format: int32 + type: integer + required: + - template + type: object + required: + - name + - provision + - teardown + type: object + minItems: 1 + type: array + expireAt: + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$ + type: string + required: + - applications + type: object + required: + - enableCache + - template type: object status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + 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 + operationId: + type: string + operationName: + type: string + originalCacheKey: + type: string + phase: + type: string + required: + - conditions + - operationId + - operationName + - originalCacheKey + - phase type: object type: object served: true diff --git a/internal/controller/mocks/mock_requirement_adapter.go b/internal/controller/mocks/mock_requirement_adapter.go new file mode 100644 index 0000000..cd5daff --- /dev/null +++ b/internal/controller/mocks/mock_requirement_adapter.go @@ -0,0 +1,117 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/Azure/operation-cache-controller/internal/controller (interfaces: RequirementAdapterInterface) +// +// Generated by this command: +// +// mockgen -destination=./mocks/mock_requirement_adapter.go -package=mocks github.com/Azure/operation-cache-controller/internal/controller RequirementAdapterInterface +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + reconciler "github.com/Azure/operation-cache-controller/internal/utils/reconciler" + gomock "go.uber.org/mock/gomock" +) + +// MockRequirementAdapterInterface is a mock of RequirementAdapterInterface interface. +type MockRequirementAdapterInterface struct { + ctrl *gomock.Controller + recorder *MockRequirementAdapterInterfaceMockRecorder + isgomock struct{} +} + +// MockRequirementAdapterInterfaceMockRecorder is the mock recorder for MockRequirementAdapterInterface. +type MockRequirementAdapterInterfaceMockRecorder struct { + mock *MockRequirementAdapterInterface +} + +// NewMockRequirementAdapterInterface creates a new mock instance. +func NewMockRequirementAdapterInterface(ctrl *gomock.Controller) *MockRequirementAdapterInterface { + mock := &MockRequirementAdapterInterface{ctrl: ctrl} + mock.recorder = &MockRequirementAdapterInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRequirementAdapterInterface) EXPECT() *MockRequirementAdapterInterfaceMockRecorder { + return m.recorder +} + +// EnsureCacheExisted mocks base method. +func (m *MockRequirementAdapterInterface) EnsureCacheExisted(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureCacheExisted", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureCacheExisted indicates an expected call of EnsureCacheExisted. +func (mr *MockRequirementAdapterInterfaceMockRecorder) EnsureCacheExisted(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureCacheExisted", reflect.TypeOf((*MockRequirementAdapterInterface)(nil).EnsureCacheExisted), ctx) +} + +// EnsureCachedOperationAcquired mocks base method. +func (m *MockRequirementAdapterInterface) EnsureCachedOperationAcquired(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureCachedOperationAcquired", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureCachedOperationAcquired indicates an expected call of EnsureCachedOperationAcquired. +func (mr *MockRequirementAdapterInterfaceMockRecorder) EnsureCachedOperationAcquired(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureCachedOperationAcquired", reflect.TypeOf((*MockRequirementAdapterInterface)(nil).EnsureCachedOperationAcquired), ctx) +} + +// EnsureInitialized mocks base method. +func (m *MockRequirementAdapterInterface) EnsureInitialized(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureInitialized", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureInitialized indicates an expected call of EnsureInitialized. +func (mr *MockRequirementAdapterInterfaceMockRecorder) EnsureInitialized(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureInitialized", reflect.TypeOf((*MockRequirementAdapterInterface)(nil).EnsureInitialized), ctx) +} + +// EnsureNotExpired mocks base method. +func (m *MockRequirementAdapterInterface) EnsureNotExpired(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureNotExpired", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureNotExpired indicates an expected call of EnsureNotExpired. +func (mr *MockRequirementAdapterInterfaceMockRecorder) EnsureNotExpired(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureNotExpired", reflect.TypeOf((*MockRequirementAdapterInterface)(nil).EnsureNotExpired), ctx) +} + +// EnsureOperationReady mocks base method. +func (m *MockRequirementAdapterInterface) EnsureOperationReady(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureOperationReady", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureOperationReady indicates an expected call of EnsureOperationReady. +func (mr *MockRequirementAdapterInterfaceMockRecorder) EnsureOperationReady(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureOperationReady", reflect.TypeOf((*MockRequirementAdapterInterface)(nil).EnsureOperationReady), ctx) +} diff --git a/internal/controller/requirement_adapter.go b/internal/controller/requirement_adapter.go new file mode 100644 index 0000000..9eb2d27 --- /dev/null +++ b/internal/controller/requirement_adapter.go @@ -0,0 +1,303 @@ +package controller + +import ( + "context" + "fmt" + "time" + + "github.com/go-logr/logr" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + appsv1 "github.com/Azure/operation-cache-controller/api/v1" + ctlutils "github.com/Azure/operation-cache-controller/internal/utils/controller" + cacheutils "github.com/Azure/operation-cache-controller/internal/utils/controller/cache" + oputils "github.com/Azure/operation-cache-controller/internal/utils/controller/operation" + rqutils "github.com/Azure/operation-cache-controller/internal/utils/controller/requirement" + "github.com/Azure/operation-cache-controller/internal/utils/ptr" + "github.com/Azure/operation-cache-controller/internal/utils/reconciler" +) + +type requirementAdapterContextKey struct{} + +//go:generate mockgen -destination=./mocks/mock_requirement_adapter.go -package=mocks github.com/Azure/operation-cache-controller/internal/controller RequirementAdapterInterface +type RequirementAdapterInterface interface { + EnsureNotExpired(ctx context.Context) (reconciler.OperationResult, error) + EnsureInitialized(ctx context.Context) (reconciler.OperationResult, error) + EnsureCacheExisted(ctx context.Context) (reconciler.OperationResult, error) + EnsureCachedOperationAcquired(ctx context.Context) (reconciler.OperationResult, error) + EnsureOperationReady(ctx context.Context) (reconciler.OperationResult, error) +} + +type RequirementAdapter struct { + requirement *appsv1.Requirement + logger logr.Logger + client client.Client + recorder record.EventRecorder +} + +func NewRequirementAdapter(ctx context.Context, requirement *appsv1.Requirement, logger logr.Logger, client client.Client, recorder record.EventRecorder) RequirementAdapterInterface { + if requirementAdapter, ok := ctx.Value(requirementAdapterContextKey{}).(RequirementAdapterInterface); ok { + return requirementAdapter + } + + return &RequirementAdapter{ + requirement: requirement, + logger: logger, + client: client, + recorder: recorder, + } +} + +func (o *RequirementAdapter) phaseIn(phases ...string) bool { + + for _, phase := range phases { + if phase == o.requirement.Status.Phase { + return true + } + } + return false +} + +func (r *RequirementAdapter) EnsureNotExpired(ctx context.Context) (reconciler.OperationResult, error) { + r.logger.V(1).Info("operation: EnsureNotExpired") + if len(r.requirement.Spec.ExpireAt) == 0 { + return reconciler.ContinueProcessing() + } + + expireTime, err := time.Parse(time.RFC3339, r.requirement.Spec.ExpireAt) + if err != nil { + r.logger.Error(err, fmt.Sprintf("Failed to parse expire time: %s", r.requirement.Spec.ExpireAt)) + r.recorder.Event(r.requirement, "Warning", "InvalidExpireTime", "Failed to parse expire time") + return reconciler.ContinueProcessing() + } + if time.Now().Before(expireTime) { + return reconciler.ContinueProcessing() + } + // Expired + r.logger.Info("deleting expired requirement", "expireAt", r.requirement.Spec.ExpireAt) + if err := r.client.Delete(ctx, r.requirement, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil { + r.logger.Error(err, "Failed to delete expired requirement") + r.recorder.Event(r.requirement, "Warning", "DeleteFailed", "Failed to delete expired requirement") + return reconciler.RequeueWithError(err) + } + return reconciler.ContinueProcessing() +} + +func (r *RequirementAdapter) EnsureInitialized(ctx context.Context) (reconciler.OperationResult, error) { + r.logger.V(1).Info("operation: EnsureInitialized") + if !r.phaseIn(rqutils.PhaseEmpty) { + return reconciler.ContinueProcessing() + } + r.requirement.Status.CacheKey = ctlutils.NewCacheKeyFromApplications(r.requirement.Spec.Template.Applications) + rqutils.ClearConditions(r.requirement) + if r.requirement.Spec.EnableCache { + r.requirement.Status.Phase = rqutils.PhaseCacheChecking + } else { + r.requirement.Status.Phase = rqutils.PhaseOperating + } + return reconciler.RequeueOnErrorOrContinue(r.client.Status().Update(ctx, r.requirement)) +} + +func (r *RequirementAdapter) ownerReference() metav1.OwnerReference { + return metav1.OwnerReference{ + APIVersion: r.requirement.APIVersion, + Kind: r.requirement.Kind, + Name: r.requirement.Name, + UID: r.requirement.UID, + Controller: ptr.Of(true), + BlockOwnerDeletion: ptr.Of(true), + } +} + +func (r *RequirementAdapter) setCacheNotExistedStatus() { + r.requirement.Status.Phase = rqutils.PhaseOperating + _ = rqutils.UpdateCondition(r.requirement, rqutils.ConditionCacheResourceFound, metav1.ConditionFalse, rqutils.ConditionReasonCacheCRFound, "Cache CR found") +} + +func (r *RequirementAdapter) setCacheHitStatus() { + r.requirement.Status.Phase = rqutils.PhaseReady + _ = rqutils.UpdateCondition(r.requirement, rqutils.ConditionOperationReady, metav1.ConditionTrue, rqutils.ConditionReasonCacheHit, "Cached Operation acquired") +} + +func (r *RequirementAdapter) setCacheMissStatus() { + r.requirement.Status.Phase = rqutils.PhaseOperating + _ = rqutils.UpdateCondition(r.requirement, rqutils.ConditionCachedOperationAcquired, metav1.ConditionTrue, rqutils.ConditionReasonCacheMiss, "No cached operation available") +} + +func (r *RequirementAdapter) defaultCacheName() string { + return fmt.Sprintf("cache-%s", r.requirement.Status.CacheKey) +} + +func (r *RequirementAdapter) EnsureCacheExisted(ctx context.Context) (reconciler.OperationResult, error) { + if !r.phaseIn(rqutils.PhaseCacheChecking) { + return reconciler.ContinueProcessing() + } + + r.logger.V(1).Info("operation: EnsureCacheExisted") + + // candidate operation id exists, go to next step to acquire the operation + if len(r.requirement.Status.OperationName) != 0 { + return reconciler.ContinueProcessing() + } + + if len(r.requirement.Status.CacheKey) == 0 { + r.logger.Error(fmt.Errorf("empty cache key"), "Cache key is empty, cannot proceed with cache creation") + return reconciler.RequeueWithError(fmt.Errorf("empty cache key")) + } + cache := &appsv1.Cache{} + // Try to get the Cache CR + if err := r.client.Get(ctx, types.NamespacedName{Name: r.defaultCacheName(), Namespace: r.requirement.Namespace}, cache); err != nil { + if client.IgnoreNotFound(err) != nil { + // If the error is not a NotFound error, return it + return reconciler.RequeueWithError(err) + } + // cache cr not found, create it + cache.Name = r.defaultCacheName() + cache.Namespace = r.requirement.Namespace + cache.Spec = appsv1.CacheSpec{ + OperationTemplate: r.requirement.Spec.Template, + ExpireTime: cacheutils.DefaultCacheExpireTime(), + } + err = r.client.Create(ctx, cache) + if err != nil { + return reconciler.RequeueWithError(err) + } + r.setCacheNotExistedStatus() + return reconciler.RequeueOnErrorOrContinue(r.client.Status().Update(ctx, r.requirement)) + } + // extend cache expire time every time when cache is checked + cache.Spec.ExpireTime = cacheutils.DefaultCacheExpireTime() + _ = r.client.Update(ctx, cache) + r.requirement.Status.OperationName = cacheutils.RandomSelectCachedOperation(cache) + return reconciler.RequeueOnErrorOrContinue(r.client.Status().Update(ctx, r.requirement)) +} + +func (r *RequirementAdapter) EnsureCachedOperationAcquired(ctx context.Context) (reconciler.OperationResult, error) { + if !r.phaseIn(rqutils.PhaseCacheChecking) { + return reconciler.ContinueProcessing() + } + r.logger.V(1).Info("operation: EnsureCachedOperationAcquired") + if len(r.requirement.Status.OperationName) == 0 { + r.logger.V(1).Info("no cached operation available") + r.setCacheMissStatus() + return reconciler.RequeueOnErrorOrContinue(r.client.Status().Update(ctx, r.requirement)) + } + operation := &appsv1.Operation{} + if err := r.client.Get(ctx, types.NamespacedName{Name: r.requirement.Status.OperationName, Namespace: r.requirement.Namespace}, operation); err != nil { + r.setCacheMissStatus() + return reconciler.RequeueOnErrorOrContinue(fmt.Errorf("failed to get operation %s: %w", r.requirement.Status.OperationName, err)) + } + // already acquired + if _, ok := operation.Annotations[oputils.AcquiredAnnotationKey]; ok { + if len(operation.OwnerReferences) != 0 { + if operation.OwnerReferences[0].UID != r.requirement.UID { + // return error if owner is not this requirement + r.logger.V(1).Info("operation already acquired by other requirement", "operation", r.requirement.Status.OperationName) + r.setCacheMissStatus() + return reconciler.RequeueOnErrorOrContinue(r.client.Status().Update(ctx, r.requirement)) + } else { + // set to ready status if the operation already acquired by this requirement + r.logger.V(1).Info("operation already acquired by this requirement", "operation", r.requirement.Status.OperationName) + r.setCacheHitStatus() + return reconciler.RequeueOnErrorOrStop(r.client.Status().Update(ctx, r.requirement)) + } + } + } + // if operation not acquired, acquire it + if err := r.acquireCachedOperation(ctx, operation); err != nil { + r.setCacheMissStatus() + return reconciler.RequeueOnErrorOrContinue(fmt.Errorf("failed to update operation %s: %w", r.requirement.Status.OperationName, err)) + } + // set to ready status if the operation acquired + r.setCacheHitStatus() + return reconciler.RequeueOnErrorOrContinue(r.client.Status().Update(ctx, r.requirement)) +} + +func (r *RequirementAdapter) acquireCachedOperation(ctx context.Context, operation *appsv1.Operation) error { + operation.Annotations[oputils.AcquiredAnnotationKey] = time.Now().Format(time.RFC3339) + operation.OwnerReferences = []metav1.OwnerReference{r.ownerReference()} + return r.client.Update(ctx, operation) +} + +func (r *RequirementAdapter) getOperation() (*appsv1.Operation, error) { + namespacedName := types.NamespacedName{ + Name: r.requirement.Status.OperationName, + Namespace: r.requirement.Namespace, + } + + operation := &appsv1.Operation{} + if err := r.client.Get(context.Background(), namespacedName, operation); err != nil { + return nil, fmt.Errorf("failed to get operation %s: %w", r.requirement.Status.OperationName, err) + } + return operation, nil +} + +func (r *RequirementAdapter) updateOperation() error { + op, err := r.getOperation() + if err != nil { + return err + } + op.Spec = r.requirement.Spec.Template + return r.client.Update(context.Background(), op) +} + +func (r *RequirementAdapter) createOperation() error { + operation := &appsv1.Operation{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.requirement.Status.OperationName, + Namespace: r.requirement.Namespace, + }, + Spec: r.requirement.Spec.Template, + } + if err := controllerutil.SetControllerReference(r.requirement, operation, r.client.Scheme()); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) + } + return r.client.Create(context.Background(), operation) +} + +func (r *RequirementAdapter) EnsureOperationReady(ctx context.Context) (reconciler.OperationResult, error) { + r.logger.V(1).Info("operation: EnsureOperationReady") + if r.phaseIn(rqutils.PhaseReady) { + // check if application changed + cacheKey := ctlutils.NewCacheKeyFromApplications(r.requirement.Spec.Template.Applications) + if r.requirement.Status.CacheKey != cacheKey { + r.logger.Info("application changed, updating operation", "oldCacheKey", r.requirement.Status.CacheKey, "newCacheKey", cacheKey) + if err := r.updateOperation(); err != nil { + return reconciler.RequeueWithError(err) + } + r.requirement.Status.CacheKey = cacheKey + r.requirement.Status.Phase = rqutils.PhaseOperating + return reconciler.RequeueOnErrorOrContinue(r.client.Status().Update(ctx, r.requirement)) + } + return reconciler.ContinueProcessing() + } + if !r.phaseIn(rqutils.PhaseOperating) { + return reconciler.ContinueProcessing() + } + if rqutils.IsCacheMissed(r.requirement) { + r.logger.V(1).Info("cache missed, creating operation") + r.requirement.Status.OperationName = r.requirement.Name + "-" + "operation" + } + // check operation status + if op, err := r.getOperation(); err == nil { + r.logger.V(1).Info("requirement operation found", "operation", op.Name) + if op.Status.Phase == oputils.PhaseReconciled { + r.logger.Info("operation is reconciled, set requirement to ready", "operationName", op.Name, "operationId", op.Status.OperationID) + r.requirement.Status.Phase = rqutils.PhaseReady + r.requirement.Status.OperationId = op.Status.OperationID + return reconciler.RequeueOnErrorOrContinue(r.client.Status().Update(ctx, r.requirement)) + } + r.logger.V(1).Info("reconciling requirement operation...", "operation", op.Name) + return reconciler.Requeue() + } + r.logger.V(1).Info("operation not found, creating one") + if err := r.createOperation(); err != nil { + return reconciler.RequeueWithError(err) + } + return reconciler.Requeue() +} diff --git a/internal/controller/requirement_adapter_test.go b/internal/controller/requirement_adapter_test.go new file mode 100644 index 0000000..753ea8e --- /dev/null +++ b/internal/controller/requirement_adapter_test.go @@ -0,0 +1,601 @@ +package controller + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + appsv1 "github.com/Azure/operation-cache-controller/api/v1" + mockpkg "github.com/Azure/operation-cache-controller/internal/mocks" + ctlutils "github.com/Azure/operation-cache-controller/internal/utils/controller" + oputils "github.com/Azure/operation-cache-controller/internal/utils/controller/operation" + rqutils "github.com/Azure/operation-cache-controller/internal/utils/controller/requirement" + "github.com/Azure/operation-cache-controller/internal/utils/reconciler" +) + +var ( + testOperationName = "test-operation" + emptyRequirement = &appsv1.Requirement{} + validRequirement = &appsv1.Requirement{ + Spec: appsv1.RequirementSpec{ + ExpireAt: time.Now().Add(time.Hour).Format(time.RFC3339), + Template: appsv1.OperationSpec{ + Applications: []appsv1.ApplicationSpec{ + { + Name: "test-app1", + Provision: newTestJobSpec(), + Teardown: newTestJobSpec(), + + Dependencies: []string{"test-app2"}, + }, + { + Name: "test-app2", + Provision: newTestJobSpec(), + Teardown: newTestJobSpec(), + }, + }, + }, + }, + } + validCache = &appsv1.Cache{ + Status: appsv1.CacheStatus{ + AvailableCaches: []string{"test-cache1", "test-cache2"}, + }, + } +) + +func TestNewRequirementAdapter(t *testing.T) { + t.Run("When creating a new Requirement Adapter", func(t *testing.T) { + ctx := context.Background() + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + + requirement := emptyRequirement.DeepCopy() + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + require.NotNil(t, adapter) + }) +} +func TestRequirementAdapter_EnsureNotExpired(t *testing.T) { + ctx := context.Background() + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + + t.Run("happy path: continue processing when expire is not set", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Spec.ExpireAt = time.Now().Add(time.Hour).Format(time.RFC3339) + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + res, err := adapter.EnsureNotExpired(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + t.Run("happy path: continue processing when requirement is not expired", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Spec.ExpireAt = time.Now().Add(time.Hour).Format(time.RFC3339) + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + res, err := adapter.EnsureNotExpired(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + + }) + t.Run("Sad path: failed to parse expire time", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Spec.ExpireAt = "invalid-time" + mockRecorder.EXPECT().Event(requirement, "Warning", "InvalidExpireTime", "Failed to parse expire time") + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + res, err := adapter.EnsureNotExpired(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + + t.Run("happy path: delete operation when expire time is in the past", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Spec.ExpireAt = time.Now().Add(-time.Hour).Format(time.RFC3339) + + mockClient.EXPECT().Delete(ctx, requirement, gomock.Any()).Return(nil) + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + res, err := adapter.EnsureNotExpired(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + t.Run("sad path: delete operation failed", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Spec.ExpireAt = time.Now().Add(-time.Hour).Format(time.RFC3339) + + mockClient.EXPECT().Delete(ctx, requirement, gomock.Any()).Return(assert.AnError) + mockRecorder.EXPECT().Event(requirement, "Warning", "DeleteFailed", "Failed to delete expired requirement") + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + res, err := adapter.EnsureNotExpired(ctx) + assert.Error(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) +} + +func TestRequirementAdapter_EnsureInitialized(t *testing.T) { + ctx := context.Background() + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusWriterCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusWriterCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + + t.Run("happy path: continue processing when requirement is in empty phase and cache disabled", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + res, err := adapter.EnsureInitialized(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + assert.Equal(t, rqutils.PhaseOperating, requirement.Status.Phase) + }) + + t.Run("happy path: continue processing when requirement is in empty phase and cache enabled", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Spec.EnableCache = true + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + res, err := adapter.EnsureInitialized(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + assert.Equal(t, rqutils.PhaseCacheChecking, requirement.Status.Phase) + }) + + t.Run("happy path: continue processing requirement is not in empty phase", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.Phase = rqutils.PhaseOperating + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureInitialized(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) +} + +func TestRequirementAdapter_EnsureCacheExisted(t *testing.T) { + ctx := context.Background() + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusWriterCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusWriterCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + + t.Run("happy path: continue processing when cache is not enabled", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureCacheExisted(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + + t.Run("happy path: continue processing when candidate operation exist", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.Phase = rqutils.PhaseCacheChecking + requirement.Status.OperationName = testOperationName + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureCacheExisted(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + assert.Equal(t, rqutils.PhaseCacheChecking, requirement.Status.Phase) + }) + + t.Run("happy path: when get a candidate operation", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.Phase = rqutils.PhaseCacheChecking + requirement.Status.CacheKey = ctlutils.NewCacheKeyFromApplications(requirement.Spec.Template.Applications) + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + cache := validCache.DeepCopy() + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(cache), gomock.Any()).DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*appsv1.Cache) = *cache + return nil + }) + + mockClient.EXPECT().Update(ctx, gomock.AssignableToTypeOf(&appsv1.Cache{})).Return(nil) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + res, err := adapter.EnsureCacheExisted(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + assert.Equal(t, rqutils.PhaseCacheChecking, requirement.Status.Phase) + }) + t.Run("sad path: cache key is not set", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.Phase = rqutils.PhaseCacheChecking + requirement.Status.CacheKey = "" + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + res, err := adapter.EnsureCacheExisted(ctx) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + assert.ErrorContains(t, err, "empty cache key") + }) + + t.Run("sad path: failed to get cache", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.Phase = rqutils.PhaseCacheChecking + requirement.Status.CacheKey = ctlutils.NewCacheKeyFromApplications(requirement.Spec.Template.Applications) + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + cache := validCache.DeepCopy() + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(cache), gomock.Any()).Return(assert.AnError) + + res, err := adapter.EnsureCacheExisted(ctx) + assert.Error(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) + + t.Run("happy path: cache not found, create a new cache", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.Phase = rqutils.PhaseCacheChecking + requirement.Status.CacheKey = ctlutils.NewCacheKeyFromApplications(requirement.Spec.Template.Applications) + errCacheNotFound := apierrors.NewNotFound(schema.GroupResource{Group: "appsv1", Resource: "Cache"}, "cache not found") + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(errCacheNotFound) + mockClient.EXPECT().Create(ctx, gomock.Any()).Return(nil) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + res, err := adapter.EnsureCacheExisted(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + assert.Equal(t, rqutils.PhaseOperating, requirement.Status.Phase) + }) + + t.Run("happy path: cache is not available", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.Phase = rqutils.PhaseCacheChecking + requirement.Status.CacheKey = ctlutils.NewCacheKeyFromApplications(requirement.Spec.Template.Applications) + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + cache := validCache.DeepCopy() + cache.Status.AvailableCaches = nil + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(cache), gomock.Any()).DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*appsv1.Cache) = *cache + return nil + }) + mockClient.EXPECT().Update(ctx, gomock.AssignableToTypeOf(&appsv1.Cache{})).Return(nil) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + res, err := adapter.EnsureCacheExisted(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + assert.Equal(t, requirement.Status.OperationName, "") + }) +} + +func TestRequirementAdapter_EnsureCachedOperationAcquired(t *testing.T) { + ctx := context.Background() + logger := log.FromContext(ctx) + testRequirementUID := types.UID("test-uid") + + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusWriterCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusWriterCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + + t.Run("happy path: continue processing when not in cache checking phase", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureCachedOperationAcquired(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + + t.Run("happy path: continue processing when operation is not set", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.OperationName = "" + requirement.Status.Phase = rqutils.PhaseCacheChecking + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + res, err := adapter.EnsureCachedOperationAcquired(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + assert.Equal(t, rqutils.PhaseOperating, requirement.Status.Phase) + }) + + t.Run("happy path: continue processing when operation is already acquired", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.UID = testRequirementUID + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseCacheChecking + operation := validOperation.DeepCopy() + operation.Annotations = map[string]string{ + oputils.AcquiredAnnotationKey: "2021-09-01T00:00:00Z", + } + operation.OwnerReferences = []metav1.OwnerReference{ + { + APIVersion: "appsv1", + Kind: "Requirement", + Name: requirement.Name, + UID: requirement.UID, + }, + } + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*appsv1.Operation) = *operation + return nil + }) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureCachedOperationAcquired(ctx) + assert.NoError(t, err) + assert.Equal(t, rqutils.PhaseReady, requirement.Status.Phase) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, CancelRequest: true}, res) + }) + + t.Run("happy path: continue processing when operation is acquired but other requirement", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.UID = testRequirementUID + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseCacheChecking + operation := validOperation.DeepCopy() + operation.Annotations = map[string]string{ + oputils.AcquiredAnnotationKey: "2021-09-01T00:00:00Z", + } + operation.OwnerReferences = []metav1.OwnerReference{ + { + APIVersion: "appsv1", + Kind: "Requirement", + Name: "other-requirement", + UID: "other-uid", + }, + } + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*appsv1.Operation) = *operation + return nil + }) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureCachedOperationAcquired(ctx) + assert.NoError(t, err) + assert.Equal(t, rqutils.PhaseOperating, requirement.Status.Phase) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + }) + + t.Run("happy path: continue processing when operation is not acquired, acquired it with success", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.UID = testRequirementUID + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseCacheChecking + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + operation := validOperation.DeepCopy() + operation.Annotations = map[string]string{} + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*appsv1.Operation) = *operation + return nil + }) + mockClient.EXPECT().Update(ctx, gomock.AssignableToTypeOf(&appsv1.Operation{})).Return(nil) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + + res, err := adapter.EnsureCachedOperationAcquired(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + assert.Equal(t, operation.Name, requirement.Status.OperationName) + assert.Equal(t, rqutils.PhaseReady, requirement.Status.Phase) + }) + + t.Run("sad path: failed to get operation", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.UID = testRequirementUID + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseCacheChecking + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).Return(assert.AnError) + + res, err := adapter.EnsureCachedOperationAcquired(ctx) + assert.ErrorIs(t, err, assert.AnError) + assert.Equal(t, rqutils.PhaseOperating, requirement.Status.Phase) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + }) + + t.Run("sad path: when operation is not acquired, acquired it with failed", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.UID = testRequirementUID + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseCacheChecking + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + operation := validOperation.DeepCopy() + operation.Annotations = map[string]string{} + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*appsv1.Operation) = *operation + return nil + }) + mockClient.EXPECT().Update(ctx, gomock.AssignableToTypeOf(&appsv1.Operation{})).Return(assert.AnError) + + res, err := adapter.EnsureCachedOperationAcquired(ctx) + assert.Error(t, err) + assert.Equal(t, requirement.Status.OperationName, operation.Name) + assert.Equal(t, rqutils.PhaseOperating, requirement.Status.Phase) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + }) +} + +func TestRequirementAdapter_EnsureOperationReady(t *testing.T) { + ctx := context.Background() + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusWriterCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusWriterCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + + t.Run("happy path: continue processing when not in ready and operating phase", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureOperationReady(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + + t.Run("happy path: continue processing when in ready phase but cachekey is not changed", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.OperationName = testOperationName + requirement.Status.CacheKey = ctlutils.NewCacheKeyFromApplications(requirement.Spec.Template.Applications) + requirement.Status.Phase = rqutils.PhaseReady + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureOperationReady(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + + t.Run("happy path: ready phase but cachekey is changed", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseReady + requirement.Status.CacheKey = "test-cache-key" + operaition := validOperation.DeepCopy() + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*appsv1.Operation) = *operaition + return nil + }) + mockClient.EXPECT().Update(ctx, gomock.AssignableToTypeOf(&appsv1.Operation{})).Return(nil) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + res, err := adapter.EnsureOperationReady(ctx) + assert.NoError(t, err) + assert.Equal(t, rqutils.PhaseOperating, requirement.Status.Phase) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + }) + t.Run("sad path: failed to get operation", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseReady + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).Return(assert.AnError) + + res, err := adapter.EnsureOperationReady(ctx) + assert.ErrorIs(t, err, assert.AnError) + assert.Equal(t, rqutils.PhaseReady, requirement.Status.Phase) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) + + t.Run("happy path: continue processing when operation is not ready", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseOperating + + operation := validOperation.DeepCopy() + operation.Status.Phase = oputils.PhaseReconciling + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*appsv1.Operation) = *operation + return nil + }) + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureOperationReady(ctx) + assert.NoError(t, err) + assert.Equal(t, rqutils.PhaseOperating, requirement.Status.Phase) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) + + t.Run("happy path: continue processing when operation is ready", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseOperating + operation := validOperation.DeepCopy() + operation.Status.Phase = oputils.PhaseReconciled + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*appsv1.Operation) = *operation + return nil + }) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil) + + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureOperationReady(ctx) + assert.NoError(t, err) + assert.Equal(t, rqutils.PhaseReady, requirement.Status.Phase) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + }) + + t.Run("happy path: operation not found, create one", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseOperating + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{Group: "appsv1", Resource: "Operation"}, "operation not found")) + mockClient.EXPECT().Scheme().Return(scheme) + mockClient.EXPECT().Create(ctx, gomock.Any()).Return(nil) + + res, err := adapter.EnsureOperationReady(ctx) + assert.NoError(t, err) + assert.Equal(t, rqutils.PhaseOperating, requirement.Status.Phase) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) + + t.Run("sad path: failed to create operation", func(t *testing.T) { + requirement := validRequirement.DeepCopy() + requirement.Status.OperationName = testOperationName + requirement.Status.Phase = rqutils.PhaseOperating + adapter := NewRequirementAdapter(ctx, requirement, logger, mockClient, mockRecorder) + schema := runtime.NewScheme() + _ = appsv1.AddToScheme(schema) + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&appsv1.Operation{}), gomock.Any()).Return(assert.AnError) + mockClient.EXPECT().Scheme().Return(schema) + mockClient.EXPECT().Create(ctx, gomock.Any()).Return(assert.AnError) + + res, err := adapter.EnsureOperationReady(ctx) + assert.ErrorIs(t, err, assert.AnError) + assert.Equal(t, rqutils.PhaseOperating, requirement.Status.Phase) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) +} diff --git a/internal/controller/requirement_controller.go b/internal/controller/requirement_controller.go index 6452928..c33e436 100644 --- a/internal/controller/requirement_controller.go +++ b/internal/controller/requirement_controller.go @@ -18,19 +18,27 @@ package controller import ( "context" + "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/log" - appv1 "github.com/Azure/operation-cache-controller/api/v1" + appsv1 "github.com/Azure/operation-cache-controller/api/v1" + "github.com/Azure/operation-cache-controller/internal/utils/reconciler" ) +var defaultCheckInterval = 10 * time.Minute + // RequirementReconciler reconciles a Requirement object type RequirementReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + recorder record.EventRecorder } // +kubebuilder:rbac:groups=app.github.com,resources=requirements,verbs=get;list;watch;create;update;patch;delete @@ -47,17 +55,70 @@ type RequirementReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile func (r *RequirementReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = logf.FromContext(ctx) + logger := log.FromContext(ctx).WithValues("requirement", req.NamespacedName) + + requirement := &appsv1.Requirement{} + if err := r.Get(ctx, req.NamespacedName, requirement); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + adapter := NewRequirementAdapter(ctx, requirement, logger, r.Client, r.recorder) + return r.ReconcileHandler(ctx, adapter) +} +func (r *RequirementReconciler) ReconcileHandler(ctx context.Context, adapter RequirementAdapterInterface) (ctrl.Result, error) { + operations := []reconciler.ReconcileOperation{ + adapter.EnsureNotExpired, + adapter.EnsureInitialized, + adapter.EnsureCacheExisted, + adapter.EnsureCachedOperationAcquired, + adapter.EnsureOperationReady, + } - // TODO(user): your logic here + for _, operation := range operations { + result, err := operation(ctx) + if err != nil { + return ctrl.Result{}, err + } + if result.RequeueRequest { + return ctrl.Result{RequeueAfter: reconciler.DefaultRequeueDelay}, err + } + if result.CancelRequest { + return ctrl.Result{}, nil + } + } - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: defaultCheckInterval}, nil } +var requirementOwnerKey = ".requirement.metadata.controller" + // SetupWithManager sets up the controller with the Manager. func (r *RequirementReconciler) SetupWithManager(mgr ctrl.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &appsv1.Operation{}, requirementOwnerKey, + func(rawObj client.Object) []string { + // grab the Operation object, extract the owner + op := rawObj.(*appsv1.Operation) + owner := metav1.GetControllerOf(op) + if owner == nil { + return nil + } + // Make sure the owner is a Requirement object + if owner.APIVersion != appsv1.GroupVersion.String() || owner.Kind != "Requirement" { + return nil + } + return []string{owner.Name} + }); err != nil { + return err + } + + r.recorder = mgr.GetEventRecorderFor("Requirement") + return ctrl.NewControllerManagedBy(mgr). - For(&appv1.Requirement{}). + For(&appsv1.Requirement{}). + Owns(&appsv1.Operation{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: 100, + }). Named("requirement"). Complete(r) } diff --git a/internal/controller/requirement_controller_test.go b/internal/controller/requirement_controller_test.go index cfd3431..a1ebc84 100644 --- a/internal/controller/requirement_controller_test.go +++ b/internal/controller/requirement_controller_test.go @@ -18,48 +18,238 @@ package controller import ( "context" + "fmt" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "go.uber.org/mock/gomock" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/reconcile" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - appv1 "github.com/Azure/operation-cache-controller/api/v1" + appsv1 "github.com/Azure/operation-cache-controller/api/v1" + "github.com/Azure/operation-cache-controller/internal/controller/mocks" + "github.com/Azure/operation-cache-controller/internal/utils/reconciler" ) var _ = Describe("Requirement Controller", func() { + Context("When setupWithManager is called", func() { + It("Should setup the controller with the manager", func() { + + // Create a new mock controller + mockCtrl := gomock.NewController(GinkgoT()) + defer mockCtrl.Finish() + + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) + Expect(err).NotTo(HaveOccurred()) + + err = (&RequirementReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + recorder: k8sManager.GetEventRecorderFor("requirement-controller"), + }).SetupWithManager(k8sManager) + + Expect(err).NotTo(HaveOccurred()) + }) + }) + Context("When creating a new Requirement Controller", func() { + var ( + timeout = time.Second * 10 + interval = time.Millisecond * 250 + ) + It("Should create a new Requirement Controller", func() { + key := types.NamespacedName{ + Name: "test-requirement", + Namespace: "default", + } + requirement := &appsv1.Requirement{ + ObjectMeta: metav1.ObjectMeta{ + Name: key.Name, + Namespace: key.Namespace, + }, + Spec: appsv1.RequirementSpec{ + Template: appsv1.OperationSpec{ + Applications: []appsv1.ApplicationSpec{ + { + Name: "test-app", + Provision: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Image: "test-image", + }, + }, + }, + }, + }, + Teardown: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Image: "test-image", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + Expect(k8sClient.Create(context.Background(), requirement)).Should(Succeed()) + + fetchedRequirement := &appsv1.Requirement{} + Eventually(func() bool { + err := k8sClient.Get(context.Background(), key, fetchedRequirement) + return err == nil + }, timeout, interval).Should(BeTrue()) + }) + }) + Context("When reconciling a resource with adapter", func() { + var ( + mockClientCtrl *gomock.Controller + mockRecorderCtrl *gomock.Controller + mockAdapterCtrl *gomock.Controller + mockAdapter *mocks.MockRequirementAdapterInterface + + requirementReconciler *RequirementReconciler + + key = types.NamespacedName{ + Name: "test-requirement", + Namespace: "default", + } + ) + + BeforeEach(func() { + mockClientCtrl = gomock.NewController(GinkgoT()) + mockRecorderCtrl = gomock.NewController(GinkgoT()) + mockAdapterCtrl = gomock.NewController(GinkgoT()) + mockAdapter = mocks.NewMockRequirementAdapterInterface(mockAdapterCtrl) + + requirementReconciler = &RequirementReconciler{ + Client: k8sClient, + Scheme: scheme.Scheme, + } + }) + + AfterEach(func() { + mockClientCtrl.Finish() + mockRecorderCtrl.Finish() + mockAdapterCtrl.Finish() + }) + + It("Should reconcile the resource with adapter", func() { + mockAdapter.EXPECT().EnsureNotExpired(gomock.Any()).Return(reconciler.ContinueOperationResult(), nil) + mockAdapter.EXPECT().EnsureInitialized(gomock.Any()).Return(reconciler.ContinueOperationResult(), nil) + mockAdapter.EXPECT().EnsureCacheExisted(gomock.Any()).Return(reconciler.ContinueOperationResult(), nil) + mockAdapter.EXPECT().EnsureCachedOperationAcquired(gomock.Any()).Return(reconciler.ContinueOperationResult(), nil) + mockAdapter.EXPECT().EnsureOperationReady(gomock.Any()).Return(reconciler.ContinueOperationResult(), nil) + + result, err := requirementReconciler.Reconcile(context.WithValue(context.Background(), requirementAdapterContextKey{}, mockAdapter), ctrl.Request{ + NamespacedName: key, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(ctrl.Result{RequeueAfter: defaultCheckInterval})) + }) + It("Should return error if any", func() { + By("Reconciling the resource with adapter") + ctx := context.Background() + testErr := fmt.Errorf("test-error") + mockAdapter.EXPECT().EnsureNotExpired(gomock.Any()).Return(reconciler.RequeueWithError(testErr)) + + result, err := requirementReconciler.Reconcile(context.WithValue(ctx, requirementAdapterContextKey{}, mockAdapter), ctrl.Request{ + NamespacedName: key, + }) + Expect(err).To(MatchError(testErr)) + Expect(result).Should(Equal(reconcile.Result{})) + }) + + It("should cancel the reconcile loop", func() { + By("Reconciling the created resource") + ctx := context.Background() + + mockAdapter.EXPECT().EnsureNotExpired(gomock.Any()).Return(reconciler.OperationResult{ + CancelRequest: true, + }, nil) + + result, err := requirementReconciler.Reconcile(context.WithValue(ctx, requirementAdapterContextKey{}, mockAdapter), ctrl.Request{ + NamespacedName: key, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result).Should(Equal(reconcile.Result{})) + }) + }) Context("When reconciling a resource", func() { - const resourceName = "test-resource" + const resourceName = "test-req-resource" ctx := context.Background() typeNamespacedName := types.NamespacedName{ Name: resourceName, - Namespace: "default", // TODO(user):Modify as needed + Namespace: "default", } - requirement := &appv1.Requirement{} + requirement := &appsv1.Requirement{} BeforeEach(func() { - By("creating the custom resource for the Kind Requirement") + By("Creating a new Requirement resource") err := k8sClient.Get(ctx, typeNamespacedName, requirement) if err != nil && errors.IsNotFound(err) { - resource := &appv1.Requirement{ + resource := &appsv1.Requirement{ ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, - Namespace: "default", + Name: typeNamespacedName.Name, + Namespace: typeNamespacedName.Namespace, + }, + Spec: appsv1.RequirementSpec{ + Template: appsv1.OperationSpec{ + Applications: []appsv1.ApplicationSpec{ + { + Name: "test-app", + Provision: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Image: "test-image", + }, + }, + }, + }, + }, + Teardown: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Image: "test-image", + }, + }, + }, + }, + }, + }, + }, + }, }, - // TODO(user): Specify other spec details if needed. } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + Expect(k8sClient.Create(context.Background(), resource)).Should(Succeed()) } }) AfterEach(func() { // TODO(user): Cleanup logic after each test, like removing the resource instance. - resource := &appv1.Requirement{} + resource := &appsv1.Requirement{} err := k8sClient.Get(ctx, typeNamespacedName, resource) Expect(err).NotTo(HaveOccurred()) @@ -72,13 +262,11 @@ var _ = Describe("Requirement Controller", func() { Client: k8sClient, Scheme: k8sClient.Scheme(), } - - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + result, err := controllerReconciler.Reconcile(ctx, ctrl.Request{ NamespacedName: typeNamespacedName, }) Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. + Expect(result).Should(Equal(reconcile.Result{RequeueAfter: reconciler.DefaultRequeueDelay})) }) }) }) diff --git a/internal/utils/controller/requirement/const.go b/internal/utils/controller/requirement/const.go new file mode 100644 index 0000000..2e0b754 --- /dev/null +++ b/internal/utils/controller/requirement/const.go @@ -0,0 +1,27 @@ +package requirement + +const ( + FinalizerName = "finalizer.requirement.devinfra.goms.io" + + // RequirementInitialized is the condition type for Requirement initialized + ConditionRequirementInitialized = "RequirementInitialized" + // Cache CRD for Requirement is existed + ConditionCacheResourceFound = "CacheCRFound" + // no available Operation CRD for Requirement in cache, create one + ConditionCachedOperationAcquired = "CachedOpAcquired" + // Operation CRD for Requirement is ready + ConditionOperationReady = "OperationReady" + + ConditionReasonNoOperationAvailable = "NoOperationAvailable" + ConditionReasonCacheCRNotFound = "CacheCRNotFound" + ConditionReasonCacheCRFound = "CacheCRFound" + ConditionReasonCacheHit = "CacheHit" + ConditionReasonCacheMiss = "CacheMiss" + + PhaseEmpty = "" + PhaseCacheChecking = "CacheChecking" + PhaseOperating = "Operating" + PhaseReady = "Ready" + PhaseDeleted = "Deleted" + PhaseDeleting = "Deleting" +) diff --git a/internal/utils/controller/requirement/status.go b/internal/utils/controller/requirement/status.go new file mode 100644 index 0000000..9cac96e --- /dev/null +++ b/internal/utils/controller/requirement/status.go @@ -0,0 +1,58 @@ +package requirement + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + appsv1 "github.com/Azure/operation-cache-controller/api/v1" +) + +func ClearConditions(r *appsv1.Requirement) { + if r.Status.Conditions == nil { + r.Status.Conditions = []metav1.Condition{} + } +} + +func getCondition(r *appsv1.Requirement, conditionType string) (int, *metav1.Condition) { + for i, condition := range r.Status.Conditions { + if condition.Type == conditionType { + return i, &condition + } + } + return -1, nil +} + +func IsCacheMissed(r *appsv1.Requirement) bool { + _, condition := getCondition(r, ConditionCachedOperationAcquired) + return condition == nil || condition.Status == metav1.ConditionFalse +} + +func UpdateCondition(r *appsv1.Requirement, conditionType string, conditionStatus metav1.ConditionStatus, reason, message string) bool { + condition := &metav1.Condition{ + Type: conditionType, + Status: conditionStatus, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + } + // Try to find this pod condition. + idx, oldCondition := getCondition(r, condition.Type) + + if oldCondition == nil { + // We are adding new pod condition. + r.Status.Conditions = append(r.Status.Conditions, *condition) + return true + } + // We are updating an existing condition, so we need to check if it has changed. + if condition.Status == oldCondition.Status { + condition.LastTransitionTime = oldCondition.LastTransitionTime + } + + isEqual := condition.Status == oldCondition.Status && + condition.Reason == oldCondition.Reason && + condition.Message == oldCondition.Message && + condition.LastTransitionTime.Equal(&oldCondition.LastTransitionTime) + + r.Status.Conditions[idx] = *condition + // Return true if one of the fields have changed. + return !isEqual +} diff --git a/internal/utils/controller/requirement/status_test.go b/internal/utils/controller/requirement/status_test.go new file mode 100644 index 0000000..76bf583 --- /dev/null +++ b/internal/utils/controller/requirement/status_test.go @@ -0,0 +1,158 @@ +package requirement + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + appsv1 "github.com/Azure/operation-cache-controller/api/v1" +) + +// --- Test ClearConditions --- +func TestClearConditions(t *testing.T) { + // Requirement with nil Conditions. + req := &appsv1.Requirement{ + Status: appsv1.RequirementStatus{ + // Conditions nil for test. + }, + } + ClearConditions(req) + require.NotNil(t, req.Status.Conditions) + require.Equal(t, 0, len(req.Status.Conditions)) +} + +// --- Test IsCacheMissed --- +func TestIsCacheMissed(t *testing.T) { + tests := []struct { + name string + conditions []metav1.Condition + wantMissed bool + }{ + { + name: "no conditions", + conditions: nil, + wantMissed: true, + }, + { + name: "condition exists - false status", + conditions: []metav1.Condition{ + { + Type: ConditionCachedOperationAcquired, + Status: metav1.ConditionFalse, + }, + }, + wantMissed: true, + }, + { + name: "condition exists - true status", + conditions: []metav1.Condition{ + { + Type: ConditionCachedOperationAcquired, + Status: metav1.ConditionTrue, + }, + }, + wantMissed: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := &appsv1.Requirement{ + Status: appsv1.RequirementStatus{ + Conditions: tt.conditions, + }, + } + got := IsCacheMissed(req) + require.Equal(t, tt.wantMissed, got) + }) + } +} + +// --- Test UpdateCondition --- +func TestUpdateCondition(t *testing.T) { + // Helper to create a Requirement with given conditions. + newReq := func(conds []metav1.Condition) *appsv1.Requirement { + return &appsv1.Requirement{ + Status: appsv1.RequirementStatus{ + Conditions: conds, + }, + } + } + + now := metav1.NewTime(time.Now()) + + tests := []struct { + name string + initialConds []metav1.Condition + conditionType string + conditionStatus metav1.ConditionStatus + reason string + message string + wantChanged bool + }{ + { + name: "add new condition", + initialConds: []metav1.Condition{}, + conditionType: "TestCondition", + conditionStatus: metav1.ConditionTrue, + reason: "ReasonA", + message: "MessageA", + wantChanged: true, + }, + { + name: "no change when same condition exists", + initialConds: []metav1.Condition{ + { + Type: "TestCondition", + Status: metav1.ConditionTrue, + Reason: "ReasonA", + Message: "MessageA", + LastTransitionTime: now, + }, + }, + conditionType: "TestCondition", + conditionStatus: metav1.ConditionTrue, + reason: "ReasonA", + message: "MessageA", + wantChanged: false, + }, + { + name: "update existing condition if field changes", + initialConds: []metav1.Condition{ + { + Type: "TestCondition", + Status: metav1.ConditionFalse, + Reason: "OldReason", + Message: "OldMessage", + LastTransitionTime: now, + }, + }, + conditionType: "TestCondition", + conditionStatus: metav1.ConditionTrue, + reason: "NewReason", + message: "NewMessage", + wantChanged: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := newReq(tt.initialConds) + changed := UpdateCondition(req, tt.conditionType, tt.conditionStatus, tt.reason, tt.message) + require.Equal(t, tt.wantChanged, changed) + + // Verify the condition is updated/added. + var found *metav1.Condition + for i := range req.Status.Conditions { + if req.Status.Conditions[i].Type == tt.conditionType { + found = &req.Status.Conditions[i] + break + } + } + require.NotNil(t, found) + require.Equal(t, tt.conditionStatus, found.Status) + require.Equal(t, tt.reason, found.Reason) + require.Equal(t, tt.message, found.Message) + }) + } +}