diff --git a/.github/.testcoverage.yml b/.github/.testcoverage.yml index 6c0fa4e..fc291ce 100644 --- a/.github/.testcoverage.yml +++ b/.github/.testcoverage.yml @@ -15,11 +15,11 @@ threshold: # (optional; default 0) # Minimum coverage percentage required for each package. - package: 60 + package: 80 # (optional; default 0) # Minimum overall project coverage percentage required. - total: 60 + total: 80 # Holds regexp rules which will override thresholds for matched files or packages # using their paths. diff --git a/.gitignore b/.gitignore index 1b906dd..a081bf2 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ go.work.sum # kubebuilder binaries bin/ + +# vscode settings +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index b18e21f..cc987e6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # operation-cache-controller [![Go Report Card](https://goreportcard.com/badge/github.com/Azure/operation-cache-controller)](https://goreportcard.com/report/github.com/Azure/operation-cache-controller) -[![Go Test Status](https://github.com/Azure/operation-cache-controller/.github/workflows/test.yml/badge.svg)](https://github.com/Azure/operation-cache-controller/.github/workflows/test.yml) +[![Go Test Status](https://github.com/Azure/operation-cache-controller/actions/workflows/test.yml/badge.svg)](https://github.com/Azure/operation-cache-controller/actions/workflows/test.yml) [![Go Test Coverage](https://raw.githubusercontent.com/Azure/operation-cache-controller/badges/.badges/main/coverage.svg)](/.github/.testcoverage.yml) A k8s controller used to manage operations and cache the outcome of that operation diff --git a/api/v1/cache_types.go b/api/v1/cache_types.go index 75ea87d..0d46e77 100644 --- a/api/v1/cache_types.go +++ b/api/v1/cache_types.go @@ -28,14 +28,25 @@ type CacheSpec 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 Cache. Edit cache_types.go to remove/update - Foo string `json:"foo,omitempty"` + OperationTemplate OperationSpec `json:"operationTemplate"` + + // Strategy is the cache strategy + // +kubebuilder:validation:optional + Strategy string `json:"strategy,omitempty"` + + // ExpireTime is the RFC3339-format time when the cache will be expired. If not set, the cache is never expired. + // +kubebuilder:validation:optional + // +kubebuilder:validation:Pattern:=`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$` + ExpireTime string `json:"expireTime,omitempty"` } // CacheStatus defines the observed state of Cache. type CacheStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file + CacheKey string `json:"cacheKey"` + KeepAliveCount int32 `json:"keepAlive"` + AvailableCaches []string `json:"availableCaches,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index c0c3bf6..73ab98e 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -155,8 +155,8 @@ func (in *Cache) DeepCopyInto(out *Cache) { *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 Cache. @@ -212,6 +212,7 @@ func (in *CacheList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CacheSpec) DeepCopyInto(out *CacheSpec) { *out = *in + in.OperationTemplate.DeepCopyInto(&out.OperationTemplate) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CacheSpec. @@ -227,6 +228,11 @@ func (in *CacheSpec) DeepCopy() *CacheSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CacheStatus) DeepCopyInto(out *CacheStatus) { *out = *in + if in.AvailableCaches != nil { + in, out := &in.AvailableCaches, &out.AvailableCaches + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CacheStatus. diff --git a/config/crd/bases/app.github.com_caches.yaml b/config/crd/bases/app.github.com_caches.yaml index 90b7182..e5b6525 100644 --- a/config/crd/bases/app.github.com_caches.yaml +++ b/config/crd/bases/app.github.com_caches.yaml @@ -26,10 +26,7552 @@ spec: type: object spec: properties: - foo: + expireTime: + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$ type: string + operationTemplate: + 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 + strategy: + type: string + required: + - operationTemplate type: object status: + properties: + availableCaches: + items: + type: string + type: array + cacheKey: + type: string + keepAlive: + format: int32 + type: integer + required: + - cacheKey + - keepAlive type: object type: object served: true diff --git a/internal/controller/appdeployment_controller_test.go b/internal/controller/appdeployment_controller_test.go index 34d0ca7..dfd11d2 100644 --- a/internal/controller/appdeployment_controller_test.go +++ b/internal/controller/appdeployment_controller_test.go @@ -35,6 +35,7 @@ import ( func newTestJobSpec() batchv1.JobSpec { return batchv1.JobSpec{ Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { diff --git a/internal/controller/cache_adapter.go b/internal/controller/cache_adapter.go new file mode 100644 index 0000000..867bf1a --- /dev/null +++ b/internal/controller/cache_adapter.go @@ -0,0 +1,231 @@ +package controller + +import ( + "context" + "errors" + "fmt" + "strings" + "sync" + "time" + + "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "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" + ctrlutils "github.com/Azure/operation-cache-controller/internal/utils/controller" + oputils "github.com/Azure/operation-cache-controller/internal/utils/controller/operation" + "github.com/Azure/operation-cache-controller/internal/utils/reconciler" +) + +//go:generate mockgen -destination=./mocks/mock_cache_adapter.go -package=mocks github.com/Azure/operation-cache-controller/internal/controller CacheAdapterInterface +type CacheAdapterInterface interface { + CheckCacheExpiry(ctx context.Context) (reconciler.OperationResult, error) + EnsureCacheInitialized(ctx context.Context) (reconciler.OperationResult, error) + CalculateKeepAliveCount(ctx context.Context) (reconciler.OperationResult, error) + AdjustCache(ctx context.Context) (reconciler.OperationResult, error) +} + +type CacheAdapter struct { + cache *appsv1.Cache + logger logr.Logger + client client.Client + scheme *runtime.Scheme + recorder record.EventRecorder + setControllerReferenceFunc func(owner, controlled metav1.Object, scheme *runtime.Scheme, opts ...controllerutil.OwnerReferenceOption) error +} + +func NewCacheAdapter(ctx context.Context, + cache *appsv1.Cache, logger logr.Logger, client client.Client, scheme *runtime.Scheme, recorder record.EventRecorder, + fn func(owner, controlled metav1.Object, scheme *runtime.Scheme, opts ...controllerutil.OwnerReferenceOption) error) CacheAdapterInterface { + return &CacheAdapter{ + cache: cache, + logger: logger, + client: client, + scheme: scheme, + recorder: recorder, + setControllerReferenceFunc: fn, + } +} + +// updateStatus updates the status of the cache cr +func (c *CacheAdapter) updateStatus(ctx context.Context) error { + if err := c.client.Status().Update(ctx, c.cache); err != nil { + return fmt.Errorf("unable to update cache status: %w", err) + } + return nil +} + +// CheckCacheExpiry checks if the cache cr is expired. If it is, the cr is deleted. +func (c *CacheAdapter) CheckCacheExpiry(ctx context.Context) (reconciler.OperationResult, error) { + if c.cache.Spec.ExpireTime == "" { + return reconciler.ContinueProcessing() + } + ce, err := time.Parse(time.RFC3339, c.cache.Spec.ExpireTime) + if err != nil { + c.logger.Error(err, "failed to parse expire time") + // TODO: set cache expiry condition if needed + return reconciler.ContinueProcessing() + } + if time.Now().After(ce) { + c.logger.Info("cache is expired, deleting cache cr") + if err := c.client.Delete(ctx, c.cache); err != nil { + return reconciler.RequeueWithError(err) + } + return reconciler.StopProcessing() + } + return reconciler.ContinueProcessing() +} + +// EnsureCacheInitialized ensures the cache cr is initialized +func (c *CacheAdapter) EnsureCacheInitialized(ctx context.Context) (reconciler.OperationResult, error) { + // initialize the AvailableCaches in status if it is nil + if c.cache.Status.AvailableCaches == nil { + c.cache.Status.AvailableCaches = []string{} + } + if c.cache.Status.CacheKey == "" { + c.cache.Status.CacheKey = ctrlutils.NewCacheKeyFromApplications(c.cache.Spec.OperationTemplate.Applications) + } + + return reconciler.RequeueOnErrorOrContinue(c.updateStatus(ctx)) +} + +// CalculateKeepAliveCount calculates the keepAliveCount for the cache cr +func (c *CacheAdapter) CalculateKeepAliveCount(ctx context.Context) (reconciler.OperationResult, error) { + // before we have cache service to provide the keepAliveCount, we use fixed value + c.cache.Status.KeepAliveCount = 5 + return reconciler.RequeueOnErrorOrContinue(c.updateStatus(ctx)) +} + +func (c *CacheAdapter) createOperationsAsync(ctx context.Context, ops []*appsv1.Operation) error { + wg := sync.WaitGroup{} + errChan := make(chan error, len(ops)) + for _, op := range ops { + wg.Add(1) + go func() { + defer wg.Done() + errChan <- c.client.Create(ctx, op) + }() + } + wg.Wait() + close(errChan) + var errs error + for err := range errChan { + errs = errors.Join(errs, err) + } + return errs +} + +func (c *CacheAdapter) deleteOperationsAsync(ctx context.Context, ops []*appsv1.Operation) error { + wg := sync.WaitGroup{} + errChan := make(chan error, len(ops)) + for _, op := range ops { + wg.Add(1) + go func() { + defer wg.Done() + errChan <- c.client.Delete(ctx, op) + }() + } + wg.Wait() + close(errChan) + var errs error + for err := range errChan { + errs = errors.Join(errs, err) + } + return errs +} + +func operationReady(op *appsv1.Operation) bool { + return op.Status.Phase == oputils.PhaseReconciled +} + +func (c *CacheAdapter) initOperationFromCache(operationName string) *appsv1.Operation { + op := &appsv1.Operation{} + + annotations := op.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + annotations[ctrlutils.AnnotationNameCacheMode] = ctrlutils.AnnotationValueTrue + + labels := op.GetLabels() + if labels == nil { + labels = map[string]string{} + } + // TODO: set up requirement label instead + cacheKeyLabelValue := c.cache.Status.CacheKey + if len(c.cache.Status.CacheKey) > 63 { + cacheKeyLabelValue = cacheKeyLabelValue[:63] + } + labels[ctrlutils.LabelNameCacheKey] = cacheKeyLabelValue + + op.SetAnnotations(annotations) + op.SetNamespace(c.cache.Namespace) + op.SetName(operationName) + op.SetLabels(labels) + op.Spec = c.cache.Spec.OperationTemplate + return op +} + +func (c *CacheAdapter) AdjustCache(ctx context.Context) (reconciler.OperationResult, error) { + var ownedOps appsv1.OperationList + if err := c.client.List(ctx, &ownedOps, client.InNamespace(c.cache.Namespace), client.MatchingFields{cacheOwnerKey: c.cache.Name}); err != nil { + return reconciler.RequeueWithError(err) + } + availableCaches := []string{} + for _, op := range ownedOps.Items { + if operationReady(&op) { + availableCaches = append(availableCaches, op.Name) + } + } + c.cache.Status.AvailableCaches = availableCaches + + keepAliveCount := int(c.cache.Status.KeepAliveCount) + cacheBalance := len(availableCaches) - keepAliveCount + switch { + case cacheBalance == 0: + // do nothing: should we remove the not available operations? + case cacheBalance > 0: + // remove all the not available operations and cut available operations down to keepAliveCount + availableCacheNumToRemove := cacheBalance + opsToRemove := []*appsv1.Operation{} + for _, op := range ownedOps.Items { + if !operationReady(&op) { + opsToRemove = append(opsToRemove, &op) + } else { + if availableCacheNumToRemove > 0 { + opsToRemove = append(opsToRemove, &op) + availableCacheNumToRemove-- + } + } + } + c.logger.Info("removing operations", "operations", opsToRemove) + if err := c.deleteOperationsAsync(ctx, opsToRemove); err != nil { + return reconciler.RequeueWithError(err) + } + case cacheBalance < 0: + if len(ownedOps.Items) < keepAliveCount { + // also count not available operations, create new operations to meet the keepAliveCount + opsToCreate := []*appsv1.Operation{} + opsNumToCreate := keepAliveCount - len(ownedOps.Items) + for range opsNumToCreate { + opName := fmt.Sprintf("cached-operation-%s-%s", c.cache.Status.CacheKey[:8], strings.ToLower(ctrlutils.GenerateRandomString(5))) + opToCreate := c.initOperationFromCache(opName) + if err := c.setControllerReferenceFunc(c.cache, opToCreate, c.scheme); err != nil { + return reconciler.RequeueWithError(err) + } + opsToCreate = append(opsToCreate, opToCreate) + } + c.logger.Info("creating operations", "operations", opsToCreate) + if err := c.createOperationsAsync(ctx, opsToCreate); err != nil { + return reconciler.RequeueWithError(err) + } + } + // else do nothing: we assume that any not ready operations are in progress and will be ready + // we can bring in stuck operations handling if we consider that's one case for cache controller to solve + } + return reconciler.RequeueOnErrorOrContinue(c.updateStatus(ctx)) +} diff --git a/internal/controller/cache_adapter_test.go b/internal/controller/cache_adapter_test.go new file mode 100644 index 0000000..3f9e116 --- /dev/null +++ b/internal/controller/cache_adapter_test.go @@ -0,0 +1,411 @@ +package controller + +import ( + "context" + "testing" + "time" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "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" + ctrlutils "github.com/Azure/operation-cache-controller/internal/utils/controller" + oputils "github.com/Azure/operation-cache-controller/internal/utils/controller/operation" +) + +func TestNewCacheAdapter(t *testing.T) { + t.Run("NewCacheAdapter", func(t *testing.T) { + testCache := &appsv1.Cache{} + testlogger := logr.Logger{} + scheme := runtime.NewScheme() + var ( + mockClientCtrl *gomock.Controller + mockClient *mockpkg.MockClient + mockRecorderCtrl *gomock.Controller + mockRecorder *mockpkg.MockEventRecorder + ) + mockClient = mockpkg.NewMockClient(mockClientCtrl) + mockRecorder = mockpkg.NewMockEventRecorder(mockRecorderCtrl) + adapter := NewCacheAdapter(context.Background(), testCache, testlogger, mockClient, scheme, mockRecorder, ctrl.SetControllerReference) + assert.NotNil(t, adapter) + }) +} + +func getTestApps() []appsv1.ApplicationSpec { + return []appsv1.ApplicationSpec{ + { + Name: "test-app-available", + Provision: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{}}, + }, + }, + }, + }, + } +} + +func TestCacheCheckCacheExpiry(t *testing.T) { + ctx := context.Background() + testlogger := log.FromContext(ctx) + scheme := runtime.NewScheme() + var ( + mockClientCtrl *gomock.Controller + mockClient *mockpkg.MockClient + mockRecorderCtrl *gomock.Controller + mockRecorder *mockpkg.MockEventRecorder + ) + mockClientCtrl = gomock.NewController(t) + mockRecorderCtrl = gomock.NewController(t) + mockClient = mockpkg.NewMockClient(mockClientCtrl) + mockRecorder = mockpkg.NewMockEventRecorder(mockRecorderCtrl) + + t.Run("happy path", func(t *testing.T) { + t.Run("cache not expired", func(t *testing.T) { + testCache := &appsv1.Cache{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cache", + Namespace: "test-ns", + }, + Spec: appsv1.CacheSpec{ + ExpireTime: time.Now().Add(1 * time.Hour).Format(time.RFC3339), + }, + Status: appsv1.CacheStatus{}, + } + adapter := NewCacheAdapter(ctx, testCache, testlogger, mockClient, scheme, mockRecorder, ctrl.SetControllerReference) + assert.NotNil(t, adapter) + + res, err := adapter.CheckCacheExpiry(ctx) + assert.Nil(t, err) + assert.Equal(t, false, res.RequeueRequest) + assert.Equal(t, false, res.CancelRequest) + }) + t.Run("cache expired", func(t *testing.T) { + testCache := &appsv1.Cache{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cache", + Namespace: "test-ns", + }, + Spec: appsv1.CacheSpec{ + ExpireTime: time.Now().Add(-1 * time.Hour).Format(time.RFC3339), + }, + Status: appsv1.CacheStatus{}, + } + adapter := NewCacheAdapter(ctx, testCache, testlogger, mockClient, scheme, mockRecorder, ctrl.SetControllerReference) + assert.NotNil(t, adapter) + mockClient.EXPECT().Delete(ctx, gomock.Any()).Return(nil) + + res, err := adapter.CheckCacheExpiry(ctx) + assert.Nil(t, err) + assert.Equal(t, false, res.RequeueRequest) + assert.Equal(t, true, res.CancelRequest) + }) + t.Run("cache expireTime not set", func(t *testing.T) { + testCache := &appsv1.Cache{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cache", + Namespace: "test-ns", + }, + Spec: appsv1.CacheSpec{}, + Status: appsv1.CacheStatus{}, + } + adapter := NewCacheAdapter(ctx, testCache, testlogger, mockClient, scheme, mockRecorder, ctrl.SetControllerReference) + assert.NotNil(t, adapter) + + res, err := adapter.CheckCacheExpiry(ctx) + assert.Nil(t, err) + assert.Equal(t, false, res.RequeueRequest) + assert.Equal(t, false, res.CancelRequest) + }) + }) + + t.Run("negative cases", func(t *testing.T) { + t.Run("invalid expire time", func(t *testing.T) { + testCache := &appsv1.Cache{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cache", + Namespace: "test-ns", + }, + Spec: appsv1.CacheSpec{ + ExpireTime: "invalid-time", + }, + Status: appsv1.CacheStatus{}, + } + adapter := NewCacheAdapter(ctx, testCache, testlogger, mockClient, scheme, mockRecorder, ctrl.SetControllerReference) + assert.NotNil(t, adapter) + + res, err := adapter.CheckCacheExpiry(ctx) + assert.Nil(t, err) + assert.Equal(t, false, res.RequeueRequest) + assert.Equal(t, false, res.CancelRequest) + }) + }) +} + +func TestCacheEnsureCacheInitialized(t *testing.T) { + ctx := context.Background() + testlogger := log.FromContext(ctx) + scheme := runtime.NewScheme() + var ( + mockClientCtrl *gomock.Controller + mockClient *mockpkg.MockClient + mockRecorderCtrl *gomock.Controller + mockRecorder *mockpkg.MockEventRecorder + mockStatusWriterCtrl *gomock.Controller + mockStatusWriter *mockpkg.MockStatusWriter + ) + mockClientCtrl = gomock.NewController(t) + mockRecorderCtrl = gomock.NewController(t) + mockClient = mockpkg.NewMockClient(mockClientCtrl) + mockRecorder = mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusWriterCtrl = gomock.NewController(t) + mockStatusWriter = mockpkg.NewMockStatusWriter(mockStatusWriterCtrl) + + testApps := getTestApps() + testCacheKey := ctrlutils.NewCacheKeyFromApplications(testApps) + + t.Run("happy path", func(t *testing.T) { + testCache := &appsv1.Cache{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cache", + Namespace: "test-ns", + }, + Spec: appsv1.CacheSpec{ + OperationTemplate: appsv1.OperationSpec{ + Applications: testApps, + }, + }, + Status: appsv1.CacheStatus{}, + } + adapter := NewCacheAdapter(ctx, testCache, testlogger, mockClient, scheme, mockRecorder, ctrl.SetControllerReference) + assert.NotNil(t, adapter) + mockClient.EXPECT().Status().Return(mockStatusWriter) + mockStatusWriter.EXPECT().Update(ctx, gomock.Any()).Return(nil) + + res, err := adapter.EnsureCacheInitialized(ctx) + assert.Nil(t, err) + assert.Equal(t, false, res.RequeueRequest) + assert.Equal(t, testCache.Status.CacheKey, testCacheKey) + }) +} + +func TestCacheCalculateKeepAliveCount(t *testing.T) { + ctx := context.Background() + testlogger := log.FromContext(ctx) + scheme := runtime.NewScheme() + var ( + mockClientCtrl *gomock.Controller + mockClient *mockpkg.MockClient + mockRecorderCtrl *gomock.Controller + mockRecorder *mockpkg.MockEventRecorder + mockStatusWriterCtrl *gomock.Controller + mockStatusWriter *mockpkg.MockStatusWriter + ) + mockClientCtrl = gomock.NewController(t) + mockRecorderCtrl = gomock.NewController(t) + mockClient = mockpkg.NewMockClient(mockClientCtrl) + mockRecorder = mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusWriterCtrl = gomock.NewController(t) + mockStatusWriter = mockpkg.NewMockStatusWriter(mockStatusWriterCtrl) + testApps := getTestApps() + + t.Run("happy path", func(t *testing.T) { + testCache := &appsv1.Cache{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cache", + Namespace: "test-ns", + }, + Spec: appsv1.CacheSpec{ + OperationTemplate: appsv1.OperationSpec{ + Applications: testApps, + }, + }, + Status: appsv1.CacheStatus{}, + } + adapter := NewCacheAdapter(ctx, testCache, testlogger, mockClient, scheme, mockRecorder, ctrl.SetControllerReference) + assert.NotNil(t, adapter) + mockClient.EXPECT().Status().Return(mockStatusWriter) + mockStatusWriter.EXPECT().Update(ctx, gomock.Any()).Return(nil) + + res, err := adapter.CalculateKeepAliveCount(ctx) + assert.Nil(t, err) + assert.Equal(t, false, res.RequeueRequest) + assert.Equal(t, testCache.Status.KeepAliveCount, int32(5)) + }) +} + +func TestCacheAdjustCache(t *testing.T) { + ctx := context.Background() + testlogger := log.FromContext(ctx) + scheme := runtime.NewScheme() + var ( + mockClientCtrl *gomock.Controller + mockClient *mockpkg.MockClient + mockRecorderCtrl *gomock.Controller + mockRecorder *mockpkg.MockEventRecorder + mockStatusWriterCtrl *gomock.Controller + mockStatusWriter *mockpkg.MockStatusWriter + ) + mockClientCtrl = gomock.NewController(t) + mockRecorderCtrl = gomock.NewController(t) + mockClient = mockpkg.NewMockClient(mockClientCtrl) + mockRecorder = mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusWriterCtrl = gomock.NewController(t) + mockStatusWriter = mockpkg.NewMockStatusWriter(mockStatusWriterCtrl) + + testApps := getTestApps() + testCacheKey := ctrlutils.NewCacheKeyFromApplications(testApps) + + newOperation := &appsv1.Operation{ + Spec: appsv1.OperationSpec{ + Applications: testApps, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operation-new", + Namespace: "test-ns", + Labels: map[string]string{ctrlutils.LabelNameCacheKey: testCacheKey}, + }, + Status: appsv1.OperationStatus{ + Phase: oputils.PhaseEmpty, + }, + } + + availableOperation := &appsv1.Operation{ + Spec: appsv1.OperationSpec{ + Applications: testApps, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operation-available", + Namespace: "test-ns", + Labels: map[string]string{ctrlutils.LabelNameCacheKey: testCacheKey}, + }, + Status: appsv1.OperationStatus{ + Phase: oputils.PhaseReconciled, + }, + } + // TODO assert operation status + // assert.True(t, oputils.IsAvailable(ctx, availableOperation)) + + // TODO add operations in other statuses + + t.Run("cache balance = 0", func(t *testing.T) { + t.Run("happy path", func(t *testing.T) { + resOperations := appsv1.OperationList{Items: []appsv1.Operation{ + *newOperation.DeepCopy(), + *availableOperation.DeepCopy(), + *availableOperation.DeepCopy(), + }} + testCache := &appsv1.Cache{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cache", + Namespace: "test-ns", + }, + Spec: appsv1.CacheSpec{ + OperationTemplate: appsv1.OperationSpec{ + Applications: testApps, + }, + }, + Status: appsv1.CacheStatus{ + CacheKey: testCacheKey, + KeepAliveCount: 2, + }, + } + adapter := NewCacheAdapter(ctx, testCache, testlogger, mockClient, scheme, mockRecorder, ctrl.SetControllerReference) + assert.NotNil(t, adapter) + mockClient.EXPECT().List(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).SetArg(1, resOperations).Return(nil) + mockClient.EXPECT().Status().Return(mockStatusWriter) + mockStatusWriter.EXPECT().Update(ctx, gomock.Any()).Return(nil) + + res, err := adapter.AdjustCache(ctx) + assert.Nil(t, err) + assert.Equal(t, false, res.RequeueRequest) + assert.Equal(t, testCache.Status.AvailableCaches, []string{"test-operation-available", "test-operation-available"}) + }) + }) + + t.Run("cache balance > 0", func(t *testing.T) { + t.Run("happy path", func(t *testing.T) { + resOperationItems := []appsv1.Operation{ + *newOperation.DeepCopy(), + *availableOperation.DeepCopy(), + *availableOperation.DeepCopy(), + *availableOperation.DeepCopy(), + *availableOperation.DeepCopy(), + } + resOperations := appsv1.OperationList{Items: resOperationItems} + testCache := &appsv1.Cache{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cache", + Namespace: "test-ns", + }, + Spec: appsv1.CacheSpec{ + OperationTemplate: appsv1.OperationSpec{ + Applications: testApps, + }, + }, + Status: appsv1.CacheStatus{ + CacheKey: testCacheKey, + KeepAliveCount: 2, + }, + } + adapter := NewCacheAdapter(ctx, testCache, testlogger, mockClient, scheme, mockRecorder, ctrl.SetControllerReference) + assert.NotNil(t, adapter) + mockClient.EXPECT().List(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).SetArg(1, resOperations).Return(nil) + mockClient.EXPECT().Delete(ctx, gomock.Any()).Return(nil).Times(3) + mockClient.EXPECT().Status().Return(mockStatusWriter) + mockStatusWriter.EXPECT().Update(ctx, gomock.Any()).Return(nil) + + res, err := adapter.AdjustCache(ctx) + assert.Nil(t, err) + assert.Equal(t, false, res.RequeueRequest) + assert.Equal(t, testCache.Status.AvailableCaches, []string{"test-operation-available", "test-operation-available", "test-operation-available", "test-operation-available"}) + }) + }) + + t.Run("cache balance < 0", func(t *testing.T) { + t.Run("happy path", func(t *testing.T) { + resOperations := appsv1.OperationList{Items: []appsv1.Operation{ + *newOperation.DeepCopy(), + *availableOperation.DeepCopy(), + }} + testCache := &appsv1.Cache{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cache", + Namespace: "test-ns", + }, + Spec: appsv1.CacheSpec{ + OperationTemplate: appsv1.OperationSpec{ + Applications: testApps, + }, + }, + Status: appsv1.CacheStatus{ + CacheKey: testCacheKey, + KeepAliveCount: 3, + }, + } + adapter := NewCacheAdapter(ctx, testCache, testlogger, mockClient, scheme, mockRecorder, func(owner, controlled metav1.Object, scheme *runtime.Scheme, opts ...controllerutil.OwnerReferenceOption) error { + return nil + }) + assert.NotNil(t, adapter) + mockClient.EXPECT().List(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).SetArg(1, resOperations).Return(nil) + mockClient.EXPECT().Create(ctx, gomock.Any()).Return(nil).Times(1) + mockClient.EXPECT().Status().Return(mockStatusWriter) + mockStatusWriter.EXPECT().Update(ctx, gomock.Any()).Return(nil) + + res, err := adapter.AdjustCache(ctx) + assert.Nil(t, err) + assert.Equal(t, false, res.RequeueRequest) + assert.Equal(t, testCache.Status.AvailableCaches, []string{"test-operation-available"}) + }) + }) +} diff --git a/internal/controller/cache_controller.go b/internal/controller/cache_controller.go index 9b63bee..1309186 100644 --- a/internal/controller/cache_controller.go +++ b/internal/controller/cache_controller.go @@ -18,24 +18,36 @@ 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" +) + +const ( + defaultCacheCheckInterval = time.Second * 60 ) // CacheReconciler reconciles a Cache object type CacheReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + recorder record.EventRecorder } // +kubebuilder:rbac:groups=app.github.com,resources=caches,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=app.github.com,resources=caches/status,verbs=get;update;patch // +kubebuilder:rbac:groups=app.github.com,resources=caches/finalizers,verbs=update +// +kubebuilder:rbac:groups=app.github.com,resources=operations,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=app.github.com,resources=operations/status,verbs=get // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -45,19 +57,74 @@ type CacheReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/reconcile func (r *CacheReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = logf.FromContext(ctx) + logger := log.FromContext(ctx) + cache := &appsv1.Cache{} + if err := r.Get(ctx, req.NamespacedName, cache); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + adapter := NewCacheAdapter(ctx, cache, logger, r.Client, r.Scheme, r.recorder, ctrl.SetControllerReference) + return r.reconcileHandler(ctx, adapter) +} + +func (r *CacheReconciler) reconcileHandler(ctx context.Context, adapter CacheAdapterInterface) (ctrl.Result, error) { + logger := log.FromContext(ctx) - // TODO(user): your logic here + operations := []reconciler.ReconcileOperation{ + adapter.CheckCacheExpiry, + adapter.EnsureCacheInitialized, + adapter.CalculateKeepAliveCount, + adapter.AdjustCache, + } - return ctrl.Result{}, nil + for _, operation := range operations { + result, err := operation(ctx) + if err != nil || result.RequeueRequest { + logger.Error(err, "cache operation failed") + return ctrl.Result{RequeueAfter: result.RequeueDelay}, err + } + if result.CancelRequest { + logger.Info("cache reconcile canceled, requeue after 60 seconds") + return ctrl.Result{RequeueAfter: defaultCacheCheckInterval}, nil + } + } + logger.Info("cache reconcile completed, requeue after 60 seconds") + return ctrl.Result{RequeueAfter: defaultCacheCheckInterval}, nil +} + +var cacheOwnerKey = ".metadata.controller.cache" + +func cacheOperationIndexerFunc(obj client.Object) []string { + // grab the operation object, extract the owner... + operation := obj.(*appsv1.Operation) + owner := metav1.GetControllerOf(operation) + if owner == nil { + return nil + } + // ...make sure it's a Cache... + if owner.APIVersion != appsv1.GroupVersion.String() || owner.Kind != "Cache" { + return nil + } + // ...and if so, return it + return []string{owner.Name} } // SetupWithManager sets up the controller with the Manager. -func (r *CacheReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *CacheReconciler) SetupWithManager(mgr ctrl.Manager) error { // +gocover:ignore:block init controller + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &appsv1.Operation{}, cacheOwnerKey, cacheOperationIndexerFunc); err != nil { // +gocover:ignore:block init controller + return err + } + // +gocover:ignore:block init controller + r.recorder = mgr.GetEventRecorderFor("Cache") + return ctrl.NewControllerManagedBy(mgr). - For(&appv1.Cache{}). + For(&appsv1.Cache{}). + Owns(&appsv1.Operation{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: 50, + }). Named("cache"). Complete(r) } diff --git a/internal/controller/cache_controller_test.go b/internal/controller/cache_controller_test.go index 14a328f..539021d 100644 --- a/internal/controller/cache_controller_test.go +++ b/internal/controller/cache_controller_test.go @@ -18,67 +18,294 @@ package controller import ( "context" + "testing" + + // "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "k8s.io/client-go/kubernetes/scheme" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/Azure/operation-cache-controller/internal/controller/mocks" + "github.com/Azure/operation-cache-controller/internal/utils/reconciler" 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" ) -var _ = Describe("Cache Controller", func() { - Context("When reconciling a resource", func() { - const resourceName = "test-resource" +func TestReconcile(t *testing.T) { + ctx := context.Background() + + t.Run("get cache failed", func(t *testing.T) { + builder := fake.NewClientBuilder() + buildReconciler := CacheReconciler{ + Client: builder.Build(), + } + _, err := buildReconciler.Reconcile(ctx, ctrl.Request{}) + assert.Error(t, err) + }) +} + +func TestReconcileHandler(t *testing.T) { + ctx := context.Background() + + t.Run("reconcile successfully", func(t *testing.T) { + builder := fake.NewClientBuilder() + scheme := runtime.NewScheme() + + builder.WithScheme(scheme) + + cacheReconciler := CacheReconciler{ + Client: builder.Build(), + Scheme: scheme, + } + mockCacheAdapterCtrl := gomock.NewController(t) + cacheAdapter := mocks.NewMockCacheAdapterInterface(mockCacheAdapterCtrl) + cacheAdapter.EXPECT().CheckCacheExpiry(ctx).Return(reconciler.OperationResult{}, nil) + cacheAdapter.EXPECT().EnsureCacheInitialized(ctx).Return(reconciler.OperationResult{}, nil) + cacheAdapter.EXPECT().CalculateKeepAliveCount(ctx).Return(reconciler.OperationResult{}, nil) + cacheAdapter.EXPECT().AdjustCache(ctx).Return(reconciler.OperationResult{}, nil) + res, err := cacheReconciler.reconcileHandler(ctx, cacheAdapter) + assert.NoError(t, err) + assert.Equal(t, defaultCacheCheckInterval, res.RequeueAfter) + }) + + t.Run("reconcile canceled", func(t *testing.T) { + builder := fake.NewClientBuilder() + scheme := runtime.NewScheme() + + builder.WithScheme(scheme) - ctx := context.Background() + cacheReconciler := CacheReconciler{ + Client: builder.Build(), + Scheme: scheme, + } + mockCacheAdapterCtrl := gomock.NewController(t) + cacheAdapter := mocks.NewMockCacheAdapterInterface(mockCacheAdapterCtrl) + cacheAdapter.EXPECT().CheckCacheExpiry(ctx).Return(reconciler.OperationResult{CancelRequest: true}, nil) + res, err := cacheReconciler.reconcileHandler(ctx, cacheAdapter) + assert.NoError(t, err) + assert.Equal(t, defaultCacheCheckInterval, res.RequeueAfter) + }) + + t.Run("reconcile err", func(t *testing.T) { + builder := fake.NewClientBuilder() + scheme := runtime.NewScheme() + + builder.WithScheme(scheme) - typeNamespacedName := types.NamespacedName{ - Name: resourceName, - Namespace: "default", // TODO(user):Modify as needed + cacheReconciler := CacheReconciler{ + Client: builder.Build(), + Scheme: scheme, } - cache := &appv1.Cache{} - - BeforeEach(func() { - By("creating the custom resource for the Kind Cache") - err := k8sClient.Get(ctx, typeNamespacedName, cache) - if err != nil && errors.IsNotFound(err) { - resource := &appv1.Cache{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, - Namespace: "default", + mockCacheAdapterCtrl := gomock.NewController(t) + cacheAdapter := mocks.NewMockCacheAdapterInterface(mockCacheAdapterCtrl) + cacheAdapter.EXPECT().CheckCacheExpiry(ctx).Return(reconciler.OperationResult{}, assert.AnError) + _, err := cacheReconciler.reconcileHandler(ctx, cacheAdapter) + assert.NotNil(t, err) + }) +} + +func TestCacheOperationIndexerFunc(t *testing.T) { + t.Run("with Cache owner", func(t *testing.T) { + // Create an operation with a Cache owner + operation := &appsv1.Operation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operation", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: appsv1.GroupVersion.String(), + Kind: "Cache", + Name: "test-cache", + Controller: func() *bool { b := true; return &b }(), }, - // TODO(user): Specify other spec details if needed. - } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - } - }) + }, + }, + } - AfterEach(func() { - // TODO(user): Cleanup logic after each test, like removing the resource instance. - resource := &appv1.Cache{} - err := k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) + // Call the indexer function + result := cacheOperationIndexerFunc(operation) - By("Cleanup the specific resource instance Cache") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - }) - It("should successfully reconcile the resource", func() { - By("Reconciling the created resource") - controllerReconciler := &CacheReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), - } - - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, + // Verify the result + assert.Equal(t, []string{"test-cache"}, result) + }) + + t.Run("with no owner", func(t *testing.T) { + // Create an operation with no owner + operation := &appsv1.Operation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operation", + }, + } + + // Call the indexer function + result := cacheOperationIndexerFunc(operation) + + // Verify the result + assert.Nil(t, result) + }) + + t.Run("with non-Cache owner", func(t *testing.T) { + // Create an operation with a non-Cache owner + operation := &appsv1.Operation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operation", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: appsv1.GroupVersion.String(), + Kind: "Requirement", + Name: "test-requirement", + Controller: func() *bool { b := true; return &b }(), + }, + }, + }, + } + + // Call the indexer function + result := cacheOperationIndexerFunc(operation) + + // Verify the result + assert.Nil(t, result) + }) + + t.Run("with non-controller owner", func(t *testing.T) { + // Create an operation with a non-controller owner reference + operation := &appsv1.Operation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operation", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: appsv1.GroupVersion.String(), + Kind: "Cache", + Name: "test-cache", + Controller: func() *bool { b := false; return &b }(), + }, + }, + }, + } + + // Call the indexer function + result := cacheOperationIndexerFunc(operation) + + // Verify the result + assert.Nil(t, result) + }) + + t.Run("with different API version", func(t *testing.T) { + // Create an operation with a different API version + operation := &appsv1.Operation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operation", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "different.group/v1", + Kind: "Cache", + Name: "test-cache", + Controller: func() *bool { b := true; return &b }(), + }, + }, + }, + } + + // Call the indexer function + result := cacheOperationIndexerFunc(operation) + + // Verify the result + assert.Nil(t, result) + }) +} + +var _ = Describe("Cache Controller", func() { + Context("When setupWithManager is called", func() { + It("should set up 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()) - // 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. + + err = (&CacheReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + recorder: k8sManager.GetEventRecorderFor("appdeployment-controller"), + }).SetupWithManager(k8sManager) + + Expect(err).NotTo(HaveOccurred()) }) }) + // TODO: need to figure out how to test the controller with indexer + // Context("When reconciling a resource", func() { + // const resourceName = "test-resource" + + // ctx := context.Background() + + // typeNamespacedName := types.NamespacedName{ + // Name: resourceName, + // Namespace: "default", // TODO(user):Modify as needed + // } + // cache := &appv1.Cache{} + + // BeforeEach(func() { + // By("creating the custom resource for the Kind Cache") + // err := k8sClient.Get(ctx, typeNamespacedName, cache) + // if err != nil && errors.IsNotFound(err) { + // resource := &appv1.Cache{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: resourceName, + // Namespace: "default", + // }, + // Spec: appv1.CacheSpec{ + // OperationTemplate: appv1.OperationSpec{ + // Applications: []appv1.ApplicationSpec{ + // { + // Name: "app1", + // Provision: newTestJobSpec(), + // Teardown: newTestJobSpec(), + // }, + // }, + // }, + // // Format time in UTC with Z suffix + // ExpireTime: time.Now().UTC().Add(1 * time.Hour).Format("2006-01-02T15:04:05Z"), + // }, + // } + // Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + // } + // }) + + // AfterEach(func() { + // // TODO(user): Cleanup logic after each test, like removing the resource instance. + // resource := &appv1.Cache{} + // err := k8sClient.Get(ctx, typeNamespacedName, resource) + // Expect(err).NotTo(HaveOccurred()) + + // By("Cleanup the specific resource instance Cache") + // Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + // }) + // It("should successfully reconcile the resource", func() { + // By("Reconciling the created resource") + // controllerReconciler := &CacheReconciler{ + // Client: k8sClient, + // Scheme: k8sClient.Scheme(), + // } + + // _, err := controllerReconciler.Reconcile(ctx, reconcile.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. + // }) + // }) }) diff --git a/internal/controller/mocks/mock_cache_adapter.go b/internal/controller/mocks/mock_cache_adapter.go new file mode 100644 index 0000000..8888728 --- /dev/null +++ b/internal/controller/mocks/mock_cache_adapter.go @@ -0,0 +1,102 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/Azure/operation-cache-controller/internal/controller (interfaces: CacheAdapterInterface) +// +// Generated by this command: +// +// mockgen -destination=./mocks/mock_cache_adapter.go -package=mocks github.com/Azure/operation-cache-controller/internal/controller CacheAdapterInterface +// + +// 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" +) + +// MockCacheAdapterInterface is a mock of CacheAdapterInterface interface. +type MockCacheAdapterInterface struct { + ctrl *gomock.Controller + recorder *MockCacheAdapterInterfaceMockRecorder + isgomock struct{} +} + +// MockCacheAdapterInterfaceMockRecorder is the mock recorder for MockCacheAdapterInterface. +type MockCacheAdapterInterfaceMockRecorder struct { + mock *MockCacheAdapterInterface +} + +// NewMockCacheAdapterInterface creates a new mock instance. +func NewMockCacheAdapterInterface(ctrl *gomock.Controller) *MockCacheAdapterInterface { + mock := &MockCacheAdapterInterface{ctrl: ctrl} + mock.recorder = &MockCacheAdapterInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCacheAdapterInterface) EXPECT() *MockCacheAdapterInterfaceMockRecorder { + return m.recorder +} + +// AdjustCache mocks base method. +func (m *MockCacheAdapterInterface) AdjustCache(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AdjustCache", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AdjustCache indicates an expected call of AdjustCache. +func (mr *MockCacheAdapterInterfaceMockRecorder) AdjustCache(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AdjustCache", reflect.TypeOf((*MockCacheAdapterInterface)(nil).AdjustCache), ctx) +} + +// CalculateKeepAliveCount mocks base method. +func (m *MockCacheAdapterInterface) CalculateKeepAliveCount(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CalculateKeepAliveCount", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CalculateKeepAliveCount indicates an expected call of CalculateKeepAliveCount. +func (mr *MockCacheAdapterInterfaceMockRecorder) CalculateKeepAliveCount(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateKeepAliveCount", reflect.TypeOf((*MockCacheAdapterInterface)(nil).CalculateKeepAliveCount), ctx) +} + +// CheckCacheExpiry mocks base method. +func (m *MockCacheAdapterInterface) CheckCacheExpiry(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckCacheExpiry", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckCacheExpiry indicates an expected call of CheckCacheExpiry. +func (mr *MockCacheAdapterInterfaceMockRecorder) CheckCacheExpiry(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckCacheExpiry", reflect.TypeOf((*MockCacheAdapterInterface)(nil).CheckCacheExpiry), ctx) +} + +// EnsureCacheInitialized mocks base method. +func (m *MockCacheAdapterInterface) EnsureCacheInitialized(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureCacheInitialized", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureCacheInitialized indicates an expected call of EnsureCacheInitialized. +func (mr *MockCacheAdapterInterfaceMockRecorder) EnsureCacheInitialized(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureCacheInitialized", reflect.TypeOf((*MockCacheAdapterInterface)(nil).EnsureCacheInitialized), ctx) +} diff --git a/internal/controller/operation_controller.go b/internal/controller/operation_controller.go index fbe8abe..3e8bda6 100644 --- a/internal/controller/operation_controller.go +++ b/internal/controller/operation_controller.go @@ -19,7 +19,6 @@ package controller import ( "context" - batchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" @@ -111,7 +110,7 @@ func (r *OperationReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&appsv1.Operation{}). - Owns(&batchv1.Job{}). + Owns(&appsv1.AppDeployment{}). WithOptions(controller.Options{ MaxConcurrentReconciles: 100, }). diff --git a/internal/utils/controller/cache/cache.go b/internal/utils/controller/cache/cache.go new file mode 100644 index 0000000..c40964e --- /dev/null +++ b/internal/utils/controller/cache/cache.go @@ -0,0 +1,21 @@ +package cache + +import ( + "math/rand" + "time" + + appsv1 "github.com/Azure/operation-cache-controller/api/v1" +) + +func RandomSelectCachedOperation(cache *appsv1.Cache) string { + if len(cache.Status.AvailableCaches) == 0 { + return "" + } + // nolint:gosec, G404 // this is expected PRNG usage + return cache.Status.AvailableCaches[rand.Intn(len(cache.Status.AvailableCaches))] +} + +func DefaultCacheExpireTime() string { + // cache expire after 2 hours + return time.Now().Add(2 * time.Hour).Format(time.RFC3339) +} diff --git a/internal/utils/controller/cache/cache_test.go b/internal/utils/controller/cache/cache_test.go new file mode 100644 index 0000000..8273c5c --- /dev/null +++ b/internal/utils/controller/cache/cache_test.go @@ -0,0 +1,45 @@ +package cache + +import ( + "testing" + + "github.com/stretchr/testify/require" + + appsv1 "github.com/Azure/operation-cache-controller/api/v1" +) + +func TestRandomSelectCachedOperation(t *testing.T) { + tests := []struct { + name string + caches []string + expectEmpty bool + }{ + {"empty caches", nil, true}, + {"non-empty caches", []string{"cache1", "cache2", "cache3"}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // ...existing setup code if any... + cacheInstance := &appsv1.Cache{ + Status: appsv1.CacheStatus{ + AvailableCaches: tt.caches, + }, + } + + result := RandomSelectCachedOperation(cacheInstance) + + if tt.expectEmpty { + require.Equal(t, "", result) + } else { + require.Contains(t, tt.caches, result) + } + // ...existing teardown code if any... + }) + } +} + +func TestDefaultCacheExpireTime(t *testing.T) { + result := DefaultCacheExpireTime() + require.NotEmpty(t, result) +} diff --git a/internal/utils/controller/const.go b/internal/utils/controller/const.go new file mode 100644 index 0000000..0f34cfd --- /dev/null +++ b/internal/utils/controller/const.go @@ -0,0 +1,12 @@ +package controller + +const ( + LabelNameCacheKey = "github.com/Azure/operation-cache-controller/cache-key" +) + +const ( + AnnotationNameCacheMode = "github.com/Azure/operation-cache-controller/cache-mode" + AnnotationNameCacheKey = "github.com/Azure/operation-cache-controller/cache-key-annotation" + AnnotationValueTrue = "true" + AnnotationValueFalse = "false" +) diff --git a/internal/utils/controller/rand_string.go b/internal/utils/controller/rand_string.go new file mode 100644 index 0000000..2c1e3ff --- /dev/null +++ b/internal/utils/controller/rand_string.go @@ -0,0 +1,16 @@ +package controller + +import ( + "math/rand" +) + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +// GenerateRandomString generates a random string of length n +func GenerateRandomString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return string(b) +} diff --git a/internal/utils/controller/rand_string_test.go b/internal/utils/controller/rand_string_test.go new file mode 100644 index 0000000..9a12a27 --- /dev/null +++ b/internal/utils/controller/rand_string_test.go @@ -0,0 +1,80 @@ +package controller + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerateRandomString(t *testing.T) { + tests := []struct { + name string + length int + want struct { + length int + charset string + } + }{ + { + name: "generate string of length 0", + length: 0, + want: struct { + length int + charset string + }{ + length: 0, + charset: letterBytes, + }, + }, + { + name: "generate string of length 10", + length: 10, + want: struct { + length int + charset string + }{ + length: 10, + charset: letterBytes, + }, + }, + { + name: "generate string of length 32", + length: 32, + want: struct { + length int + charset string + }{ + length: 32, + charset: letterBytes, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Generate multiple strings to ensure randomness + generated := make(map[string]bool) + for i := 0; i < 100; i++ { + result := GenerateRandomString(tt.length) + + // Check length + assert.Equal(t, tt.want.length, len(result), "generated string length should match requested length") + + // Check that string only contains valid characters + for _, char := range result { + assert.Contains(t, tt.want.charset, string(char), "generated string should only contain valid characters") + } + + // Track uniqueness + generated[result] = true + } + + // For non-zero length strings, check that we're getting different values (randomness check) + if tt.length > 0 { + // With 100 generations, we should get at least 95 unique strings for any non-zero length + // This is a probabilistic test, but the chance of failure is extremely low with proper randomness + assert.Greater(t, len(generated), 95, "should generate mostly unique strings") + } + }) + } +} diff --git a/internal/utils/ptr/ptr_test.go b/internal/utils/ptr/ptr_test.go index 08a7e28..774433c 100644 --- a/internal/utils/ptr/ptr_test.go +++ b/internal/utils/ptr/ptr_test.go @@ -68,12 +68,43 @@ func TestDeref(t *testing.T) { input: false, expected: false, }, + { + name: "nil string pointer", + input: (*string)(nil), + expected: "", + }, + { + name: "nil int pointer", + input: (*int)(nil), + expected: 0, + }, + { + name: "nil bool pointer", + input: (*bool)(nil), + expected: false, + }, + { + name: "nil float pointer", + input: (*float64)(nil), + expected: 0.0, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - assert.Equal(Deref(Of(tt.input)), tt.expected) + switch v := tt.input.(type) { + case *string: + assert.Equal(Deref(v), tt.expected) + case *int: + assert.Equal(Deref(v), tt.expected) + case *bool: + assert.Equal(Deref(v), tt.expected) + case *float64: + assert.Equal(Deref(v), tt.expected) + default: + assert.Equal(Deref(Of(tt.input)), tt.expected) + } }) } }