diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 169aa591e..75e319092 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,9 +10,10 @@ jobs: - uses: actions/setup-go@v3 with: go-version: "1.21" - - uses: golangci/golangci-lint-action@v3 + - uses: golangci/golangci-lint-action@v6 with: - version: v1.56 + version: v1.59 + args: --timeout=10m - run: make test integration: @@ -21,8 +22,8 @@ jobs: fail-fast: true matrix: k8s_version: - - 1.24.13 # current version at GKE stable channel (https://cloud.google.com/kubernetes-engine/docs/release-notes#current_versions) - - 1.25.8 # current version at GKE regular channel (https://cloud.google.com/kubernetes-engine/docs/release-notes#current_versions) + - 1.27.11 # current version at GKE stable channel (https://cloud.google.com/kubernetes-engine/docs/release-notes#current_versions) + - 1.28.7 # current version at GKE regular channel (https://cloud.google.com/kubernetes-engine/docs/release-notes#current_versions) steps: - uses: actions/checkout@master - name: Running up Kubernetes (using Minikube) diff --git a/api/v1alpha1/rpaasinstance.go b/api/v1alpha1/rpaasinstance.go index c48607a24..ae233a7d8 100644 --- a/api/v1alpha1/rpaasinstance.go +++ b/api/v1alpha1/rpaasinstance.go @@ -10,7 +10,11 @@ import ( ) const ( - DefaultLabelKeyPrefix = "rpaas.extensions.tsuru.io" + DefaultLabelKeyPrefix = "rpaas.extensions.tsuru.io" + + RpaasOperatorValidationNameLabelKey = DefaultLabelKeyPrefix + "/validation-name" + RpaasOperatorValidationHashAnnotationKey = DefaultLabelKeyPrefix + "/validation-hash" + RpaasOperatorInstanceNameLabelKey = DefaultLabelKeyPrefix + "/instance-name" RpaasOperatorServiceNameLabelKey = DefaultLabelKeyPrefix + "/service-name" RpaasOperatorPlanNameLabelKey = DefaultLabelKeyPrefix + "/plan-name" @@ -123,3 +127,13 @@ func mergeMap(a, b map[string]string) map[string]string { } return a } + +func (i *RpaasValidation) BelongsToCluster(clusterName string) bool { + instanceCluster := i.Labels[RpaasOperatorClusterNameLabelKey] + + if instanceCluster == "" { + return false + } + + return clusterName == instanceCluster +} diff --git a/api/v1alpha1/rpaasvalidation_types.go b/api/v1alpha1/rpaasvalidation_types.go new file mode 100644 index 000000000..68ac52d2e --- /dev/null +++ b/api/v1alpha1/rpaasvalidation_types.go @@ -0,0 +1,50 @@ +// Copyright 2024 tsuru authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +kubebuilder:object:root=true +// +kubebuilder:resource:shortName=rpaas-validation +// +kubebuilder:subresource:status +// RpaasInstance is the Schema for the rpaasinstances API +type RpaasValidation struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Status RpaasValidationStatus `json:"status,omitempty"` + // Spec reuse the same properties of RpaasInstance, just to avoid duplication of code + Spec RpaasInstanceSpec `json:"spec,omitempty"` +} + +// RpaasValidationStatus defines the observed state of RpaasValidation +type RpaasValidationStatus struct { + //Revision hash calculated for the current spec of rpaasvalidation + RevisionHash string `json:"revisionHash,omitempty"` + + // The most recent generation observed by the rpaas operator controller. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Valid determines whether validation is valid + Valid *bool `json:"valid,omitempty"` + + // Feedback of validation of nginx + Error string `json:"error,omitempty"` +} + +// +kubebuilder:object:root=true + +// RpaasValidationList contains a list of RpaasInstance +type RpaasValidationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RpaasValidation `json:"items"` +} + +func init() { + SchemeBuilder.Register(&RpaasValidation{}, &RpaasValidationList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e99e4e95c..0bf07c1b2 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -736,6 +736,85 @@ func (in *RpaasPlanSpec) DeepCopy() *RpaasPlanSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RpaasValidation) DeepCopyInto(out *RpaasValidation) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RpaasValidation. +func (in *RpaasValidation) DeepCopy() *RpaasValidation { + if in == nil { + return nil + } + out := new(RpaasValidation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RpaasValidation) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RpaasValidationList) DeepCopyInto(out *RpaasValidationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RpaasValidation, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RpaasValidationList. +func (in *RpaasValidationList) DeepCopy() *RpaasValidationList { + if in == nil { + return nil + } + out := new(RpaasValidationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RpaasValidationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RpaasValidationStatus) DeepCopyInto(out *RpaasValidationStatus) { + *out = *in + if in.Valid != nil { + in, out := &in.Valid, &out.Valid + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RpaasValidationStatus. +func (in *RpaasValidationStatus) DeepCopy() *RpaasValidationStatus { + if in == nil { + return nil + } + out := new(RpaasValidationStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScheduledWindow) DeepCopyInto(out *ScheduledWindow) { *out = *in diff --git a/cmd/plugin/rpaasv2/cmd/autoscale_test.go b/cmd/plugin/rpaasv2/cmd/autoscale_test.go index fc02487ef..da4313a7a 100644 --- a/cmd/plugin/rpaasv2/cmd/autoscale_test.go +++ b/cmd/plugin/rpaasv2/cmd/autoscale_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" "github.com/tsuru/rpaas-operator/pkg/rpaas/client/autogenerated" ) @@ -71,7 +71,7 @@ max replicas: 5 Schedules: []autogenerated.ScheduledWindow{ {MinReplicas: 1, Start: "00 08 * * 1-5", End: "00 20 * * 1-5"}, {MinReplicas: 5, Start: "00 20 * * 2", End: "00 01 * * 3"}, - {MinReplicas: 5, Start: "00 22 * * 0", End: "00 02 * * 1", Timezone: pointer.String("America/Chile")}, + {MinReplicas: 5, Start: "00 22 * * 0", End: "00 02 * * 1", Timezone: ptr.To("America/Chile")}, }, }) }), diff --git a/config/crd/bases/extensions.tsuru.io_rpaasvalidations.yaml b/config/crd/bases/extensions.tsuru.io_rpaasvalidations.yaml new file mode 100644 index 000000000..3de4f06ee --- /dev/null +++ b/config/crd/bases/extensions.tsuru.io_rpaasvalidations.yaml @@ -0,0 +1,6460 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: rpaasvalidations.extensions.tsuru.io +spec: + group: extensions.tsuru.io + names: + kind: RpaasValidation + listKind: RpaasValidationList + plural: rpaasvalidations + shortNames: + - rpaas-validation + singular: rpaasvalidation + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: RpaasInstance is the Schema for the rpaasinstances API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec reuse the same properties of RpaasInstance, just to + avoid duplication of code + properties: + allowedUpstreams: + description: AllowedUpstreams holds the endpoints to which the RpaasInstance + should be able to access + items: + properties: + host: + type: string + port: + type: integer + type: object + type: array + autoscale: + description: Autoscale holds the infos used to configure the HorizontalPodAutoscaler + for this instance. + properties: + kedaOptions: + description: KEDAOptions defines the options used when creating + autoscaling resources via KEDA's API. + properties: + enabled: + description: Enabled whether should use KEDA as the autoscaling + controller. + type: boolean + pollingInterval: + description: PollingInterval is the interval in seconds to + check each trigger on. + format: int32 + type: integer + prometheusServerAddress: + description: PrometheusServerAddress is the Prometheus server + (URL) address. Mandatory if Enabled field is true. + type: string + rpsAuthenticationRef: + description: RPSAuthenticationRef is the reference to KEDA's + authentication resource regarding the request per second + trigger. + properties: + kind: + description: Kind of the resource being referred to. Defaults + to TriggerAuthentication. + type: string + name: + type: string + required: + - name + type: object + rpsQueryTemplate: + description: RPSQueryTemplate is a gotemplate used to define + the requests per second Prometheus query. Mandatory if Enabled + field is true. + type: string + timezone: + description: Timezone is a zone name registered on IANA time + zone database, e.g. "America/Sao_Paulo". + type: string + type: object + maxReplicas: + description: MaxReplicas is the upper limit for the number of + replicas that can be set by the HorizontalPodAutoscaler. + format: int32 + type: integer + minReplicas: + description: MinReplicas is the lower limit for the number of + replicas that can be set by the HorizontalPodAutoscaler. Defaults + to the RpaasInstance replicas value. + format: int32 + type: integer + schedules: + description: Schedules are the time windows where the minimum + replica count should change. + items: + properties: + end: + description: End is a Cron expression indicating the end + of the scheduled window. + type: string + minReplicas: + description: MinReplicas is the minimum replica count set + while the scheduled window is active. + format: int32 + type: integer + start: + description: Start is a Cron expression indicating the start + of the shedule window. + type: string + timezone: + description: Timezone is a zone name registered on IANA + time zone database, e.g. "America/Sao_Paulo". + type: string + required: + - end + - minReplicas + - start + type: object + type: array + targetCPUUtilizationPercentage: + description: TargetCPUUtilizationPercentage is the target average + CPU utilization over all the pods. Represented as a percentage + of requested CPU, e.g. int32(80) equals to 80%. + format: int32 + type: integer + targetMemoryUtilizationPercentage: + description: TargetMemoryUtilizationPercentage is the target average + memory utilization over all the pods. Represented as a percentage + of requested memory, e.g. int32(80) equals to 80%. + format: int32 + type: integer + targetRequestsPerSecond: + description: TargetRequestsPerSecond is the target rate of HTTP + requests (in a second) pods should keep before scaling up/down + pods. + format: int32 + type: integer + required: + - maxReplicas + type: object + binds: + description: Binds is the list of apps bounded to the instance + items: + properties: + host: + type: string + name: + type: string + required: + - host + - name + type: object + type: array + blocks: + additionalProperties: + properties: + value: + type: string + valueFrom: + properties: + configMapKeyRef: + description: Selects a key from a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key + must be defined + type: boolean + required: + - key + type: object + namespace: + type: string + type: object + type: object + description: Blocks are configuration file fragments added to the + generated nginx config. + type: object + configHistoryLimit: + description: The number of old Configs to retain to allow rollback. + type: integer + dns: + description: DNS Configuration for the current flavor + properties: + ttl: + description: TTL is the DNS entry time to live in seconds (default + is 60s) + format: int32 + type: integer + zone: + description: Zone is the suffix which all DNS entries will be + formed with using the rule `instance_name.zone` + type: string + required: + - zone + type: object + dynamicCertificates: + description: DynamicCertificates enables automatic issuing and renewal + for TLS certificates. + properties: + certManager: + description: CertManager contains specific configurations to enable + Cert Manager integration. + properties: + dnsNames: + description: DNSNames is a list of DNS names to be set in + Subject Alternative Names. + items: + type: string + type: array + dnsNamesDefault: + description: DNSNamesDefault when is set use the provided + DNSName from DNS Zone field. + type: boolean + ipAddresses: + description: IPAddresses is a list of IP addresses to be set + in Subject Alternative Names. + items: + type: string + type: array + issuer: + description: "Issuer refers either to Issuer or ClusterIssuer + resource. \n NOTE: when there's no Issuer on this name, + it tries using ClusterIssuer instead." + type: string + type: object + certManagerRequests: + description: CertManagerRequests is similar to CertManager field + but for several requests. + items: + properties: + dnsNames: + description: DNSNames is a list of DNS names to be set in + Subject Alternative Names. + items: + type: string + type: array + dnsNamesDefault: + description: DNSNamesDefault when is set use the provided + DNSName from DNS Zone field. + type: boolean + ipAddresses: + description: IPAddresses is a list of IP addresses to be + set in Subject Alternative Names. + items: + type: string + type: array + issuer: + description: "Issuer refers either to Issuer or ClusterIssuer + resource. \n NOTE: when there's no Issuer on this name, + it tries using ClusterIssuer instead." + type: string + type: object + type: array + type: object + enablePodDisruptionBudget: + description: "EnablePodDisruptionBudget defines whether a PodDisruptionBudget + should be attached to Nginx or not. Defaults to disabled. \n If + enabled, PDB's min available is calculated as: minAvailable = floor(N + * 90%), where N: - rpaasinstance.spec.autoscale.minReplicas (if + set and less than maxReplicas); - rpaasinstance.spec.autoscale.maxReplicas + (if set); - rpaasinstance.spec.replicas (if set); - zero, otherwise." + type: boolean + extraFiles: + description: "ExtraFiles points to a ConfigMap where the files are + stored. \n Deprecated: ExtraFiles stores all files in a single ConfigMap. + In other words, they share the limit of 1MiB due to etcd limitations. + In order to get around it, use the Field field." + properties: + files: + additionalProperties: + type: string + description: Files maps each key entry from the ConfigMap to its + relative location on the nginx filesystem. + type: object + name: + description: Name points to a ConfigMap resource (in the same + namespace) which holds the files. + type: string + required: + - name + type: object + files: + description: Files is a list of regular files of general purpose to + be mounted on Nginx pods. As ConfigMap stores the file content, + a file cannot exceed 1MiB. + items: + properties: + configMap: + description: ConfigMap is a reference to ConfigMap in the namespace + that contains the file content. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must + be defined + type: boolean + required: + - key + type: object + name: + description: Name is the filaname of the file. + type: string + required: + - name + type: object + type: array + flavors: + description: Flavors are references to RpaasFlavors resources. When + provided, each flavor merges its instance template spec with this + instance spec. + items: + type: string + type: array + ingress: + description: Ingress defines a minimal set of configurations to expose + the instance over an Ingress. + properties: + annotations: + additionalProperties: + type: string + description: Annotations are extra annotations for the Ingress + resource. + type: object + ingressClassName: + description: IngressClassName is the class to be set on Ingress. + type: string + labels: + additionalProperties: + type: string + description: Labels are extra labels for the Ingress resource. + type: object + type: object + lifecycle: + description: Lifecycle describes actions that should be executed when + some event happens to nginx container. + properties: + postStart: + properties: + exec: + description: ExecAction describes a "run in container" action. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: + type: string + type: array + type: object + type: object + preStop: + properties: + exec: + description: ExecAction describes a "run in container" action. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: + type: string + type: array + type: object + type: object + type: object + locations: + description: Locations hold paths that can be configured to forward + resquests to one destination app or include raw NGINX configurations + itself. + items: + properties: + content: + properties: + value: + type: string + valueFrom: + properties: + configMapKeyRef: + description: Selects a key from a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + namespace: + type: string + type: object + type: object + destination: + type: string + forceHTTPS: + type: boolean + path: + type: string + required: + - path + type: object + type: array + planName: + description: PlanName is the name of the rpaasplan instance. + type: string + planNamespace: + description: PlanNamespace is the namespace of target plan and their + flavors, when empty uses the same namespace of instance. + type: string + planTemplate: + description: PlanTemplate allow overriding fields in the specified + plan. + properties: + config: + description: Config defines some NGINX configurations values that + can be used in the configuration template. + properties: + cacheEnabled: + type: boolean + cacheInactive: + type: string + cacheLoaderFiles: + type: integer + cachePath: + type: string + cacheSize: + 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 + cacheZoneSize: + 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 + httpListenOptions: + type: string + httpsListenOptions: + type: string + logAdditionalFields: + additionalProperties: + type: string + type: object + logAdditionalHeaders: + items: + type: string + type: array + logFormat: + type: string + logFormatEscape: + type: string + logFormatName: + type: string + mapHashBucketSize: + type: integer + mapHashMaxSize: + type: integer + resolverAddresses: + items: + type: string + type: array + resolverTTL: + type: string + syslogEnabled: + type: boolean + syslogFacility: + type: string + syslogServerAddress: + type: string + syslogTag: + type: string + templateExtraVars: + additionalProperties: + type: string + type: object + upstreamKeepalive: + type: integer + user: + type: string + vtsEnabled: + type: boolean + vtsStatusHistogramBuckets: + type: string + workerConnections: + type: integer + workerProcesses: + type: integer + type: object + default: + description: Default indicates whether plan is default. + type: boolean + description: + description: Description describes the plan. + type: string + image: + description: Image is the NGINX container image name. Defaults + to Nginx image value. + type: string + resources: + description: Resources requirements to be set on the NGINX container. + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in + pod.spec.resourceClaims of the Pod where this field + is used. It makes that resource available inside a + container. + 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 + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + 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 + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + template: + description: Template contains the main NGINX configuration template. + properties: + value: + type: string + valueFrom: + properties: + configMapKeyRef: + description: Selects a key from a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + namespace: + type: string + type: object + type: object + type: object + podTemplate: + description: PodTemplate used to configure the NGINX pod template. + properties: + affinity: + description: Affinity to be set on the nginx pod. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + annotations: + additionalProperties: + type: string + description: Annotations are custom annotations to be set into + Pod. + type: object + containerSecurityContext: + description: ContainerSecurityContext configures security attributes + for the nginx container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + containers: + description: Containers are executed in parallel to the main nginx + container + items: + description: A single application container that you want to + run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The container + image''s CMD is used if this is not provided. Variable + references $(VAR_NAME) are expanded using the container''s + environment. If a variable cannot be resolved, the reference + in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string literal + "$(VAR_NAME)". Escaped references will never be expanded, + regardless of whether the variable exists or not. Cannot + be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The container image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which + allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The Pod''s termination + grace period countdown begins before the PreStop hook + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period (unless delayed by + finalizers). Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is a beta field and requires enabling GRPCContainerProbe + feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Not specifying a port here DOES NOT prevent that port + from being exposed. Any port which is listening on the + default "0.0.0.0" address inside a container will be accessible + from the network. Modifying this array with strategic + merge patch may corrupt the data. For more information + See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is a beta field and requires enabling GRPCContainerProbe + feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable." + items: + description: ResourceClaim references one entry in + PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where + this field is used. It makes that resource available + inside a container. + 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 + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + 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 + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options + the container should be run with. If set, the fields of + SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that + this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is + windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored + by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the + Pod. All of a Pod's containers must have the same + effective HostProcess value (it is not allowed + to have a mix of HostProcess containers and non-HostProcess + containers). In addition, if HostProcess is true + then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed + until this completes successfully. If this probe fails, + the Pod will be restarted, just as if the livenessProbe + failed. This can be used to provide different probe parameters + at the beginning of a Pod''s lifecycle, when it might + take a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is a beta field and requires enabling GRPCContainerProbe + feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer + for stdin in the container runtime. If this is not set, + reads from stdin in the container will always result in + EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close + the stdin channel after it has been opened by a single + attach. When stdin is true the stdin stream will remain + open across multiple attach sessions. If stdinOnce is + set to true, stdin is opened on container start, is empty + until the first client attaches to stdin, and then remains + open and accepts data until the client disconnects, at + which time stdin is closed and remains closed until the + container is restarted. If this flag is false, a container + processes that reads from stdin will never receive an + EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which + the container''s termination message will be written is + mounted into the container''s filesystem. Message written + is intended to be brief final status, such as an assertion + failure message. Will be truncated by the node if greater + than 4096 bytes. The total message length across all containers + will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should + be populated. File will use the contents of terminationMessagePath + to populate the container status message on both success + and failure. FallbackToLogsOnError will use the last chunk + of container log output if the termination message file + is empty and the container exited with an error. The log + output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY + for itself, also requires 'stdin' to be true. Default + is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a raw + block device within a container. + properties: + devicePath: + description: devicePath is the path inside of the + container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. Cannot be updated. + type: string + required: + - name + type: object + type: array + hostNetwork: + description: HostNetwork enabled causes the pod to use the host's + network namespace. + type: boolean + initContainers: + description: 'InitContainers are executed in order prior to containers + being started More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/' + items: + description: A single application container that you want to + run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The container + image''s CMD is used if this is not provided. Variable + references $(VAR_NAME) are expanded using the container''s + environment. If a variable cannot be resolved, the reference + in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string literal + "$(VAR_NAME)". Escaped references will never be expanded, + regardless of whether the variable exists or not. Cannot + be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The container image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which + allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The Pod''s termination + grace period countdown begins before the PreStop hook + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period (unless delayed by + finalizers). Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is a beta field and requires enabling GRPCContainerProbe + feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Not specifying a port here DOES NOT prevent that port + from being exposed. Any port which is listening on the + default "0.0.0.0" address inside a container will be accessible + from the network. Modifying this array with strategic + merge patch may corrupt the data. For more information + See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is a beta field and requires enabling GRPCContainerProbe + feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable." + items: + description: ResourceClaim references one entry in + PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where + this field is used. It makes that resource available + inside a container. + 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 + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + 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 + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options + the container should be run with. If set, the fields of + SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that + this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is + windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored + by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the + Pod. All of a Pod's containers must have the same + effective HostProcess value (it is not allowed + to have a mix of HostProcess containers and non-HostProcess + containers). In addition, if HostProcess is true + then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed + until this completes successfully. If this probe fails, + the Pod will be restarted, just as if the livenessProbe + failed. This can be used to provide different probe parameters + at the beginning of a Pod''s lifecycle, when it might + take a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is a beta field and requires enabling GRPCContainerProbe + feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer + for stdin in the container runtime. If this is not set, + reads from stdin in the container will always result in + EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close + the stdin channel after it has been opened by a single + attach. When stdin is true the stdin stream will remain + open across multiple attach sessions. If stdinOnce is + set to true, stdin is opened on container start, is empty + until the first client attaches to stdin, and then remains + open and accepts data until the client disconnects, at + which time stdin is closed and remains closed until the + container is restarted. If this flag is false, a container + processes that reads from stdin will never receive an + EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which + the container''s termination message will be written is + mounted into the container''s filesystem. Message written + is intended to be brief final status, such as an assertion + failure message. Will be truncated by the node if greater + than 4096 bytes. The total message length across all containers + will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should + be populated. File will use the contents of terminationMessagePath + to populate the container status message on both success + and failure. FallbackToLogsOnError will use the last chunk + of container log output if the termination message file + is empty and the container exited with an error. The log + output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY + for itself, also requires 'stdin' to be true. Default + is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a raw + block device within a container. + properties: + devicePath: + description: devicePath is the path inside of the + container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. Cannot be updated. + type: string + required: + - name + type: object + type: array + labels: + additionalProperties: + type: string + description: Labels are custom labels to be added into Pod. + type: object + nodeSelector: + additionalProperties: + type: string + description: NodeSelector to be set on the nginx pod. + type: object + podSecurityContext: + description: PodSecurityContext configures security attributes + for the nginx pod. + properties: + fsGroup: + description: "A special supplemental group that applies to + all containers in a pod. Some volume types allow the Kubelet + to change the ownership of that volume to be owned by the + pod: \n 1. The owning GID will be the FSGroup 2. The setgid + bit is set (new files created in the volume will be owned + by FSGroup) 3. The permission bits are OR'd with rw-rw---- + \n If unset, the Kubelet will not modify the ownership and + permissions of any volume. Note that this field cannot be + set when spec.os.name is windows." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of changing + ownership and permission of the volume before being exposed + inside Pod. This field will only apply to volume types which + support fsGroup based ownership(and permissions). It will + have no effect on ephemeral volume types such as: secret, + configmaps and emptydir. Valid values are "OnRootMismatch" + and "Always". If not specified, "Always" is used. Note that + this field cannot be set when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this field + cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence for that container. + Note that this field cannot be set when spec.os.name is + windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + SecurityContext. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers + in this pod. Note that this field cannot be set when spec.os.name + is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's primary + GID, the fsGroup (if specified), and group memberships defined + in the container image for the uid of the container process. + If unspecified, no additional groups are added to any container. + Note that group memberships defined in the container image + for the uid of the container process are still effective, + even if they are not included in this list. Note that this + field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls used + for the pod. Pods with unsupported sysctls (by the container + runtime) might fail to launch. Note that this field cannot + be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options within a container's + SecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + ports: + description: Ports is the list of ports used by nginx. + items: + description: ContainerPort represents a network port in a single + container. + properties: + containerPort: + description: Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port to. + type: string + hostPort: + description: Number of port to expose on the host. If specified, + this must be a valid port number, 0 < x < 65536. If HostNetwork + is specified, this must match ContainerPort. Most containers + do not need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a pod must + have a unique name. Name for the port that can be referred + to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + rollingUpdate: + description: RollingUpdate defines params to control the desired + behavior of rolling update. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: 'The maximum number of pods that can be scheduled + above the desired number of pods. Value can be an absolute + number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. Absolute number + is calculated from percentage by rounding up. Defaults to + 25%. Example: when this is set to 30%, the new ReplicaSet + can be scaled up immediately when the rolling update starts, + such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, new + ReplicaSet can be scaled up further, ensuring that total + number of pods running at any time during the update is + at most 130% of desired pods.' + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: 'The maximum number of pods that can be unavailable + during the update. Value can be an absolute number (ex: + 5) or a percentage of desired pods (ex: 10%). Absolute number + is calculated from percentage by rounding down. This can + not be 0 if MaxSurge is 0. Defaults to 25%. Example: when + this is set to 30%, the old ReplicaSet can be scaled down + to 70% of desired pods immediately when the rolling update + starts. Once new pods are ready, old ReplicaSet can be scaled + down further, followed by scaling up the new ReplicaSet, + ensuring that the total number of pods available at all + times during the update is at least 70% of desired pods.' + x-kubernetes-int-or-string: true + type: object + serviceAccountName: + description: ServiceAccountName is the name of the ServiceAccount + to use to run this nginx instance. + type: string + terminationGracePeriodSeconds: + description: TerminationGracePeriodSeconds defines the max duration + seconds which the pod needs to terminate gracefully. Defaults + to pod's terminationGracePeriodSeconds default value. + format: int64 + type: integer + toleration: + description: Toletarion defines list of taints that pod can tolerate. + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: TopologySpreadConstraints describes how a group of + pods ought to spread across topology domains. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine + the number of pods in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + matchLabelKeys: + description: MatchLabelKeys is a set of pod label keys to + select the pods over which spreading will be calculated. + The keys are used to lookup values from the incoming pod + labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading + will be calculated for the incoming pod. Keys that don't + exist in the incoming pod labels will be ignored. A null + or empty list means only match against labelSelector. + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: 'MaxSkew describes the degree to which pods + may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, + it is the maximum permitted difference between the number + of matching pods in the target topology and the global + minimum. The global minimum is the minimum number of matching + pods in an eligible domain or zero if the number of eligible + domains is less than MinDomains. For example, in a 3-zone + cluster, MaxSkew is set to 1, and pods with the same labelSelector + spread as 2/2/1: In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | | P P | P P | P | - + if MaxSkew is 1, incoming pod can only be scheduled to + zone3 to become 2/2/2; scheduling it onto zone1(zone2) + would make the ActualSkew(3-1) on zone1(zone2) violate + MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled + onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, + it is used to give higher precedence to topologies that + satisfy it. It''s a required field. Default value is 1 + and 0 is not allowed.' + format: int32 + type: integer + minDomains: + description: "MinDomains indicates a minimum number of eligible + domains. When the number of eligible domains with matching + topology keys is less than minDomains, Pod Topology Spread + treats \"global minimum\" as 0, and then the calculation + of Skew is performed. And when the number of eligible + domains with matching topology keys equals or greater + than minDomains, this value has no effect on scheduling. + As a result, when the number of eligible domains is less + than minDomains, scheduler won't schedule more than maxSkew + Pods to those domains. If value is nil, the constraint + behaves as if MinDomains is equal to 1. Valid values are + integers greater than 0. When value is not nil, WhenUnsatisfiable + must be DoNotSchedule. \n For example, in a 3-zone cluster, + MaxSkew is set to 2, MinDomains is set to 5 and pods with + the same labelSelector spread as 2/2/2: | zone1 | zone2 + | zone3 | | P P | P P | P P | The number of domains + is less than 5(MinDomains), so \"global minimum\" is treated + as 0. In this situation, new pod with the same labelSelector + cannot be scheduled, because computed skew will be 3(3 + - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. \n This is a beta field and requires + the MinDomainsInPodTopologySpread feature gate to be enabled + (enabled by default)." + format: int32 + type: integer + nodeAffinityPolicy: + description: "NodeAffinityPolicy indicates how we will treat + Pod's nodeAffinity/nodeSelector when calculating pod topology + spread skew. Options are: - Honor: only nodes matching + nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes + are included in the calculations. \n If this value is + nil, the behavior is equivalent to the Honor policy. This + is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread + feature flag." + type: string + nodeTaintsPolicy: + description: "NodeTaintsPolicy indicates how we will treat + node taints when calculating pod topology spread skew. + Options are: - Honor: nodes without taints, along with + tainted nodes for which the incoming pod has a toleration, + are included. - Ignore: node taints are ignored. All nodes + are included. \n If this value is nil, the behavior is + equivalent to the Ignore policy. This is a beta-level + feature default enabled by the NodeInclusionPolicyInPodTopologySpread + feature flag." + type: string + topologyKey: + description: TopologyKey is the key of node labels. Nodes + that have a label with this key and identical values are + considered to be in the same topology. We consider each + as a "bucket", and try to put balanced number + of pods into each bucket. We define a domain as a particular + instance of a topology. Also, we define an eligible domain + as a domain whose nodes meet the requirements of nodeAffinityPolicy + and nodeTaintsPolicy. e.g. If TopologyKey is "kubernetes.io/hostname", + each Node is a domain of that topology. And, if TopologyKey + is "topology.kubernetes.io/zone", each zone is a domain + of that topology. It's a required field. + type: string + whenUnsatisfiable: + description: 'WhenUnsatisfiable indicates how to deal with + a pod if it doesn''t satisfy the spread constraint. - + DoNotSchedule (default) tells the scheduler not to schedule + it. - ScheduleAnyway tells the scheduler to schedule the + pod in any location, but giving higher precedence to + topologies that would help reduce the skew. A constraint + is considered "Unsatisfiable" for an incoming pod if and + only if every possible node assignment for that pod would + violate "MaxSkew" on some topology. For example, in a + 3-zone cluster, MaxSkew is set to 1, and pods with the + same labelSelector spread as 3/1/1: | zone1 | zone2 | + zone3 | | P P P | P | P | If WhenUnsatisfiable + is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) + on zone2(zone3) satisfies MaxSkew(1). In other words, + the cluster can still be imbalanced, but scheduler won''t + make it *more* imbalanced. It''s a required field.' + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + volumeMounts: + description: VolumeMounts will mount volume declared above in + directories + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: Path within the container at which the volume + should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are + propagated from the host to container and the other way + around. When not set, MountPropagationNone is used. This + field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise + (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's + volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which + the container's volume should be mounted. Behaves similarly + to SubPath but environment variable references $(VAR_NAME) + are expanded using the container's environment. Defaults + to "" (volume's root). SubPathExpr and SubPath are mutually + exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: Volumes that will attach to nginx instances + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: 'awsElasticBlockStore represents an AWS Disk + resource that is attached to a kubelet''s host machine + and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + properties: + fsType: + description: 'fsType is the filesystem type of the volume + that you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'partition is the partition in the volume + that you want to mount. If omitted, the default is + to mount by volume name. Examples: For volume /dev/sda1, + you specify the partition as "1". Similarly, the volume + partition for /dev/sda is "0" (or you can leave the + property empty).' + format: int32 + type: integer + readOnly: + description: 'readOnly value true will force the readOnly + setting in VolumeMounts. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'volumeID is unique ID of the persistent + disk resource in AWS (Amazon EBS volume). More info: + https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in + the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the + blob storage + type: string + fsType: + description: fsType is Filesystem type to mount. Must + be a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure managed + data disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: readOnly Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: readOnly defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: 'monitors is Required: Monitors is a collection + of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: 'readOnly is Optional: Defaults to false + (read/write). ReadOnly here will force the ReadOnly + setting in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'secretFile is Optional: SecretFile is + the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'secretRef is Optional: SecretRef is reference + to the authentication secret for User, default is + empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'user is optional: User is the rados user + name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + required: + - monitors + type: object + cinder: + description: 'cinder represents a cinder volume attached + and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + properties: + fsType: + description: 'fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating + system. Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'readOnly defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'secretRef is optional: points to a secret + object containing parameters used to connect to OpenStack.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeID: + description: 'volumeID used to identify the volume in + cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: 'defaultMode is optional: mode bits used + to set permissions on created files by default. Must + be an octal value between 0000 and 0777 or a decimal + value between 0 and 511. YAML accepts both octal and + decimal values, JSON requires decimal values for mode + bits. Defaults to 0644. Directories within the path + are not affected by this setting. This might be in + conflict with other options that affect the file mode, + like fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + items: + description: items if unspecified, each key-value pair + in the Data field of the referenced ConfigMap will + be projected into the volume as a file whose name + is the key and content is the value. If specified, + the listed keys will be projected into the specified + paths, and unlisted keys will not be present. If a + key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. + Paths must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used + to set permissions on this file. Must be an + octal value between 0000 and 0777 or a decimal + value between 0 and 511. YAML accepts both octal + and decimal values, JSON requires decimal values + for mode bits. If not specified, the volume + defaultMode will be used. This might be in conflict + with other options that affect the file mode, + like fsGroup, and the result can be other mode + bits set.' + format: int32 + type: integer + path: + description: path is the relative path of the + file to map the key to. May not be an absolute + path. May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: driver is the name of the CSI driver that + handles this volume. Consult with your admin for the + correct name as registered in the cluster. + type: string + fsType: + description: fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the + associated CSI driver which will determine the default + filesystem to apply. + type: string + nodePublishSecretRef: + description: nodePublishSecretRef is a reference to + the secret object containing sensitive information + to pass to the CSI driver to complete the CSI NodePublishVolume + and NodeUnpublishVolume calls. This field is optional, + and may be empty if no secret is required. If the + secret object contains more than one secret, all secret + references are passed. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + readOnly: + description: readOnly specifies a read-only configuration + for the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: volumeAttributes stores driver-specific + properties that are passed to the CSI driver. Consult + your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a Optional: mode bits used + to set permissions on created files by default. Must + be an octal value between 0000 and 0777 or a decimal + value between 0 and 511. YAML accepts both octal and + decimal values, JSON requires decimal values for mode + bits. Defaults to 0644. Directories within the path + are not affected by this setting. This might be in + conflict with other options that affect the file mode, + like fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used to set + permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + description: 'emptyDir represents a temporary directory + that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + properties: + medium: + description: 'medium represents what type of storage + medium should back this directory. The default is + "" which means to use the node''s default medium. + Must be an empty string (default) or Memory. More + info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: 'sizeLimit is the total amount of local + storage required for this EmptyDir volume. The size + limit is also applicable for memory medium. The maximum + usage on memory medium EmptyDir would be the minimum + value between the SizeLimit specified here and the + sum of memory limits of all containers in a pod. The + default is nil which means that the limit is undefined. + More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + 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: + description: "ephemeral represents a volume that is handled + by a cluster storage driver. The volume's lifecycle is + tied to the pod that defines it - it will be created before + the pod starts, and deleted when the pod is removed. \n + Use this if: a) the volume is only needed while the pod + runs, b) features of normal volumes like restoring from + snapshot or capacity tracking are needed, c) the storage + driver is specified through a storage class, and d) the + storage driver supports dynamic volume provisioning through + \ a PersistentVolumeClaim (see EphemeralVolumeSource + for more information on the connection between this + volume type and PersistentVolumeClaim). \n Use PersistentVolumeClaim + or one of the vendor-specific APIs for volumes that persist + for longer than the lifecycle of an individual pod. \n + Use CSI for light-weight local ephemeral volumes if the + CSI driver is meant to be used that way - see the documentation + of the driver for more information. \n A pod can use both + types of ephemeral volumes and persistent volumes at the + same time." + properties: + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC + to provision the volume. The pod in which this EphemeralVolumeSource + is embedded will be the owner of the PVC, i.e. the + PVC will be deleted together with the pod. The name + of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` + array entry. Pod validation will reject the pod if + the concatenated name is not valid for a PVC (for + example, too long). \n An existing PVC with that name + that is not owned by the pod will *not* be used for + the pod to avoid using an unrelated volume by mistake. + Starting the pod is then blocked until the unrelated + PVC is removed. If such a pre-created PVC is meant + to be used by the pod, the PVC has to updated with + an owner reference to the pod once the pod exists. + Normally this should not be necessary, but it may + be useful when manually reconstructing a broken cluster. + \n This field is read-only and no changes will be + made by Kubernetes to the PVC after it has been created. + \n Required, must not be nil." + properties: + metadata: + description: May contain labels and annotations + that will be copied into the PVC when creating + it. No other fields are allowed and will be rejected + during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. + The entire content is copied unchanged into the + PVC that gets created from this template. The + same fields as in a PersistentVolumeClaim are + also valid here. + properties: + accessModes: + description: 'accessModes contains the desired + access modes the volume should have. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'dataSource field can be used to + specify either: * An existing VolumeSnapshot + object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller + can support the specified data source, it + will create a new volume based on the contents + of the specified data source. When the AnyVolumeDataSource + feature gate is enabled, dataSource contents + will be copied to dataSourceRef, and dataSourceRef + contents will be copied to dataSource when + dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef + will not be copied to dataSource.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'dataSourceRef specifies the object + from which to populate the volume with data, + if a non-empty volume is desired. This may + be any object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding + will only succeed if the type of the specified + object matches some installed volume populator + or dynamic provisioner. This field will replace + the functionality of the dataSource field + and as such if both fields are non-empty, + they must have the same value. For backwards + compatibility, when namespace isn''t specified + in dataSourceRef, both fields (dataSource + and dataSourceRef) will be set to the same + value automatically if one of them is empty + and the other is non-empty. When namespace + is specified in dataSourceRef, dataSource + isn''t set to the same value and must be empty. + There are three important differences between + dataSource and dataSourceRef: * While dataSource + only allows two specific types of objects, + dataSourceRef allows any non-core object, + as well as PersistentVolumeClaim objects. + * While dataSource ignores disallowed values + (dropping them), dataSourceRef preserves + all values, and generates an error if a disallowed + value is specified. * While dataSource only + allows local objects, dataSourceRef allows + objects in any namespaces. (Beta) Using + this field requires the AnyVolumeDataSource + feature gate to be enabled. (Alpha) Using + the namespace field of dataSourceRef requires + the CrossNamespaceVolumeDataSource feature + gate to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + namespace: + description: Namespace is the namespace + of resource being referenced Note that + when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant + object is required in the referent namespace + to allow that namespace's owner to accept + the reference. See the ReferenceGrant + documentation for details. (Alpha) This + field requires the CrossNamespaceVolumeDataSource + feature gate to be enabled. + type: string + required: + - kind + - name + type: object + resources: + description: 'resources represents the minimum + resources the volume should have. If RecoverVolumeExpansionFailure + feature is enabled users are allowed to specify + resource requirements that are lower than + previous value but must still be higher than + capacity recorded in the status field of the + claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + claims: + description: "Claims lists the names of + resources, defined in spec.resourceClaims, + that are used by this container. \n This + is an alpha field and requires enabling + the DynamicResourceAllocation feature + gate. \n This field is immutable." + items: + description: ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name + of one entry in pod.spec.resourceClaims + of the Pod where this field is used. + It makes that resource available + inside a container. + 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 + description: 'Limits describes the maximum + amount of compute resources allowed. More + info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + 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 + description: 'Requests describes the minimum + amount of compute resources required. + If Requests is omitted for a container, + it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'storageClassName is the name of + the StorageClass required by the claim. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of + volume is required by the claim. Value of + Filesystem is implied when not included in + claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: 'fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred + to be "ext4" if unspecified. TODO: how do we prevent + errors in the filesystem from compromising the machine' + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: 'readOnly is Optional: Defaults to false + (read/write). ReadOnly here will force the ReadOnly + setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide + names (WWNs)' + items: + type: string + type: array + wwids: + description: 'wwids Optional: FC volume world wide identifiers + (wwids) Either wwids or combination of targetWWNs + and lun must be set, but not both simultaneously.' + items: + type: string + type: array + type: object + flexVolume: + description: flexVolume represents a generic volume resource + that is provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to use + for this volume. + type: string + fsType: + description: fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". The default filesystem + depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: 'readOnly is Optional: defaults to false + (read/write). ReadOnly here will force the ReadOnly + setting in VolumeMounts.' + type: boolean + secretRef: + description: 'secretRef is Optional: secretRef is reference + to the secret object containing sensitive information + to pass to the plugin scripts. This may be empty if + no secret object is specified. If the secret object + contains more than one secret, all secrets are passed + to the plugin scripts.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: datasetName is Name of the dataset stored + as metadata -> name on the dataset for Flocker should + be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: 'gcePersistentDisk represents a GCE Disk resource + that is attached to a kubelet''s host machine and then + exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + properties: + fsType: + description: 'fsType is filesystem type of the volume + that you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'partition is the partition in the volume + that you want to mount. If omitted, the default is + to mount by volume name. Examples: For volume /dev/sda1, + you specify the partition as "1". Similarly, the volume + partition for /dev/sda is "0" (or you can leave the + property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + format: int32 + type: integer + pdName: + description: 'pdName is unique name of the PD resource + in GCE. Used to identify the disk in GCE. More info: + https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'readOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + required: + - pdName + type: object + gitRepo: + description: 'gitRepo represents a git repository at a particular + revision. DEPRECATED: GitRepo is deprecated. To provision + a container with a git repo, mount an EmptyDir into an + InitContainer that clones the repo using git, then mount + the EmptyDir into the Pod''s container.' + properties: + directory: + description: directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, + the volume directory will be the git repository. Otherwise, + if specified, the volume will contain the git repository + in the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified + revision. + type: string + required: + - repository + type: object + glusterfs: + description: 'glusterfs represents a Glusterfs mount on + the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + properties: + endpoints: + description: 'endpoints is the endpoint name that details + Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'path is the Glusterfs volume path. More + info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'readOnly here will force the Glusterfs + volume to be mounted with read-only permissions. Defaults + to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: 'hostPath represents a pre-existing file or + directory on the host machine that is directly exposed + to the container. This is generally used for system agents + or other privileged things that are allowed to see the + host machine. Most containers will NOT need this. More + info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- TODO(jonesdl) We need to restrict who can use host + directory mounts and who can/can not mount host directories + as read/write.' + properties: + path: + description: 'path of the directory on the host. If + the path is a symlink, it will follow the link to + the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'type for HostPath Volume Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + required: + - path + type: object + iscsi: + description: 'iscsi represents an ISCSI Disk resource that + is attached to a kubelet''s host machine and then exposed + to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'fsType is the filesystem type of the volume + that you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + initiatorName: + description: initiatorName is the custom iSCSI Initiator + Name. If initiatorName is specified with iscsiInterface + simultaneously, new iSCSI interface : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iscsiInterface is the interface Name that + uses an iSCSI transport. Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: portals is the iSCSI Target Portal List. + The portal is either an IP or ip_addr:port if the + port is other than default (typically TCP ports 860 + and 3260). + items: + type: string + type: array + readOnly: + description: readOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + targetPortal: + description: targetPortal is iSCSI Target Portal. The + Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and + 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: 'name of the volume. Must be a DNS_LABEL and + unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'nfs represents an NFS mount on the host that + shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + properties: + path: + description: 'path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'readOnly here will force the NFS export + to be mounted with read-only permissions. Defaults + to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'server is the hostname or IP address of + the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: 'persistentVolumeClaimVolumeSource represents + a reference to a PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + properties: + claimName: + description: 'claimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: readOnly Will force the ReadOnly setting + in VolumeMounts. Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: fSType represents the filesystem type to + mount Must be a filesystem type supported by the host + operating system. Ex. "ext4", "xfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + readOnly: + description: readOnly defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: defaultMode are the mode bits used to set + permissions on created files by default. Must be an + octal value between 0000 and 0777 or a decimal value + between 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this + setting. This might be in conflict with other options + that affect the file mode, like fsGroup, and the result + can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + configMap: + description: configMap information about the configMap + data to project + properties: + items: + description: items if unspecified, each key-value + pair in the Data field of the referenced + ConfigMap will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the ConfigMap, the volume setup will + error unless it is marked optional. Paths + must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode + bits used to set permissions on this + file. Must be an octal value between + 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal + and decimal values, JSON requires + decimal values for mode bits. If not + specified, the volume defaultMode + will be used. This might be in conflict + with other options that affect the + file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: path is the relative path + of the file to map the key to. May + not be an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + downwardAPI: + description: downwardAPI information about the + downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used + to set permissions on this file, must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of + the container: only resources limits + and requests (limits.cpu, limits.memory, + requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the secret + data to project + properties: + items: + description: items if unspecified, each key-value + pair in the Data field of the referenced + Secret will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the Secret, the volume setup will error + unless it is marked optional. Paths must + be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode + bits used to set permissions on this + file. Must be an octal value between + 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal + and decimal values, JSON requires + decimal values for mode bits. If not + specified, the volume defaultMode + will be used. This might be in conflict + with other options that affect the + file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: path is the relative path + of the file to map the key to. May + not be an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: audience is the intended audience + of the token. A recipient of a token must + identify itself with an identifier specified + in the audience of the token, and otherwise + should reject the token. The audience defaults + to the identifier of the apiserver. + type: string + expirationSeconds: + description: expirationSeconds is the requested + duration of validity of the service account + token. As the token approaches expiration, + the kubelet volume plugin will proactively + rotate the service account token. The kubelet + will start trying to rotate the token if + the token is older than 80 percent of its + time to live or if the token is older than + 24 hours.Defaults to 1 hour and must be + at least 10 minutes. + format: int64 + type: integer + path: + description: path is the path relative to + the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: group to map volume access to Default is + no group + type: string + readOnly: + description: readOnly here will force the Quobyte volume + to be mounted with read-only permissions. Defaults + to false. + type: boolean + registry: + description: registry represents a single or multiple + Quobyte Registry services specified as a string as + host:port pair (multiple entries are separated with + commas) which acts as the central registry for volumes + type: string + tenant: + description: tenant owning the given Quobyte volume + in the Backend Used with dynamically provisioned Quobyte + volumes, value is set by the plugin + type: string + user: + description: user to map volume access to Defaults to + serivceaccount user + type: string + volume: + description: volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: 'rbd represents a Rados Block Device mount + on the host that shares a pod''s lifetime. More info: + https://examples.k8s.io/volumes/rbd/README.md' + properties: + fsType: + description: 'fsType is the filesystem type of the volume + that you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + image: + description: 'image is the rados image name. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + items: + type: string + type: array + pool: + description: 'pool is the rados pool name. Default is + rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'readOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'secretRef is name of the authentication + secret for RBDUser. If provided overrides keyring. + Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'user is the rados user name. Default is + admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO + API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO + Protection Domain for the configured storage. + type: string + readOnly: + description: readOnly Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: secretRef references to the secret for + ScaleIO user and other sensitive information. If this + is not provided, Login operation will fail. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: storageMode indicates whether the storage + for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool + associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: volumeName is the name of a volume already + created in the ScaleIO system that is associated with + this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: 'secret represents a secret that should populate + this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + properties: + defaultMode: + description: 'defaultMode is Optional: mode bits used + to set permissions on created files by default. Must + be an octal value between 0000 and 0777 or a decimal + value between 0 and 511. YAML accepts both octal and + decimal values, JSON requires decimal values for mode + bits. Defaults to 0644. Directories within the path + are not affected by this setting. This might be in + conflict with other options that affect the file mode, + like fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + items: + description: items If unspecified, each key-value pair + in the Data field of the referenced Secret will be + projected into the volume as a file whose name is + the key and content is the value. If specified, the + listed keys will be projected into the specified paths, + and unlisted keys will not be present. If a key is + specified which is not present in the Secret, the + volume setup will error unless it is marked optional. + Paths must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used + to set permissions on this file. Must be an + octal value between 0000 and 0777 or a decimal + value between 0 and 511. YAML accepts both octal + and decimal values, JSON requires decimal values + for mode bits. If not specified, the volume + defaultMode will be used. This might be in conflict + with other options that affect the file mode, + like fsGroup, and the result can be other mode + bits set.' + format: int32 + type: integer + path: + description: path is the relative path of the + file to map the key to. May not be an absolute + path. May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the Secret + or its keys must be defined + type: boolean + secretName: + description: 'secretName is the name of the secret in + the pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + readOnly: + description: readOnly defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: secretRef specifies the secret to use for + obtaining the StorageOS API credentials. If not specified, + default values will be attempted. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeName: + description: volumeName is the human-readable name of + the StorageOS volume. Volume names are only unique + within a namespace. + type: string + volumeNamespace: + description: volumeNamespace specifies the scope of + the volume within StorageOS. If no namespace is specified + then the Pod's namespace will be used. This allows + the Kubernetes name scoping to be mirrored within + StorageOS for tighter integration. Set VolumeName + to any name to override the default behaviour. Set + to "default" if you are not using namespaces within + StorageOS. Namespaces that do not pre-exist within + StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: fsType is filesystem type to mount. Must + be a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based + Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + proxyProtocol: + description: ProxyProtocol defines whether allocate additional ports + to expose via proxy protocol + type: boolean + replicas: + description: Number of desired pods. This is a pointer to distinguish + between explicit zero and not specified. Defaults to 1. + format: int32 + type: integer + service: + description: Service to expose the nginx instance + properties: + annotations: + additionalProperties: + type: string + description: Annotations are extra annotations for the service. + type: object + externalTrafficPolicy: + description: ExternalTrafficPolicy defines whether external traffic + will be routed to node-local or cluster-wide endpoints. Defaults + to the default Service externalTrafficPolicy value. + type: string + labels: + additionalProperties: + type: string + description: Labels are extra labels for the service. + type: object + loadBalancerIP: + description: LoadBalancerIP is an optional load balancer IP for + the service. + type: string + type: + description: Type is the type of the service. Defaults to the + default service type value. + type: string + usePodSelector: + description: UsePodSelector defines whether Service should automatically + map the endpoints using the pod's label selector. Defaults to + true. + type: boolean + type: object + suspend: + default: false + description: Suspend flag tells whether controller should suspend + any further modifications on this resource. Defaults to false. + type: boolean + tls: + description: TLS configuration. + items: + properties: + hosts: + description: 'Hosts are a list of hosts included in the TLS + certificate. Defaults to the wildcard of hosts: "*".' + items: + type: string + type: array + secretName: + description: "SecretName is the name of the Secret which contains + the certificate-key pair. It must reside in the same Namespace + as the Nginx resource. \n NOTE: The Secret should follow the + Kubernetes TLS secrets type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets." + type: string + required: + - secretName + type: object + type: array + tlsSessionResumption: + description: TLSSessionResumption configures the instance to support + session resumption using either session tickets or session ID (in + the future). Defaults to disabled. + properties: + sessionTicket: + description: SessionTicket defines the parameters to set the TLS + session tickets. + properties: + image: + description: Image is the container image name used to execute + the session ticket rotation job. It requires either "bash", + "base64", "openssl" and "kubectl" programs be installed + into. Defaults to "bitnami/kubectl:latest". + type: string + keepLastKeys: + description: KeepLastKeys defines how many session ticket + encryption keys should be kept in addition to the current + one. Zero means no old encryption keys. Defaults to zero. + format: int32 + type: integer + keyLength: + description: KeyLength defines the length of bytes for a session + tickets. Should be either 48 or 80 bytes. Defaults to 48 + bytes. + type: integer + keyRotationInterval: + description: KeyRotationInterval defines the time interval, + in minutes, that a key rotation job should occurs. Defaults + to 60 minutes (an hour). + format: int32 + type: integer + type: object + type: object + type: object + status: + description: RpaasValidationStatus defines the observed state of RpaasValidation + properties: + error: + description: Feedback of validation of nginx + type: string + observedGeneration: + description: The most recent generation observed by the rpaas operator + controller. + format: int64 + type: integer + revisionHash: + description: Revision hash calculated for the current spec of rpaasvalidation + type: string + valid: + description: Valid determines whether validation is valid + type: boolean + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 9dbb28d3c..43d64b41c 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,6 +3,7 @@ # It should be run by config/default resources: - bases/extensions.tsuru.io_rpaasinstances.yaml +- bases/extensions.tsuru.io_rpaasvalidations.yaml - bases/extensions.tsuru.io_rpaasplans.yaml - bases/extensions.tsuru.io_rpaasflavors.yaml # +kubebuilder:scaffold:crdkustomizeresource diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 86439ed97..5a2f8a880 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -6,6 +6,20 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - create + - delete + - get + - list + - update + - watch - apiGroups: - "" resources: @@ -103,6 +117,24 @@ rules: - get - list - watch +- apiGroups: + - extensions.tsuru.io + resources: + - rpaasvalidations + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - extensions.tsuru.io + resources: + - rpaasvalidations/status + verbs: + - get + - patch + - update - apiGroups: - keda.sh resources: diff --git a/controllers/controller.go b/controllers/controller.go index 39b5bd7b2..27e407833 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -38,7 +38,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/tsuru/rpaas-operator/api/v1alpha1" @@ -222,23 +222,12 @@ func mergeInstanceWithFlavor(instance *v1alpha1.RpaasInstance, flavor v1alpha1.R } func (r *RpaasInstanceReconciler) listDefaultFlavors(ctx context.Context, instance *v1alpha1.RpaasInstance) ([]v1alpha1.RpaasFlavor, error) { - flavorList := &v1alpha1.RpaasFlavorList{} flavorNamespace := instance.Namespace if instance.Spec.PlanNamespace != "" { flavorNamespace = instance.Spec.PlanNamespace } - if err := r.Client.List(ctx, flavorList, client.InNamespace(flavorNamespace)); err != nil { - return nil, err - } - var result []v1alpha1.RpaasFlavor - for _, flavor := range flavorList.Items { - if flavor.Spec.Default { - result = append(result, flavor) - } - } - sort.SliceStable(result, func(i, j int) bool { return result[i].Name < result[j].Name }) - return result, nil + return listDefaultFlavors(ctx, r.Client, flavorNamespace) } func (r *RpaasInstanceReconciler) reconcileTLSSessionResumption(ctx context.Context, instance *v1alpha1.RpaasInstance) (hasChanged bool, err error) { @@ -256,7 +245,7 @@ func (r *RpaasInstanceReconciler) reconcileTLSSessionResumption(ctx context.Cont } func (r *RpaasInstanceReconciler) reconcileSecretForSessionTickets(ctx context.Context, instance *v1alpha1.RpaasInstance) (hasChanged bool, err error) { - enabled := isTLSSessionTicketEnabled(instance) + enabled := isTLSSessionTicketEnabled(&instance.Spec) newSecret, err := newSecretForTLSSessionTickets(instance) if err != nil { @@ -309,7 +298,7 @@ func (r *RpaasInstanceReconciler) reconcileSecretForSessionTickets(ctx context.C } func (r *RpaasInstanceReconciler) reconcileCronJobForSessionTickets(ctx context.Context, instance *v1alpha1.RpaasInstance) (hasChanged bool, err error) { - enabled := isTLSSessionTicketEnabled(instance) + enabled := isTLSSessionTicketEnabled(&instance.Spec) newCronJob := newCronJobForSessionTickets(instance) @@ -356,7 +345,7 @@ func (r *RpaasInstanceReconciler) reconcileCronJobForSessionTickets(ctx context. } func newCronJobForSessionTickets(instance *v1alpha1.RpaasInstance) *batchv1.CronJob { - enabled := isTLSSessionTicketEnabled(instance) + enabled := isTLSSessionTicketEnabled(&instance.Spec) keyLength := v1alpha1.DefaultSessionTicketKeyLength if enabled && instance.Spec.TLSSessionResumption.SessionTicket.KeyLength != 0 { @@ -421,7 +410,7 @@ func newCronJobForSessionTickets(instance *v1alpha1.RpaasInstance) *batchv1.Cron Env: []corev1.EnvVar{ { Name: "SECRET_NAME", - Value: secretNameForTLSSessionTickets(instance), + Value: secretNameForTLSSessionTickets(instance.Name), }, { Name: "SECRET_NAMESPACE", @@ -476,7 +465,7 @@ func newCronJobForSessionTickets(instance *v1alpha1.RpaasInstance) *batchv1.Cron func newSecretForTLSSessionTickets(instance *v1alpha1.RpaasInstance) (*corev1.Secret, error) { keyLength := v1alpha1.DefaultSessionTicketKeyLength - if isTLSSessionTicketEnabled(instance) && instance.Spec.TLSSessionResumption.SessionTicket.KeyLength != 0 { + if isTLSSessionTicketEnabled(&instance.Spec) && instance.Spec.TLSSessionResumption.SessionTicket.KeyLength != 0 { keyLength = instance.Spec.TLSSessionResumption.SessionTicket.KeyLength } @@ -496,7 +485,7 @@ func newSecretForTLSSessionTickets(instance *v1alpha1.RpaasInstance) (*corev1.Se Kind: "Secret", }, ObjectMeta: metav1.ObjectMeta{ - Name: secretNameForTLSSessionTickets(instance), + Name: secretNameForTLSSessionTickets(instance.Name), Namespace: instance.Namespace, Labels: instance.GetBaseLabels(nil), OwnerReferences: []metav1.OwnerReference{ @@ -511,20 +500,20 @@ func newSecretForTLSSessionTickets(instance *v1alpha1.RpaasInstance) (*corev1.Se }, nil } -func isTLSSessionTicketEnabled(instance *v1alpha1.RpaasInstance) bool { - return instance.Spec.TLSSessionResumption != nil && instance.Spec.TLSSessionResumption.SessionTicket != nil +func isTLSSessionTicketEnabled(spec *v1alpha1.RpaasInstanceSpec) bool { + return spec.TLSSessionResumption != nil && spec.TLSSessionResumption.SessionTicket != nil } func tlsSessionTicketKeys(instance *v1alpha1.RpaasInstance) int { var nkeys int - if isTLSSessionTicketEnabled(instance) { + if isTLSSessionTicketEnabled(&instance.Spec) { nkeys = int(instance.Spec.TLSSessionResumption.SessionTicket.KeepLastKeys) } return nkeys + 1 } -func secretNameForTLSSessionTickets(instance *v1alpha1.RpaasInstance) string { - return fmt.Sprintf("%s%s", instance.Name, sessionTicketsSecretSuffix) +func secretNameForTLSSessionTickets(instanceName string) string { + return fmt.Sprintf("%s%s", instanceName, sessionTicketsSecretSuffix) } func generateSessionTicket(keyLength v1alpha1.SessionTicketKeyLength) ([]byte, error) { @@ -918,33 +907,7 @@ func newPDB(instance *v1alpha1.RpaasInstance, nginx *nginxv1alpha1.Nginx) (*poli } func (r *RpaasInstanceReconciler) reconcileConfigMap(ctx context.Context, configMap *corev1.ConfigMap) (hasChanged bool, err error) { - found := &corev1.ConfigMap{} - err = r.Client.Get(ctx, types.NamespacedName{Name: configMap.ObjectMeta.Name, Namespace: configMap.ObjectMeta.Namespace}, found) - if err != nil { - if !k8sErrors.IsNotFound(err) { - logrus.Errorf("Failed to get configMap: %v", err) - return false, err - } - err = r.Client.Create(ctx, configMap) - if err != nil { - logrus.Errorf("Failed to create configMap: %v", err) - return false, err - } - return true, nil - } - - configMap.ObjectMeta.ResourceVersion = found.ObjectMeta.ResourceVersion - - if reflect.DeepEqual(found.Data, configMap.Data) && reflect.DeepEqual(found.BinaryData, configMap.BinaryData) && reflect.DeepEqual(found.Labels, configMap.Labels) { - return false, nil - } - - err = r.Client.Update(ctx, configMap) - if err != nil { - logrus.Errorf("Failed to update configMap: %v", err) - return false, err - } - return true, nil + return reconcileConfigMap(ctx, r.Client, configMap) } func (r *RpaasInstanceReconciler) getNginx(ctx context.Context, instance *v1alpha1.RpaasInstance) (*nginxv1alpha1.Nginx, error) { @@ -1005,12 +968,18 @@ func (r *RpaasInstanceReconciler) reconcileNginx(ctx context.Context, instance * } func (r *RpaasInstanceReconciler) renderTemplate(ctx context.Context, instance *v1alpha1.RpaasInstance, plan *v1alpha1.RpaasPlan) (string, error) { - blocks, err := r.getConfigurationBlocks(ctx, instance, plan) + rf := &referenceFinder{ + spec: &instance.Spec, + client: r.Client, + namespace: instance.Namespace, + } + + blocks, err := rf.getConfigurationBlocks(ctx, plan) if err != nil { return "", err } - if err = r.updateLocationValues(ctx, instance); err != nil { + if err = rf.updateLocationValues(ctx); err != nil { return "", err } @@ -1027,57 +996,6 @@ func (r *RpaasInstanceReconciler) renderTemplate(ctx context.Context, instance * return cr.Render(config) } -func (r *RpaasInstanceReconciler) getConfigurationBlocks(ctx context.Context, instance *v1alpha1.RpaasInstance, plan *v1alpha1.RpaasPlan) (nginx.ConfigurationBlocks, error) { - var blocks nginx.ConfigurationBlocks - - if plan.Spec.Template != nil { - mainBlock, err := util.GetValue(ctx, r.Client, "", plan.Spec.Template) - if err != nil { - return blocks, err - } - - blocks.MainBlock = mainBlock - } - - for blockType, blockValue := range instance.Spec.Blocks { - content, err := util.GetValue(ctx, r.Client, instance.Namespace, &blockValue) - if err != nil { - return blocks, err - } - - switch blockType { - case v1alpha1.BlockTypeRoot: - blocks.RootBlock = content - case v1alpha1.BlockTypeHTTP: - blocks.HttpBlock = content - case v1alpha1.BlockTypeServer: - blocks.ServerBlock = content - case v1alpha1.BlockTypeLuaServer: - blocks.LuaServerBlock = content - case v1alpha1.BlockTypeLuaWorker: - blocks.LuaWorkerBlock = content - } - } - - return blocks, nil -} - -func (r *RpaasInstanceReconciler) updateLocationValues(ctx context.Context, instance *v1alpha1.RpaasInstance) error { - for _, location := range instance.Spec.Locations { - if location.Content == nil { - continue - } - - content, err := util.GetValue(ctx, r.Client, instance.Namespace, location.Content) - if err != nil { - return err - } - - location.Content.Value = content - } - return nil -} - func (r *RpaasInstanceReconciler) listConfigs(ctx context.Context, instance *v1alpha1.RpaasInstance) (*corev1.ConfigMapList, error) { configList := &corev1.ConfigMapList{} listOptions := &client.ListOptions{Namespace: instance.ObjectMeta.Namespace} @@ -1192,7 +1110,7 @@ func newNginx(instanceMergedWithFlavors *v1alpha1.RpaasInstance, plan *v1alpha1. replicas := instanceMergedWithFlavors.Spec.Replicas if shutdown := instanceMergedWithFlavors.Spec.Shutdown; shutdown { - replicas = pointer.Int32(0) + replicas = ptr.To(int32(0)) } if isAutoscaleEnabled(&instanceMergedWithFlavors.Spec) { @@ -1259,12 +1177,12 @@ func newNginx(instanceMergedWithFlavors *v1alpha1.RpaasInstance, plan *v1alpha1. }) } - if isTLSSessionTicketEnabled(instanceMergedWithFlavors) { + if isTLSSessionTicketEnabled(&instanceMergedWithFlavors.Spec) { n.Spec.PodTemplate.Volumes = append(n.Spec.PodTemplate.Volumes, corev1.Volume{ Name: sessionTicketsVolumeName, VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: secretNameForTLSSessionTickets(instanceMergedWithFlavors), + SecretName: secretNameForTLSSessionTickets(instanceMergedWithFlavors.Name), }, }, }) @@ -1293,7 +1211,7 @@ func generateNginxHash(nginx *nginxv1alpha1.Nginx) (string, error) { return strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:])), nil } -func generateSpecHash(spec *v1alpha1.RpaasInstanceSpec) (string, error) { +func generateSpecHash(spec any) (string, error) { if spec == nil { return "", nil } diff --git a/controllers/controller_test.go b/controllers/controller_test.go index 769e50789..57efc5a94 100644 --- a/controllers/controller_test.go +++ b/controllers/controller_test.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/tools/record" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -1106,7 +1106,7 @@ func Test_reconcileHPA(t *testing.T) { Kind: "Deployment", Name: "my-instance", }, - MinReplicas: pointer.Int32(1), + MinReplicas: ptr.To(int32(1)), MaxReplicas: 10, Metrics: []autoscalingv2.MetricSpec{ { @@ -1115,7 +1115,7 @@ func Test_reconcileHPA(t *testing.T) { Name: "cpu", Target: autoscalingv2.MetricTarget{ Type: autoscalingv2.UtilizationMetricType, - AverageUtilization: pointer.Int32(50), + AverageUtilization: ptr.To(int32(50)), }, }, }, @@ -1126,9 +1126,9 @@ func Test_reconcileHPA(t *testing.T) { }, instance: func(ri *v1alpha1.RpaasInstance) *v1alpha1.RpaasInstance { ri.Spec.Autoscale = &v1alpha1.RpaasInstanceAutoscaleSpec{ - MinReplicas: pointer.Int32(1), + MinReplicas: ptr.To(int32(1)), MaxReplicas: 10, - TargetCPUUtilizationPercentage: pointer.Int32(50), + TargetCPUUtilizationPercentage: ptr.To(int32(50)), } return ri }, @@ -1141,7 +1141,7 @@ func Test_reconcileHPA(t *testing.T) { Kind: "Deployment", Name: "my-instance", }, - MinReplicas: pointer.Int32(1), + MinReplicas: ptr.To(int32(1)), MaxReplicas: 10, Metrics: []autoscalingv2.MetricSpec{ { @@ -1150,7 +1150,7 @@ func Test_reconcileHPA(t *testing.T) { Name: "cpu", Target: autoscalingv2.MetricTarget{ Type: autoscalingv2.UtilizationMetricType, - AverageUtilization: pointer.Int32(50), + AverageUtilization: ptr.To(int32(50)), }, }, }, @@ -1340,9 +1340,9 @@ func Test_reconcileHPA(t *testing.T) { Kind: "Deployment", Name: "my-instance", }, - MinReplicaCount: pointer.Int32(2), - MaxReplicaCount: pointer.Int32(500), - PollingInterval: pointer.Int32(5), + MinReplicaCount: ptr.To(int32(2)), + MaxReplicaCount: ptr.To(int32(500)), + PollingInterval: ptr.To(int32(5)), Advanced: &kedav1alpha1.AdvancedConfig{ HorizontalPodAutoscalerConfig: &kedav1alpha1.HorizontalPodAutoscalerConfig{ Name: "my-instance", @@ -1369,9 +1369,9 @@ func Test_reconcileHPA(t *testing.T) { expectedChanged: false, instance: func(ri *v1alpha1.RpaasInstance) *v1alpha1.RpaasInstance { ri.Spec.Autoscale = &v1alpha1.RpaasInstanceAutoscaleSpec{ - MinReplicas: pointer.Int32(2), + MinReplicas: ptr.To(int32(2)), MaxReplicas: 500, - TargetRequestsPerSecond: pointer.Int32(50), + TargetRequestsPerSecond: ptr.To(int32(50)), KEDAOptions: &v1alpha1.AutoscaleKEDAOptions{ Enabled: true, PrometheusServerAddress: "https://prometheus.example.com", @@ -1380,15 +1380,15 @@ func Test_reconcileHPA(t *testing.T) { Kind: "ClusterTriggerAuthentication", Name: "prometheus-auth", }, - PollingInterval: pointer.Int32(5), + PollingInterval: ptr.To(int32(5)), }, } return ri }, expectedScaledObject: func(so *kedav1alpha1.ScaledObject) *kedav1alpha1.ScaledObject { - so.Spec.MinReplicaCount = pointer.Int32(2) - so.Spec.MaxReplicaCount = pointer.Int32(500) - so.Spec.PollingInterval = pointer.Int32(5) + so.Spec.MinReplicaCount = ptr.To(int32(2)) + so.Spec.MaxReplicaCount = ptr.To(int32(500)) + so.Spec.PollingInterval = ptr.To(int32(5)) so.Spec.Triggers = []kedav1alpha1.ScaleTriggers{ { Type: "prometheus", @@ -1695,8 +1695,8 @@ func Test_reconcilePDB(t *testing.T) { Namespace: "rpaasv2", }, Spec: v1alpha1.RpaasInstanceSpec{ - EnablePodDisruptionBudget: pointer.Bool(true), - Replicas: pointer.Int32(1), + EnablePodDisruptionBudget: ptr.To(true), + Replicas: ptr.To(int32(1)), }, }, expectedChanged: true, @@ -2355,8 +2355,8 @@ func TestReconcileRpaasInstance_reconcileTLSSessionResumption(t *testing.T) { }, Spec: batchv1.CronJobSpec{ Schedule: "*/60 * * * *", - SuccessfulJobsHistoryLimit: pointer.Int32(1), - FailedJobsHistoryLimit: pointer.Int32(1), + SuccessfulJobsHistoryLimit: ptr.To(int32(1)), + FailedJobsHistoryLimit: ptr.To(int32(1)), JobTemplate: batchv1.JobTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ diff --git a/controllers/skeleton.go b/controllers/skeleton.go new file mode 100644 index 000000000..e506d2fc3 --- /dev/null +++ b/controllers/skeleton.go @@ -0,0 +1,124 @@ +// Copyright 2024 tsuru authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package controllers + +import ( + "context" + "reflect" + "sort" + + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/tsuru/rpaas-operator/api/v1alpha1" + "github.com/tsuru/rpaas-operator/internal/pkg/rpaas/nginx" + "github.com/tsuru/rpaas-operator/pkg/util" +) + +func listDefaultFlavors(ctx context.Context, c client.Client, namespace string) ([]v1alpha1.RpaasFlavor, error) { + flavorList := &v1alpha1.RpaasFlavorList{} + if err := c.List(ctx, flavorList, client.InNamespace(namespace)); err != nil { + return nil, err + } + + var result []v1alpha1.RpaasFlavor + for _, flavor := range flavorList.Items { + if flavor.Spec.Default { + result = append(result, flavor) + } + } + sort.SliceStable(result, func(i, j int) bool { return result[i].Name < result[j].Name }) + return result, nil +} + +type referenceFinder struct { + client client.Client + namespace string + spec *v1alpha1.RpaasInstanceSpec +} + +func (r *referenceFinder) getConfigurationBlocks(ctx context.Context, plan *v1alpha1.RpaasPlan) (nginx.ConfigurationBlocks, error) { + var blocks nginx.ConfigurationBlocks + + if plan.Spec.Template != nil { + mainBlock, err := util.GetValue(ctx, r.client, "", plan.Spec.Template) + if err != nil { + return blocks, err + } + + blocks.MainBlock = mainBlock + } + + for blockType, blockValue := range r.spec.Blocks { + content, err := util.GetValue(ctx, r.client, r.namespace, &blockValue) + if err != nil { + return blocks, err + } + + switch blockType { + case v1alpha1.BlockTypeRoot: + blocks.RootBlock = content + case v1alpha1.BlockTypeHTTP: + blocks.HttpBlock = content + case v1alpha1.BlockTypeServer: + blocks.ServerBlock = content + case v1alpha1.BlockTypeLuaServer: + blocks.LuaServerBlock = content + case v1alpha1.BlockTypeLuaWorker: + blocks.LuaWorkerBlock = content + } + } + + return blocks, nil +} + +func (r *referenceFinder) updateLocationValues(ctx context.Context) error { + for _, location := range r.spec.Locations { + if location.Content == nil { + continue + } + + content, err := util.GetValue(ctx, r.client, r.namespace, location.Content) + if err != nil { + return err + } + + location.Content.Value = content + } + return nil +} + +func reconcileConfigMap(ctx context.Context, c client.Client, configMap *corev1.ConfigMap) (hasChanged bool, err error) { + found := &corev1.ConfigMap{} + err = c.Get(ctx, types.NamespacedName{Name: configMap.ObjectMeta.Name, Namespace: configMap.ObjectMeta.Namespace}, found) + if err != nil { + if !k8sErrors.IsNotFound(err) { + logrus.Errorf("Failed to get configMap: %v", err) + return false, err + } + err = c.Create(ctx, configMap) + if err != nil { + logrus.Errorf("Failed to create configMap: %v", err) + return false, err + } + return true, nil + } + + configMap.ObjectMeta.ResourceVersion = found.ObjectMeta.ResourceVersion + + if reflect.DeepEqual(found.Data, configMap.Data) && reflect.DeepEqual(found.BinaryData, configMap.BinaryData) && reflect.DeepEqual(found.Labels, configMap.Labels) { + return false, nil + } + + err = c.Update(ctx, configMap) + if err != nil { + logrus.Errorf("Failed to update configMap: %v", err) + return false, err + } + return true, nil +} diff --git a/controllers/validation_controller.go b/controllers/validation_controller.go new file mode 100644 index 000000000..f2061d163 --- /dev/null +++ b/controllers/validation_controller.go @@ -0,0 +1,506 @@ +package controllers + +import ( + "context" + "fmt" + "path/filepath" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/tsuru/rpaas-operator/api/v1alpha1" + "github.com/tsuru/rpaas-operator/internal/pkg/rpaas/nginx" +) + +// RpaasValidationReconciler reconciles a RpaasValidation object +type RpaasValidationReconciler struct { + client.Client + Log logr.Logger +} + +// +kubebuilder:rbac:groups="",resources=configmaps;secrets;services;pods,verbs=get;list;watch;create;update;delete +// +kubebuilder:rbac:groups="",resources=events,verbs=create;update;patch + +// +kubebuilder:rbac:groups=extensions.tsuru.io,resources=rpaasflavors,verbs=get;list;watch +// +kubebuilder:rbac:groups=extensions.tsuru.io,resources=rpaasplans,verbs=get;list;watch +// +kubebuilder:rbac:groups=extensions.tsuru.io,resources=rpaasvalidations,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=extensions.tsuru.io,resources=rpaasvalidations/status,verbs=get;update;patch + +func (r *RpaasValidationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + validation, err := r.getRpaasValidation(ctx, req.NamespacedName) + if k8sErrors.IsNotFound(err) { + return reconcile.Result{}, nil + } + + if err != nil { + return reconcile.Result{}, err + } + + validationHash, err := generateSpecHash(&validation.Spec) + if err != nil { + return reconcile.Result{}, err + } + + if validation.Status.RevisionHash == validationHash && validation.Status.Valid != nil { + return reconcile.Result{}, nil + } + + planName := types.NamespacedName{ + Name: validation.Spec.PlanName, + Namespace: validation.Namespace, + } + if validation.Spec.PlanNamespace != "" { + planName.Namespace = validation.Spec.PlanNamespace + } + + plan := &v1alpha1.RpaasPlan{} + err = r.Client.Get(ctx, planName, plan) + if err != nil { + return reconcile.Result{}, err + } + + validationMergedWithFlavors, err := r.mergeWithFlavors(ctx, validation.DeepCopy()) + if err != nil { + return reconcile.Result{}, err + } + + if validationMergedWithFlavors.Spec.PlanTemplate != nil { + plan.Spec, err = mergePlans(plan.Spec, *validationMergedWithFlavors.Spec.PlanTemplate) + if err != nil { + return reconcile.Result{}, err + } + } + + rendered, err := r.renderTemplate(ctx, validationMergedWithFlavors, plan) + if err != nil { + return reconcile.Result{}, err + } + + configMap := newValidationConfigMap(validationMergedWithFlavors, rendered) + _, err = reconcileConfigMap(ctx, r.Client, configMap) + if err != nil { + return reconcile.Result{}, err + } + + pod := newValidationPod(validationMergedWithFlavors, validationHash, plan, configMap) + + existingPod, err := r.getPod(ctx, pod.Namespace, pod.Name) + if err != nil && !k8sErrors.IsNotFound(err) { + return ctrl.Result{}, err + } + + if existingPod != nil { + finished, finishErr := r.finishValidation(ctx, existingPod, validation, validationHash) + + if finishErr != nil { + return ctrl.Result{}, finishErr + } + + if finished { + return ctrl.Result{}, nil + } + } + + _, err = r.reconcilePod(ctx, pod) + if err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +func (r *RpaasValidationReconciler) finishValidation(ctx context.Context, existingPod *corev1.Pod, validation *v1alpha1.RpaasValidation, validationHash string) (finished bool, err error) { + if existingPod.Annotations[v1alpha1.RpaasOperatorValidationHashAnnotationKey] != validationHash { + return false, nil + } + + logger := r.Log.WithName("finishValidation"). + WithValues("RpaasValidation", types.NamespacedName{Name: validation.Name, Namespace: validation.Namespace}) + + var valid *bool = nil + terminatedMessage := "" + + if existingPod.Status.Phase == corev1.PodSucceeded && len(existingPod.Status.ContainerStatuses) > 0 { + containerStatus := existingPod.Status.ContainerStatuses[0] + + if containerStatus.State.Terminated != nil { + if containerStatus.State.Terminated.ExitCode == 0 { + valid = ptr.To(true) + terminatedMessage = containerStatus.State.Terminated.Message + } + } + } + + if existingPod.Status.Phase == corev1.PodFailed && len(existingPod.Status.ContainerStatuses) > 0 { + containerStatus := existingPod.Status.ContainerStatuses[0] + + if containerStatus.State.Terminated != nil { + if containerStatus.State.Terminated.ExitCode != 0 { + valid = ptr.To(false) + terminatedMessage = containerStatus.State.Terminated.Message + } + } + } + + if valid != nil { + logger.Info("validation finished", "valid", *valid, "terminatedMessage", terminatedMessage) + + validation.Status.RevisionHash = validationHash + validation.Status.ObservedGeneration = validation.ObjectMeta.Generation + validation.Status.Valid = valid + + if *valid { + validation.Status.Error = "" + } else { + validation.Status.Error = terminatedMessage + } + + err = r.Client.Status().Update(ctx, validation) + if err != nil { + return false, err + } + + err = r.Client.Delete(ctx, existingPod) + if err != nil { + return false, err + } + + return true, nil + } + + return false, nil +} + +func (r *RpaasValidationReconciler) getRpaasValidation(ctx context.Context, objKey types.NamespacedName) (*v1alpha1.RpaasValidation, error) { + var instance v1alpha1.RpaasValidation + if err := r.Client.Get(ctx, objKey, &instance); err != nil { + return nil, err + } + + return &instance, nil +} + +func (r *RpaasValidationReconciler) mergeWithFlavors(ctx context.Context, validation *v1alpha1.RpaasValidation) (*v1alpha1.RpaasValidation, error) { + mergedValidation, err := r.mergeValidationWithFlavors(ctx, validation) + if err != nil { + return nil, err + } + + // NOTE: preventing this merged resource be persisted on k8s api server. + mergedValidation.ResourceVersion = "" + + return mergedValidation, nil +} + +func (r *RpaasValidationReconciler) mergeValidationWithFlavors(ctx context.Context, validation *v1alpha1.RpaasValidation) (*v1alpha1.RpaasValidation, error) { + defaultFlavors, err := r.listDefaultFlavors(ctx, validation) + if err != nil { + return nil, err + } + + for _, defaultFlavor := range defaultFlavors { + if err := mergeValidationWithFlavor(validation, defaultFlavor); err != nil { + return nil, err + } + } + + for _, flavorName := range validation.Spec.Flavors { + flavorObjectKey := types.NamespacedName{ + Name: flavorName, + Namespace: validation.Namespace, + } + + if validation.Spec.PlanNamespace != "" { + flavorObjectKey.Namespace = validation.Spec.PlanNamespace + } + + var flavor v1alpha1.RpaasFlavor + if err := r.Client.Get(ctx, flavorObjectKey, &flavor); err != nil { + return nil, err + } + + if flavor.Spec.Default { + continue + } + + if err := mergeValidationWithFlavor(validation, flavor); err != nil { + return nil, err + } + } + + return validation, nil +} + +func (r *RpaasValidationReconciler) listDefaultFlavors(ctx context.Context, instance *v1alpha1.RpaasValidation) ([]v1alpha1.RpaasFlavor, error) { + flavorNamespace := instance.Namespace + if instance.Spec.PlanNamespace != "" { + flavorNamespace = instance.Spec.PlanNamespace + } + + return listDefaultFlavors(ctx, r.Client, flavorNamespace) +} + +func mergeValidationWithFlavor(validation *v1alpha1.RpaasValidation, flavor v1alpha1.RpaasFlavor) error { + if flavor.Spec.InstanceTemplate == nil { + return nil + } + + mergedInstanceSpec, err := mergeInstance(validation.Spec, *flavor.Spec.InstanceTemplate) + if err != nil { + return err + } + validation.Spec = mergedInstanceSpec + return nil +} + +func (r *RpaasValidationReconciler) renderTemplate(ctx context.Context, validation *v1alpha1.RpaasValidation, plan *v1alpha1.RpaasPlan) (string, error) { + rf := &referenceFinder{ + spec: &validation.Spec, + client: r.Client, + namespace: validation.Namespace, + } + + blocks, err := rf.getConfigurationBlocks(ctx, plan) + if err != nil { + return "", err + } + + if err = rf.updateLocationValues(ctx); err != nil { + return "", err + } + + cr, err := nginx.NewConfigurationRenderer(blocks) + if err != nil { + return "", err + } + + config := nginx.ConfigurationData{ + Instance: &v1alpha1.RpaasInstance{ + Spec: validation.Spec, + }, + Config: &plan.Spec.Config, + } + + return cr.Render(config) +} + +func newValidationConfigMap(validation *v1alpha1.RpaasValidation, renderedTemplate string) *corev1.ConfigMap { + return &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("validation-%s", validation.Name), + Namespace: validation.Namespace, + Labels: map[string]string{ + v1alpha1.RpaasOperatorValidationNameLabelKey: validation.Name, + }, + OwnerReferences: []metav1.OwnerReference{ + *metav1.NewControllerRef(validation, schema.GroupVersionKind{ + Group: v1alpha1.GroupVersion.Group, + Version: v1alpha1.GroupVersion.Version, + Kind: "RpaasValidation", + }), + }, + }, + Data: map[string]string{ + "nginx.conf": renderedTemplate, + }, + } +} + +func (r *RpaasValidationReconciler) getPod(ctx context.Context, namespace, name string) (*corev1.Pod, error) { + var existing corev1.Pod + err := r.Client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &existing) + + if err != nil { + return nil, err + } + + return &existing, nil +} + +func (r *RpaasValidationReconciler) reconcilePod(ctx context.Context, pod *corev1.Pod) (hasChanged bool, err error) { + existing, err := r.getPod(ctx, pod.Namespace, pod.Name) + if err != nil && k8sErrors.IsNotFound(err) { + err = r.Client.Create(ctx, pod) + if err != nil { + return false, err + } + + return true, nil + } else if err != nil { + return false, err + } + + if equality.Semantic.DeepDerivative(pod.Spec, existing.Spec) { + return false, nil + } + + err = r.Client.Delete(ctx, pod) + if err != nil { + return false, err + } + + err = r.Client.Create(ctx, pod) + if err != nil { + return false, err + } + + return true, nil +} + +func newValidationPod(validationMergedWithFlavors *v1alpha1.RpaasValidation, validationHash string, plan *v1alpha1.RpaasPlan, configMap *corev1.ConfigMap) *corev1.Pod { + n := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: configMap.Name, + Namespace: validationMergedWithFlavors.Namespace, + OwnerReferences: []metav1.OwnerReference{ + *metav1.NewControllerRef(validationMergedWithFlavors, schema.GroupVersionKind{ + Group: v1alpha1.GroupVersion.Group, + Version: v1alpha1.GroupVersion.Version, + Kind: "RpaasValidation", + }), + }, + Annotations: map[string]string{ + v1alpha1.RpaasOperatorValidationHashAnnotationKey: validationHash, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "validation", + Image: plan.Spec.Image, + VolumeMounts: validationMergedWithFlavors.Spec.PodTemplate.VolumeMounts, + Command: []string{ + "/bin/sh", + "-c", + "nginx -t 2> /dev/termination-log", + }, + }, + }, + RestartPolicy: "Never", + Volumes: validationMergedWithFlavors.Spec.PodTemplate.Volumes, + }, + } + + for i, f := range validationMergedWithFlavors.Spec.Files { + volumeName := fmt.Sprintf("extra-files-%d", i) + + n.Spec.Volumes = append(n.Spec.Volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: f.ConfigMap.LocalObjectReference, + Optional: ptr.To(false), + }, + }, + }) + + n.Spec.Containers[0].VolumeMounts = append(n.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: fmt.Sprintf("/etc/nginx/extra_files/%s", f.Name), + SubPath: f.Name, + ReadOnly: true, + }) + } + + volumeName := "nginx-config" + configMountPath := "/etc/nginx" + configFileName := "nginx.conf" + + n.Spec.Containers[0].VolumeMounts = append(n.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: fmt.Sprintf("%s/%s", configMountPath, configFileName), + SubPath: configFileName, + ReadOnly: true, + }) + + n.Spec.Volumes = append(n.Spec.Volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: configMap.Name, + }, + Optional: ptr.To(false), + }, + }, + }) + + if plan.Spec.Config.CacheEnabled != nil && *plan.Spec.Config.CacheEnabled { + n.Spec.Containers[0].VolumeMounts = append(n.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: "cache-vol", + MountPath: plan.Spec.Config.CachePath, + }) + + n.Spec.Volumes = append(n.Spec.Volumes, corev1.Volume{ + Name: "cache-vol", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumMemory, + }, + }, + }) + } + + for index, t := range validationMergedWithFlavors.Spec.TLS { + volumeName := fmt.Sprintf("nginx-certs-%d", index) + + n.Spec.Volumes = append(n.Spec.Volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: t.SecretName, + Optional: ptr.To(false), + }, + }, + }) + + n.Spec.Containers[0].VolumeMounts = append(n.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: filepath.Join(configMountPath, "certs", t.SecretName), + ReadOnly: true, + }) + } + + if isTLSSessionTicketEnabled(&validationMergedWithFlavors.Spec) { + n.Spec.Volumes = append(n.Spec.Volumes, corev1.Volume{ + Name: sessionTicketsVolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secretNameForTLSSessionTickets(validationMergedWithFlavors.Name), + Optional: ptr.To(false), + }, + }, + }) + + n.Spec.Containers[0].VolumeMounts = append(n.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: sessionTicketsVolumeName, + MountPath: sessionTicketsVolumeMountPath, + ReadOnly: true, + }) + } + + return n +} + +func (r *RpaasValidationReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.RpaasValidation{}). + Owns(&corev1.Pod{}). + Complete(r) +} diff --git a/controllers/validation_controller_test.go b/controllers/validation_controller_test.go new file mode 100644 index 000000000..40484c10c --- /dev/null +++ b/controllers/validation_controller_test.go @@ -0,0 +1,569 @@ +// Copyright 2024 tsuru authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package controllers + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + nginxv1alpha1 "github.com/tsuru/nginx-operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/tsuru/rpaas-operator/api/v1alpha1" + extensionsruntime "github.com/tsuru/rpaas-operator/pkg/runtime" +) + +func TestNewValidationPod(t *testing.T) { + pod := newValidationPod(&v1alpha1.RpaasValidation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid", + UID: types.UID("blah"), + }, + Spec: v1alpha1.RpaasInstanceSpec{}, + }, "hash", + &v1alpha1.RpaasPlan{}, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-config", + }, + }, + ) + + assert.Equal(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-config", + Annotations: map[string]string{ + "rpaas.extensions.tsuru.io/validation-hash": "hash", + }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "extensions.tsuru.io/v1alpha1", + Kind: "RpaasValidation", + Name: "valid", + UID: types.UID("blah"), + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + }, + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "nginx-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "valid-config", + }, + Optional: ptr.To(false), + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "validation", + Command: []string{ + "/bin/sh", + "-c", + "nginx -t 2> /dev/termination-log", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "nginx-config", + MountPath: "/etc/nginx/nginx.conf", + SubPath: "nginx.conf", + ReadOnly: true, + }, + }, + }, + }, + + RestartPolicy: "Never", + }, + }, pod) +} + +func TestNewValidationPodFullFeatured(t *testing.T) { + pod := newValidationPod( + &v1alpha1.RpaasValidation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid", + UID: types.UID("blah"), + }, + Spec: v1alpha1.RpaasInstanceSpec{ + Files: []v1alpha1.File{ + { + Name: "myfile", + ConfigMap: &corev1.ConfigMapKeySelector{ + Key: "myfile", + LocalObjectReference: corev1.LocalObjectReference{ + Name: "myfile", + }, + }, + }, + }, + TLS: []nginxv1alpha1.NginxTLS{ + { + SecretName: "secret-tls", + Hosts: []string{ + "host1", + "host2", + }, + }, + }, + + TLSSessionResumption: &v1alpha1.TLSSessionResumption{ + SessionTicket: &v1alpha1.TLSSessionTicket{ + KeepLastKeys: 2, + }, + }, + }, + }, + "hash", + &v1alpha1.RpaasPlan{ + Spec: v1alpha1.RpaasPlanSpec{ + Config: v1alpha1.NginxConfig{ + CacheEnabled: ptr.To(true), + CachePath: "/var/cache", + }, + }, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-config", + }, + }, + ) + + assert.Equal(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-config", + Annotations: map[string]string{ + "rpaas.extensions.tsuru.io/validation-hash": "hash", + }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "extensions.tsuru.io/v1alpha1", + Kind: "RpaasValidation", + Name: "valid", + UID: types.UID("blah"), + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + }, + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "extra-files-0", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "myfile", + }, + Optional: ptr.To(false), + }, + }, + }, + { + Name: "nginx-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "valid-config", + }, + Optional: ptr.To(false), + }, + }, + }, + { + Name: "cache-vol", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: "Memory", + }, + }, + }, + { + Name: "nginx-certs-0", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "secret-tls", + Optional: ptr.To(false), + }, + }, + }, + { + Name: "tls-session-tickets", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "valid-session-tickets", + Optional: ptr.To(false), + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "validation", + Command: []string{ + "/bin/sh", + "-c", + "nginx -t 2> /dev/termination-log", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "extra-files-0", + MountPath: "/etc/nginx/extra_files/myfile", + SubPath: "myfile", + ReadOnly: true, + }, + { + Name: "nginx-config", + MountPath: "/etc/nginx/nginx.conf", + SubPath: "nginx.conf", + ReadOnly: true, + }, + { + Name: "cache-vol", + MountPath: "/var/cache", + ReadOnly: false, + }, + { + Name: "nginx-certs-0", + MountPath: "/etc/nginx/certs/secret-tls", + ReadOnly: true, + }, + { + Name: "tls-session-tickets", + MountPath: "/etc/nginx/tickets", + ReadOnly: true, + }, + }, + }, + }, + + RestartPolicy: "Never", + }, + }, pod) +} + +func TestValidationControllerReconcicleSucceeded(t *testing.T) { + ctx := context.Background() + r := newRpaasValidationReconciler( + &v1alpha1.RpaasValidation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "punk", + Namespace: "default", + Generation: 10, + }, + Spec: v1alpha1.RpaasInstanceSpec{ + PlanName: "default", + }, + }, + &v1alpha1.RpaasPlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Namespace: "default", + }, + Spec: v1alpha1.RpaasPlanSpec{}, + }, + ) + + result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "punk"}}) + require.NoError(t, err) + assert.Equal(t, reconcile.Result{}, result) + + existingPod := &corev1.Pod{} + err = r.Client.Get(ctx, client.ObjectKey{Namespace: "default", Name: "validation-punk"}, existingPod) + require.NoError(t, err) + + assert.Equal(t, corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "nginx-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "validation-punk", + }, + Optional: ptr.To(false), + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "validation", + Command: []string{ + "/bin/sh", + "-c", + "nginx -t 2> /dev/termination-log", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "nginx-config", + MountPath: "/etc/nginx/nginx.conf", + SubPath: "nginx.conf", + ReadOnly: true, + }, + }, + }, + }, + + RestartPolicy: "Never", + }, existingPod.Spec) + + existingPod.Status.Phase = corev1.PodSucceeded + existingPod.Status.ContainerStatuses = []corev1.ContainerStatus{ + { + State: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ + ExitCode: 0, + }, + }, + }, + } + + err = r.Client.Update(ctx, existingPod) + require.NoError(t, err) + + _, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "punk"}}) + require.NoError(t, err) + + existing := &v1alpha1.RpaasValidation{} + err = r.Client.Get(ctx, client.ObjectKey{Namespace: "default", Name: "punk"}, existing) + require.NoError(t, err) + + assert.Equal(t, v1alpha1.RpaasValidationStatus{ + RevisionHash: "areskmeswpny2vmiz7sm2rrxvtlyhigbozfbkvqprcnnob57svha", + ObservedGeneration: 10, + Valid: ptr.To(true), + }, existing.Status) +} + +func TestValidationControllerReconcicleFailed(t *testing.T) { + ctx := context.Background() + r := newRpaasValidationReconciler( + &v1alpha1.RpaasValidation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "punk", + Namespace: "default", + Generation: 10, + }, + Spec: v1alpha1.RpaasInstanceSpec{ + PlanName: "default", + }, + }, + &v1alpha1.RpaasPlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Namespace: "default", + }, + Spec: v1alpha1.RpaasPlanSpec{}, + }, + ) + + result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "punk"}}) + require.NoError(t, err) + assert.Equal(t, reconcile.Result{}, result) + + existingPod := &corev1.Pod{} + err = r.Client.Get(ctx, client.ObjectKey{Namespace: "default", Name: "validation-punk"}, existingPod) + require.NoError(t, err) + + assert.Equal(t, corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "nginx-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "validation-punk", + }, + Optional: ptr.To(false), + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "validation", + Command: []string{ + "/bin/sh", + "-c", + "nginx -t 2> /dev/termination-log", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "nginx-config", + MountPath: "/etc/nginx/nginx.conf", + SubPath: "nginx.conf", + ReadOnly: true, + }, + }, + }, + }, + + RestartPolicy: "Never", + }, existingPod.Spec) + + existingPod.Status.Phase = corev1.PodFailed + existingPod.Status.ContainerStatuses = []corev1.ContainerStatus{ + { + State: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ + ExitCode: 1, + Message: "some nginx error", + }, + }, + }, + } + + err = r.Client.Update(ctx, existingPod) + require.NoError(t, err) + + _, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "punk"}}) + require.NoError(t, err) + + existing := &v1alpha1.RpaasValidation{} + err = r.Client.Get(ctx, client.ObjectKey{Namespace: "default", Name: "punk"}, existing) + require.NoError(t, err) + + assert.Equal(t, v1alpha1.RpaasValidationStatus{ + RevisionHash: "areskmeswpny2vmiz7sm2rrxvtlyhigbozfbkvqprcnnob57svha", + ObservedGeneration: 10, + Valid: ptr.To(false), + Error: "some nginx error", + }, existing.Status) + +} + +func TestValidationControllerReconcicleManyFlavors(t *testing.T) { + ctx := context.Background() + r := newRpaasValidationReconciler( + &v1alpha1.RpaasValidation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "punk", + Namespace: "default", + Generation: 10, + }, + Spec: v1alpha1.RpaasInstanceSpec{ + PlanName: "default", + Flavors: []string{"banana", "chocolate"}, + }, + }, + &v1alpha1.RpaasPlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Namespace: "default", + }, + Spec: v1alpha1.RpaasPlanSpec{}, + }, + + &v1alpha1.RpaasFlavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "banana", + Namespace: "default", + }, + }, + + &v1alpha1.RpaasFlavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "chocolate", + Namespace: "default", + }, + }, + ) + + result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "punk"}}) + require.NoError(t, err) + assert.Equal(t, reconcile.Result{}, result) + + existingPod := &corev1.Pod{} + err = r.Client.Get(ctx, client.ObjectKey{Namespace: "default", Name: "validation-punk"}, existingPod) + require.NoError(t, err) + + assert.Equal(t, corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "nginx-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "validation-punk", + }, + Optional: ptr.To(false), + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "validation", + Command: []string{ + "/bin/sh", + "-c", + "nginx -t 2> /dev/termination-log", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "nginx-config", + MountPath: "/etc/nginx/nginx.conf", + SubPath: "nginx.conf", + ReadOnly: true, + }, + }, + }, + }, + + RestartPolicy: "Never", + }, existingPod.Spec) + + existingPod.Status.Phase = corev1.PodSucceeded + existingPod.Status.ContainerStatuses = []corev1.ContainerStatus{ + { + State: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ + ExitCode: 0, + }, + }, + }, + } + + err = r.Client.Update(ctx, existingPod) + require.NoError(t, err) + + _, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "punk"}}) + require.NoError(t, err) + + existing := &v1alpha1.RpaasValidation{} + err = r.Client.Get(ctx, client.ObjectKey{Namespace: "default", Name: "punk"}, existing) + require.NoError(t, err) + + assert.Equal(t, v1alpha1.RpaasValidationStatus{ + RevisionHash: "53m2qsvzz6k4hqbabl2xngvpz52t3llgo2nnqwdcowceiay7f44a", + ObservedGeneration: 10, + Valid: ptr.To(true), + }, existing.Status) +} + +func newRpaasValidationReconciler(objs ...runtime.Object) *RpaasValidationReconciler { + return &RpaasValidationReconciler{ + Client: fake.NewClientBuilder().WithScheme(extensionsruntime.NewScheme()).WithRuntimeObjects(objs...).Build(), + Log: ctrl.Log, + } +} diff --git a/go.mod b/go.mod index 9b7cf758c..6e6e0d213 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/stern/stern v1.20.1 github.com/stretchr/testify v1.8.4 github.com/tsuru/go-tsuruclient v0.0.0-20230724221758-523def58dd2d - github.com/tsuru/nginx-operator v0.15.0 + github.com/tsuru/nginx-operator v0.15.2-0.20240515194244-a38b4b58e866 github.com/uber/jaeger-client-go v2.25.0+incompatible github.com/urfave/cli/v2 v2.3.0 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e @@ -43,9 +43,10 @@ require ( k8s.io/api v0.26.2 k8s.io/apimachinery v0.26.2 k8s.io/client-go v0.26.2 + k8s.io/klog/v2 v2.90.1 k8s.io/kubectl v0.26.2 k8s.io/metrics v0.26.2 - k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 + k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 sigs.k8s.io/controller-runtime v0.14.5 sigs.k8s.io/go-open-service-broker-client/v2 v2.0.0-20200925085050-ae25e62aaf10 ) @@ -156,7 +157,6 @@ require ( k8s.io/apiextensions-apiserver v0.26.2 // indirect k8s.io/cli-runtime v0.26.2 // indirect k8s.io/component-base v0.26.2 // indirect - k8s.io/klog/v2 v2.90.1 // indirect k8s.io/kube-aggregator v0.24.2 // indirect k8s.io/kube-openapi v0.0.0-20230303024457-afdc3dddf62d // indirect knative.dev/pkg v0.0.0-20230306194819-b77a78c6c0ad // indirect diff --git a/go.sum b/go.sum index 394f82997..ab7068ddf 100644 --- a/go.sum +++ b/go.sum @@ -798,8 +798,8 @@ github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa h1:JlLQP1xa13a994p/A github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa/go.mod h1:UibOSvkMFKRe/eiwktAPAvQG8L+p8nYsECJvu3Dgw7I= github.com/tsuru/go-tsuruclient v0.0.0-20230724221758-523def58dd2d h1:LAT4TbCfjK2CTjA0yM1Tv0RMd/GoN4Qjanx9IiL5NpU= github.com/tsuru/go-tsuruclient v0.0.0-20230724221758-523def58dd2d/go.mod h1:UNUsKvpma/GzkOmD7J3dNe/1KahJPFpVJ/SHh3RfycM= -github.com/tsuru/nginx-operator v0.15.0 h1:x/r5ZsXaAzBqpY3HAZDl6zVztOrc89oyykH7QsfWK+Q= -github.com/tsuru/nginx-operator v0.15.0/go.mod h1:SJQFeMLCkuFdwgsH8lxRgTU49DGwRixIZzPqqUqzQ7I= +github.com/tsuru/nginx-operator v0.15.2-0.20240515194244-a38b4b58e866 h1:aCoSpcfQuMztS/7xFrQ2ml02NxqipXYLMgJonyux6fk= +github.com/tsuru/nginx-operator v0.15.2-0.20240515194244-a38b4b58e866/go.mod h1:qdJQVY4buUQymyhcpYO99ZCfLMPRvcB/1ywK3PAh/+Q= github.com/tsuru/stern v1.20.2-0.20210928180051-1157b938dc3f h1:9dTZI6bQUVkKAsCnqaDl4V7fKAc5ErVpmX+bzevz3Cg= github.com/tsuru/stern v1.20.2-0.20210928180051-1157b938dc3f/go.mod h1:SUQKJ3CLsVARBwkz5z9IIL8Pj008JNj4U8eF50kkm2c= github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6 h1:1XDdWFAjIbCSG1OjN9v9KdWhuM8UtYlFcfHe/Ldkchk= @@ -1452,8 +1452,8 @@ k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= -k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/pkg v0.0.0-20230306194819-b77a78c6c0ad h1:cYCDdgSMOKiCGm6v1vvR2v4l/naGorbwoJKE/e39BJI= knative.dev/pkg v0.0.0-20230306194819-b77a78c6c0ad/go.mod h1:S+KfTInuwEkZSTwvWqrWZV/TEw6ps51GUGaSC1Fnbe0= mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w= diff --git a/internal/config/config.go b/internal/config/config.go index 1148f9a1b..3ed1dba95 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -58,12 +58,13 @@ type RpaasConfig struct { } type ClusterConfig struct { - Name string `json:"name"` - Default bool `json:"default"` - Address string `json:"address"` - Token string `json:"token"` - TokenFile string `json:"tokenFile"` - CA string `json:"ca"` + Name string `json:"name"` + Default bool `json:"default"` + DisableValidation bool `json:"disableValidation"` + Address string `json:"address"` + Token string `json:"token"` + TokenFile string `json:"tokenFile"` + CA string `json:"ca"` AuthProvider *clientcmdapi.AuthProviderConfig `json:"authProvider"` ExecProvider *clientcmdapi.ExecConfig `json:"execProvider"` diff --git a/internal/pkg/rpaas/errors.go b/internal/pkg/rpaas/errors.go index 57f667805..0ffe91c9d 100644 --- a/internal/pkg/rpaas/errors.go +++ b/internal/pkg/rpaas/errors.go @@ -11,7 +11,7 @@ import ( ) var ( - ErrNoPoolDefined = errors.New("No pool defined") + ErrNoPoolDefined = errors.New("no pool defined") ) type NotModifiedError struct { diff --git a/internal/pkg/rpaas/k8s.go b/internal/pkg/rpaas/k8s.go index 64d5afa0d..99b18459d 100644 --- a/internal/pkg/rpaas/k8s.go +++ b/internal/pkg/rpaas/k8s.go @@ -32,7 +32,6 @@ import ( nginxv1alpha1 "github.com/tsuru/nginx-operator/api/v1alpha1" nginxk8s "github.com/tsuru/nginx-operator/pkg/k8s" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" @@ -176,7 +175,7 @@ func (m *k8sRpaasManager) Debug(ctx context.Context, instanceName string, args D return executorStream(args.CommonTerminalArgs, executor, ctx) } -func (m *k8sRpaasManager) debugPodWithContainerStatus(ctx context.Context, args *DebugArgs, instanceName string) (*v1alpha1.RpaasInstance, string, *v1.ContainerStatus, error) { +func (m *k8sRpaasManager) debugPodWithContainerStatus(ctx context.Context, args *DebugArgs, instanceName string) (*v1alpha1.RpaasInstance, string, *corev1.ContainerStatus, error) { if args.Image == "" && config.Get().DebugImage == "" { return nil, "", nil, ValidationError{Msg: "Debug image not set and no default image configured"} } @@ -228,11 +227,11 @@ func (m *k8sRpaasManager) generateDebugContainer(ctx context.Context, args *Debu } instancePodWithDebug := instancePod.DeepCopy() instancePodWithDebug.Spec.EphemeralContainers = append(instancePod.Spec.EphemeralContainers, *debugContainer) - err = m.patchEphemeralContainers(ctx, instancePodWithDebug, instancePod, debugContainer) + err = m.patchEphemeralContainers(ctx, instancePodWithDebug, instancePod) return debugContainerName, err } -func (m *k8sRpaasManager) patchEphemeralContainers(ctx context.Context, instancePodWithDebug *v1.Pod, instancePod v1.Pod, debugContainer *v1.EphemeralContainer) error { +func (m *k8sRpaasManager) patchEphemeralContainers(ctx context.Context, instancePodWithDebug *corev1.Pod, instancePod corev1.Pod) error { podJS, err := json.Marshal(instancePod) if err != nil { return err @@ -540,16 +539,11 @@ func (m *k8sRpaasManager) DeleteBlock(ctx context.Context, instanceName, blockNa originalInstance := instance.DeepCopy() - if instance.Spec.Blocks == nil { - return NotFoundError{Msg: fmt.Sprintf("block %q not found", blockName)} - } - - blockType := v1alpha1.BlockType(blockName) - if _, ok := instance.Spec.Blocks[blockType]; !ok { - return NotFoundError{Msg: fmt.Sprintf("block %q not found", blockName)} + err = NewMutation(&instance.Spec).DeleteBlock(blockName) + if err != nil { + return err } - delete(instance.Spec.Blocks, blockType) return m.patchInstance(ctx, originalInstance, instance) } @@ -583,18 +577,11 @@ func (m *k8sRpaasManager) UpdateBlock(ctx context.Context, instanceName string, } originalInstance := instance.DeepCopy() - err = validateBlock(block) + err = NewMutation(&instance.Spec).UpdateBlock(block) if err != nil { return err } - if instance.Spec.Blocks == nil { - instance.Spec.Blocks = make(map[v1alpha1.BlockType]v1alpha1.Value) - } - - blockType := v1alpha1.BlockType(block.Name) - instance.Spec.Blocks[blockType] = v1alpha1.Value{Value: block.Content} - return m.patchInstance(ctx, originalInstance, instance) } @@ -868,7 +855,7 @@ func (m *k8sRpaasManager) getFlavors(ctx context.Context) ([]v1alpha1.RpaasFlavo return flavorList.Items, nil } -func (m *k8sRpaasManager) selectFlavor(ctx context.Context, flavors []v1alpha1.RpaasFlavor, name string) *v1alpha1.RpaasFlavor { +func (m *k8sRpaasManager) selectFlavor(flavors []v1alpha1.RpaasFlavor, name string) *v1alpha1.RpaasFlavor { for i := range flavors { if flavors[i].Name == name { return &flavors[i] @@ -885,46 +872,12 @@ func (m *k8sRpaasManager) BindApp(ctx context.Context, instanceName string, args } originalInstance := instance.DeepCopy() + internalBind := instance.BelongsToCluster(args.AppClusterName) - var host string - if args.AppClusterName != "" && instance.BelongsToCluster(args.AppClusterName) { - if len(args.AppInternalHosts) == 0 || args.AppInternalHosts[0] == "" { - return &ValidationError{Msg: "application internal hosts cannot be empty"} - } - - host = args.AppInternalHosts[0] - } else { - if len(args.AppHosts) == 0 || args.AppHosts[0] == "" { - return &ValidationError{Msg: "application hosts cannot be empty"} - } - - host = args.AppHosts[0] - } - - u, err := url.Parse(host) + err = NewMutation(&instance.Spec).BindApp(args, internalBind) if err != nil { return err } - if u.Scheme == "tcp" { - host = u.Host - } - - if u.Scheme == "udp" { - return &ValidationError{Msg: fmt.Sprintf("Unsupported host: %q", host)} - } - - if len(instance.Spec.Binds) > 0 { - for _, value := range instance.Spec.Binds { - if value.Host == host { - return &ConflictError{Msg: "instance already bound with this application"} - } - } - } - if instance.Spec.Binds == nil { - instance.Spec.Binds = make([]v1alpha1.Bind, 0) - } - - instance.Spec.Binds = append(instance.Spec.Binds, v1alpha1.Bind{Host: host, Name: args.AppName}) return m.patchInstance(ctx, originalInstance, instance) } @@ -937,23 +890,9 @@ func (m *k8sRpaasManager) UnbindApp(ctx context.Context, instanceName, appName s originalInstance := instance.DeepCopy() - if appName == "" { - return &ValidationError{Msg: "must specify an app name"} - } - - var found bool - for i, bind := range instance.Spec.Binds { - if bind.Name == appName { - found = true - binds := instance.Spec.Binds - // Remove the element at index i from instance.Spec.Binds *maintaining it's order! -> O(n)*. - instance.Spec.Binds = append(binds[:i], binds[i+1:]...) - break - } - } - - if !found { - return &NotFoundError{Msg: "app not found in instance bind list"} + err = NewMutation(&instance.Spec).UnbindApp(appName) + if err != nil { + return err } return m.patchInstance(ctx, originalInstance, instance) @@ -994,13 +933,11 @@ func (m *k8sRpaasManager) DeleteRoute(ctx context.Context, instanceName, path st } originalInstance := instance.DeepCopy() - - index, found := hasPath(*instance, path) - if !found { - return &NotFoundError{Msg: "path does not exist"} + err = NewMutation(&instance.Spec).DeleteRoute(path) + if err != nil { + return err } - instance.Spec.Locations = append(instance.Spec.Locations[:index], instance.Spec.Locations[index+1:]...) return m.patchInstance(ctx, originalInstance, instance) } @@ -1043,41 +980,14 @@ func (m *k8sRpaasManager) UpdateRoute(ctx context.Context, instanceName string, } originalInstance := instance.DeepCopy() - if err = validateRoute(route); err != nil { + err = NewMutation(&instance.Spec).UpdateRoute(route) + if err != nil { return err } - var content *v1alpha1.Value - if route.Content != "" { - content = &v1alpha1.Value{Value: route.Content} - } - - newLocation := v1alpha1.Location{ - Path: route.Path, - Destination: route.Destination, - ForceHTTPS: route.HTTPSOnly, - Content: content, - } - - if index, found := hasPath(*instance, route.Path); found { - instance.Spec.Locations[index] = newLocation - } else { - instance.Spec.Locations = append(instance.Spec.Locations, newLocation) - } - return m.patchInstance(ctx, originalInstance, instance) } -func hasPath(instance v1alpha1.RpaasInstance, path string) (index int, found bool) { - for i, location := range instance.Spec.Locations { - if location.Path == path { - return i, true - } - } - - return -} - func validateContent(content string) error { denyPatterns := config.Get().ConfigDenyPatterns for _, re := range denyPatterns { @@ -1088,52 +998,6 @@ func validateContent(content string) error { return nil } -func validateBlock(block ConfigurationBlock) error { - blockType := v1alpha1.BlockType(block.Name) - if !isBlockTypeAllowed(blockType) { - return ValidationError{Msg: fmt.Sprintf("block %q is not allowed", block.Name)} - } - if block.Content == "" { - return &ValidationError{Msg: "content is required"} - } - err := validateContent(block.Content) - if err != nil { - return err - } - return nil -} - -func validateRoute(r Route) error { - if r.Path == "" { - return &ValidationError{Msg: "path is required"} - } - - if !regexp.MustCompile(`^/[^ ]*`).MatchString(r.Path) { - return &ValidationError{Msg: "invalid path format"} - } - - if r.Content == "" && r.Destination == "" { - return &ValidationError{Msg: "either content or destination are required"} - } - - if r.Content != "" && r.Destination != "" { - return &ValidationError{Msg: "cannot set both content and destination"} - } - - if r.Content != "" && r.HTTPSOnly { - return &ValidationError{Msg: "cannot set both content and httpsonly"} - } - - if r.Content != "" { - err := validateContent(r.Content) - if err != nil { - return err - } - } - - return nil -} - func (m *k8sRpaasManager) getPlan(ctx context.Context, name string) (*v1alpha1.RpaasPlan, error) { if name == "" { return m.getDefaultPlan(ctx) @@ -1311,7 +1175,7 @@ func (m *k8sRpaasManager) validateFlavors(ctx context.Context, instance *v1alpha added, removed := diffFlavors(existingFlavors, flavors) for _, f := range added { - flavorObj := m.selectFlavor(ctx, allFlavors, f) + flavorObj := m.selectFlavor(allFlavors, f) if flavorObj == nil { return &ValidationError{Msg: fmt.Sprintf("flavor %q not found", f)} } @@ -1326,7 +1190,7 @@ func (m *k8sRpaasManager) validateFlavors(ctx context.Context, instance *v1alpha } for _, f := range removed { - flavorObj := m.selectFlavor(ctx, allFlavors, f) + flavorObj := m.selectFlavor(allFlavors, f) if flavorObj == nil { continue } @@ -1961,7 +1825,7 @@ func sortAddresses(addresses []clientTypes.InstanceAddress) { }) } -func (m *k8sRpaasManager) loadBalancerInstanceAddresses(ctx context.Context, svc *v1.Service) ([]clientTypes.InstanceAddress, error) { +func (m *k8sRpaasManager) loadBalancerInstanceAddresses(ctx context.Context, svc *corev1.Service) ([]clientTypes.InstanceAddress, error) { var addresses []clientTypes.InstanceAddress if isLoadBalancerReady(svc.Status.LoadBalancer.Ingress) { @@ -2038,7 +1902,7 @@ func (m *k8sRpaasManager) ingressAddresses(ctx context.Context, ing *networkingv return addresses, nil } -func formatEventsToString(events []v1.Event) string { +func formatEventsToString(events []corev1.Event) string { var buf bytes.Buffer reasonMap := map[string]bool{} @@ -2054,7 +1918,7 @@ func formatEventsToString(events []v1.Event) string { return buf.String() } -func isLoadBalancerReady(ings []v1.LoadBalancerIngress) bool { +func isLoadBalancerReady(ings []corev1.LoadBalancerIngress) bool { if len(ings) == 0 { return false } diff --git a/internal/pkg/rpaas/mutation.go b/internal/pkg/rpaas/mutation.go new file mode 100644 index 000000000..ce8e08775 --- /dev/null +++ b/internal/pkg/rpaas/mutation.go @@ -0,0 +1,207 @@ +package rpaas + +import ( + "fmt" + "net/url" + "regexp" + + "github.com/tsuru/rpaas-operator/api/v1alpha1" +) + +type mutation struct { + spec *v1alpha1.RpaasInstanceSpec +} + +// mutation represents all atomic operations that user can change the RpaasInstanceSpec +func NewMutation(spec *v1alpha1.RpaasInstanceSpec) *mutation { + return &mutation{spec} +} + +func (m *mutation) UpdateBlock(block ConfigurationBlock) error { + err := validateBlock(block) + if err != nil { + return err + } + + if m.spec.Blocks == nil { + m.spec.Blocks = make(map[v1alpha1.BlockType]v1alpha1.Value) + } + + blockType := v1alpha1.BlockType(block.Name) + m.spec.Blocks[blockType] = v1alpha1.Value{Value: block.Content} + + return nil +} + +func (m *mutation) DeleteBlock(blockName string) error { + if m.spec.Blocks == nil { + return NotFoundError{Msg: fmt.Sprintf("block %q not found", blockName)} + } + + blockType := v1alpha1.BlockType(blockName) + if _, ok := m.spec.Blocks[blockType]; !ok { + return NotFoundError{Msg: fmt.Sprintf("block %q not found", blockName)} + } + + delete(m.spec.Blocks, blockType) + return nil +} + +func (m *mutation) UpdateRoute(route Route) error { + if err := validateRoute(route); err != nil { + return err + } + + var content *v1alpha1.Value + if route.Content != "" { + content = &v1alpha1.Value{Value: route.Content} + } + + newLocation := v1alpha1.Location{ + Path: route.Path, + Destination: route.Destination, + ForceHTTPS: route.HTTPSOnly, + Content: content, + } + + if index, found := hasPath(m.spec, route.Path); found { + m.spec.Locations[index] = newLocation + } else { + m.spec.Locations = append(m.spec.Locations, newLocation) + } + + return nil +} + +func (m *mutation) DeleteRoute(path string) error { + index, found := hasPath(m.spec, path) + if !found { + return &NotFoundError{Msg: "path does not exist"} + } + + m.spec.Locations = append(m.spec.Locations[:index], m.spec.Locations[index+1:]...) + return nil +} + +func (m *mutation) BindApp(args BindAppArgs, internalBind bool) error { + var host string + if args.AppClusterName != "" && internalBind { + if len(args.AppInternalHosts) == 0 || args.AppInternalHosts[0] == "" { + return &ValidationError{Msg: "application internal hosts cannot be empty"} + } + + host = args.AppInternalHosts[0] + } else { + if len(args.AppHosts) == 0 || args.AppHosts[0] == "" { + return &ValidationError{Msg: "application hosts cannot be empty"} + } + + host = args.AppHosts[0] + } + + u, err := url.Parse(host) + if err != nil { + return err + } + if u.Scheme == "tcp" { + host = u.Host + } + + if u.Scheme == "udp" { + return &ValidationError{Msg: fmt.Sprintf("Unsupported host: %q", host)} + } + + if len(m.spec.Binds) > 0 { + for _, value := range m.spec.Binds { + if value.Host == host { + return &ConflictError{Msg: "instance already bound with this application"} + } + } + } + if m.spec.Binds == nil { + m.spec.Binds = make([]v1alpha1.Bind, 0) + } + + m.spec.Binds = append(m.spec.Binds, v1alpha1.Bind{Host: host, Name: args.AppName}) + + return nil +} + +func (m *mutation) UnbindApp(appName string) error { + if appName == "" { + return &ValidationError{Msg: "must specify an app name"} + } + + var found bool + for i, bind := range m.spec.Binds { + if bind.Name == appName { + found = true + binds := m.spec.Binds + // Remove the element at index i from instance.Spec.Binds *maintaining it's order! -> O(n)*. + m.spec.Binds = append(binds[:i], binds[i+1:]...) + break + } + } + + if !found { + return &NotFoundError{Msg: "app not found in instance bind list"} + } + + return nil +} + +func validateBlock(block ConfigurationBlock) error { + blockType := v1alpha1.BlockType(block.Name) + if !isBlockTypeAllowed(blockType) { + return ValidationError{Msg: fmt.Sprintf("block %q is not allowed", block.Name)} + } + if block.Content == "" { + return &ValidationError{Msg: "content is required"} + } + err := validateContent(block.Content) + if err != nil { + return err + } + return nil +} + +func validateRoute(r Route) error { + if r.Path == "" { + return &ValidationError{Msg: "path is required"} + } + + if !regexp.MustCompile(`^/[^ ]*`).MatchString(r.Path) { + return &ValidationError{Msg: "invalid path format"} + } + + if r.Content == "" && r.Destination == "" { + return &ValidationError{Msg: "either content or destination are required"} + } + + if r.Content != "" && r.Destination != "" { + return &ValidationError{Msg: "cannot set both content and destination"} + } + + if r.Content != "" && r.HTTPSOnly { + return &ValidationError{Msg: "cannot set both content and httpsonly"} + } + + if r.Content != "" { + err := validateContent(r.Content) + if err != nil { + return err + } + } + + return nil +} + +func hasPath(spec *v1alpha1.RpaasInstanceSpec, path string) (index int, found bool) { + for i, location := range spec.Locations { + if location.Path == path { + return i, true + } + } + + return +} diff --git a/internal/pkg/rpaas/validation/manager.go b/internal/pkg/rpaas/validation/manager.go new file mode 100644 index 000000000..a6bfc559d --- /dev/null +++ b/internal/pkg/rpaas/validation/manager.go @@ -0,0 +1,366 @@ +// Copyright 2024 tsuru authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package validation + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/tsuru/rpaas-operator/api/v1alpha1" + "github.com/tsuru/rpaas-operator/internal/pkg/rpaas" +) + +var _ rpaas.RpaasManager = &validationManager{} + +type validationManager struct { + rpaas.RpaasManager + cli client.Client +} + +func New(manager rpaas.RpaasManager, cli client.Client) rpaas.RpaasManager { + return &validationManager{ + RpaasManager: manager, + cli: cli, + } +} + +func (v *validationManager) DeleteBlock(ctx context.Context, instanceName, blockName string) error { + validation, err := v.validationCRD(ctx, instanceName) + if err != nil { + return err + } + + err = rpaas.NewMutation(&validation.Spec).DeleteBlock(blockName) + if err != nil { + return err + } + + err = v.waitController(ctx, validation) + if err != nil { + return err + } + + return v.RpaasManager.DeleteBlock(ctx, instanceName, blockName) +} + +func (v *validationManager) UpdateBlock(ctx context.Context, instanceName string, block rpaas.ConfigurationBlock) error { + validation, err := v.validationCRD(ctx, instanceName) + if err != nil { + return err + } + + err = rpaas.NewMutation(&validation.Spec).UpdateBlock(block) + if err != nil { + return err + } + + err = v.waitController(ctx, validation) + if err != nil { + return err + } + + return v.RpaasManager.UpdateBlock(ctx, instanceName, block) +} + +func (v *validationManager) CreateExtraFiles(ctx context.Context, instanceName string, files ...rpaas.File) error { + validation, err := v.validationCRD(ctx, instanceName) + if err != nil { + return err + } + + tempConfigMaps := []*corev1.ConfigMap{} + defer func() { + for _, configMap := range tempConfigMaps { + deleteErr := v.cli.Delete(ctx, configMap) + if deleteErr != nil { + klog.Error("could not delete temporary configmap", deleteErr) + } + } + + }() + + for _, f := range files { + _, found := findFileByName(validation.Spec.Files, f.Name) + if found { + return rpaas.ErrExtraFileAlreadyExists + } + } + + if validation.Spec.Files == nil { + validation.Spec.Files = make([]v1alpha1.File, 0, len(files)) + } + + for _, f := range files { + configMap, configMapErr := v.createTemporaryFileInConfigMap(ctx, validation, f) + if configMapErr != nil { + return configMapErr + } + + tempConfigMaps = append(tempConfigMaps, configMap) + + validation.Spec.Files = append(validation.Spec.Files, v1alpha1.File{ + Name: f.Name, + ConfigMap: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: configMap.Name}, + Key: f.Name, + }, + }) + } + + err = v.waitController(ctx, validation) + if err != nil { + return err + } + + return v.RpaasManager.CreateExtraFiles(ctx, instanceName, files...) +} + +func (v *validationManager) DeleteExtraFiles(ctx context.Context, instanceName string, filenames ...string) error { + validation, err := v.validationCRD(ctx, instanceName) + if err != nil { + return err + } + + for _, name := range filenames { + if index, found := findFileByName(validation.Spec.Files, name); found { + validation.Spec.Files = append(validation.Spec.Files[:index], validation.Spec.Files[index+1:]...) + } + } + + err = v.waitController(ctx, validation) + if err != nil { + return err + } + + return v.RpaasManager.DeleteExtraFiles(ctx, instanceName, filenames...) +} + +func (v *validationManager) UpdateExtraFiles(ctx context.Context, instanceName string, files ...rpaas.File) error { + validation, err := v.validationCRD(ctx, instanceName) + if err != nil { + return err + } + + tempConfigMaps := []*corev1.ConfigMap{} + defer func() { + for _, configMap := range tempConfigMaps { + deleteErr := v.cli.Delete(ctx, configMap) + if deleteErr != nil { + klog.Error("could not delete temporary configmap", deleteErr) + } + } + }() + + for _, f := range files { + position, found := findFileByName(validation.Spec.Files, f.Name) + if !found { + return rpaas.ErrNoSuchExtraFile + } + + configMap, configMapErr := v.createTemporaryFileInConfigMap(ctx, validation, f) + if configMapErr != nil { + return configMapErr + } + + tempConfigMaps = append(tempConfigMaps, configMap) + + validation.Spec.Files[position] = v1alpha1.File{ + Name: f.Name, + ConfigMap: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: configMap.Name}, + Key: f.Name, + }, + } + } + + err = v.waitController(ctx, validation) + if err != nil { + return err + } + + return v.RpaasManager.UpdateExtraFiles(ctx, instanceName, files...) +} + +func (m *validationManager) createTemporaryFileInConfigMap(ctx context.Context, validation *v1alpha1.RpaasValidation, f rpaas.File) (*corev1.ConfigMap, error) { + newConfigMap := newConfigMapForFile(validation, f) + if err := m.cli.Create(ctx, newConfigMap); err != nil { + return nil, err + } + + return newConfigMap, nil + +} + +func newConfigMapForFile(validation *v1alpha1.RpaasValidation, f rpaas.File) *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("%s-validation-file-", validation.Name), + Namespace: validation.Namespace, + // we need TO discover UID of validation early + /*OwnerReferences: []metav1.OwnerReference{ + *metav1.NewControllerRef(validation, schema.GroupVersionKind{ + Group: v1alpha1.GroupVersion.Group, + Version: v1alpha1.GroupVersion.Version, + Kind: "RpaasValidation", + }), + },*/ + }, + BinaryData: map[string][]byte{f.Name: f.Content}, + } +} + +func findFileByName(files []v1alpha1.File, filename string) (int, bool) { + for i := range files { + if files[i].Name == filename { + return i, true + } + } + + return -1, false +} + +func (v *validationManager) DeleteRoute(ctx context.Context, instanceName, path string) error { + validation, err := v.validationCRD(ctx, instanceName) + if err != nil { + return err + } + + err = rpaas.NewMutation(&validation.Spec).DeleteRoute(path) + if err != nil { + return err + } + + err = v.waitController(ctx, validation) + if err != nil { + return err + } + + return v.RpaasManager.DeleteRoute(ctx, instanceName, path) +} + +func (v *validationManager) UpdateRoute(ctx context.Context, instanceName string, route rpaas.Route) error { + validation, err := v.validationCRD(ctx, instanceName) + if err != nil { + return err + } + + err = rpaas.NewMutation(&validation.Spec).UpdateRoute(route) + if err != nil { + return err + } + + err = v.waitController(ctx, validation) + if err != nil { + return err + } + + return v.RpaasManager.UpdateRoute(ctx, instanceName, route) +} + +func (v *validationManager) BindApp(ctx context.Context, instanceName string, args rpaas.BindAppArgs) error { + validation, err := v.validationCRD(ctx, instanceName) + if err != nil { + return err + } + + internalBind := validation.BelongsToCluster(args.AppClusterName) + err = rpaas.NewMutation(&validation.Spec).BindApp(args, internalBind) + if err != nil { + return err + } + + err = v.waitController(ctx, validation) + if err != nil { + return err + } + + return v.RpaasManager.BindApp(ctx, instanceName, args) +} + +func (v *validationManager) UnbindApp(ctx context.Context, instanceName, appName string) error { + validation, err := v.validationCRD(ctx, instanceName) + if err != nil { + return err + } + + err = rpaas.NewMutation(&validation.Spec).UnbindApp(appName) + if err != nil { + return err + } + + err = v.waitController(ctx, validation) + if err != nil { + return err + } + + return v.RpaasManager.UnbindApp(ctx, instanceName, appName) +} + +func (v *validationManager) validationCRD(ctx context.Context, instanceName string) (*v1alpha1.RpaasValidation, error) { + instance, err := v.GetInstance(ctx, instanceName) + if err != nil { + return nil, err + } + + return &v1alpha1.RpaasValidation{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.Name, + Namespace: instance.Namespace, + Labels: instance.Labels, + Annotations: instance.Annotations, + }, + Spec: instance.Spec, + }, nil +} + +func (v *validationManager) waitController(ctx context.Context, validation *v1alpha1.RpaasValidation) error { + existingValidation := v1alpha1.RpaasValidation{} + err := v.cli.Get(ctx, client.ObjectKeyFromObject(validation), &existingValidation) + if err != nil { + if !k8sErrors.IsNotFound(err) { + return err + } + err = v.cli.Create(ctx, validation, &client.CreateOptions{}) + if err != nil { + return err + } + } else { + validation.ObjectMeta.ResourceVersion = existingValidation.ResourceVersion + err = v.cli.Update(ctx, validation, &client.UpdateOptions{}) + if err != nil { + return err + } + } + + maxRetries := 30 + + for retry := 0; retry < maxRetries; retry++ { + existingValidation = v1alpha1.RpaasValidation{} + err = v.cli.Get(ctx, client.ObjectKeyFromObject(validation), &existingValidation) + if err != nil { + return err + } + + isLastStatus := existingValidation.Generation == existingValidation.Status.ObservedGeneration + + if isLastStatus && existingValidation.Status.Valid != nil { + if *existingValidation.Status.Valid { + return nil + } + + return &rpaas.ValidationError{Msg: existingValidation.Status.Error} + } + + time.Sleep(time.Second) + } + + return &rpaas.ValidationError{Msg: "rpaas-operator timeout"} +} diff --git a/internal/pkg/rpaas/validation/manager_test.go b/internal/pkg/rpaas/validation/manager_test.go new file mode 100644 index 000000000..c618485c0 --- /dev/null +++ b/internal/pkg/rpaas/validation/manager_test.go @@ -0,0 +1,415 @@ +package validation + +import ( + "context" + "fmt" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + nginxv1alpha1 "github.com/tsuru/nginx-operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + metricsv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" + clientFake "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/tsuru/rpaas-operator/api/v1alpha1" + "github.com/tsuru/rpaas-operator/internal/pkg/rpaas" + "github.com/tsuru/rpaas-operator/internal/pkg/rpaas/fake" +) + +func newScheme() *runtime.Scheme { + scheme := runtime.NewScheme() + corev1.AddToScheme(scheme) + networkingv1.AddToScheme(scheme) + v1alpha1.SchemeBuilder.AddToScheme(scheme) + metricsv1beta1.SchemeBuilder.AddToScheme(scheme) + nginxv1alpha1.SchemeBuilder.AddToScheme(scheme) + return scheme +} + +func TestUpdateBlock(t *testing.T) { + block := rpaas.ConfigurationBlock{ + Name: "http", + Content: "blah;", + } + + cli := clientFake.NewClientBuilder().WithScheme(newScheme()).WithRuntimeObjects().Build() + + baseManager := &fake.RpaasManager{ + FakeUpdateBlock: func(instance string, updateBlock rpaas.ConfigurationBlock) error { + assert.Equal(t, instance, "blah") + assert.Equal(t, block, updateBlock) + return nil + }, + FakeGetInstance: func(instanceName string) (*v1alpha1.RpaasInstance, error) { + return &v1alpha1.RpaasInstance{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: instanceName, + }, + Spec: v1alpha1.RpaasInstanceSpec{}, + }, nil + }, + } + + stop := fakeValidationController(cli, true, "", nil) + defer stop() + + validationMngr := New(baseManager, cli) + + err := validationMngr.UpdateBlock(context.TODO(), "blah", block) + + require.NoError(t, err) +} + +func TestUpdateBlockControllerError(t *testing.T) { + block := rpaas.ConfigurationBlock{ + Name: "http", + Content: "blah;", + } + + cli := clientFake.NewClientBuilder().WithScheme(newScheme()).WithRuntimeObjects().Build() + + baseManager := &fake.RpaasManager{ + FakeUpdateBlock: func(instance string, updateBlock rpaas.ConfigurationBlock) error { + assert.Equal(t, instance, "blah") + assert.Equal(t, block, updateBlock) + return nil + }, + FakeGetInstance: func(instanceName string) (*v1alpha1.RpaasInstance, error) { + return &v1alpha1.RpaasInstance{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: instanceName, + }, + Spec: v1alpha1.RpaasInstanceSpec{}, + }, nil + }, + } + + stop := fakeValidationController(cli, false, "rpaas-operator error", nil) + defer stop() + + validationMngr := New(baseManager, cli) + + err := validationMngr.UpdateBlock(context.TODO(), "blah", block) + + require.Equal(t, &rpaas.ValidationError{Msg: "rpaas-operator error"}, err) +} + +func TestDeleteBlock(t *testing.T) { + cli := clientFake.NewClientBuilder().WithScheme(newScheme()).WithRuntimeObjects().Build() + + baseManager := &fake.RpaasManager{ + FakeDeleteBlock: func(instance string, blockName string) error { + assert.Equal(t, instance, "blah") + assert.Equal(t, "http", blockName) + return nil + }, + FakeGetInstance: func(instanceName string) (*v1alpha1.RpaasInstance, error) { + return &v1alpha1.RpaasInstance{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: instanceName, + }, + Spec: v1alpha1.RpaasInstanceSpec{ + Blocks: map[v1alpha1.BlockType]v1alpha1.Value{ + "http": { + Value: "blah;", + }, + }, + }, + }, nil + }, + } + + stop := fakeValidationController(cli, true, "", nil) + defer stop() + + validationMngr := New(baseManager, cli) + + err := validationMngr.DeleteBlock(context.TODO(), "blah", "http") + + require.NoError(t, err) +} + +func TestDeleteBlockError(t *testing.T) { + cli := clientFake.NewClientBuilder().WithScheme(newScheme()).WithRuntimeObjects().Build() + + baseManager := &fake.RpaasManager{ + FakeDeleteBlock: func(instance string, blockName string) error { + assert.Equal(t, instance, "blah") + assert.Equal(t, "http", blockName) + return nil + }, + FakeGetInstance: func(instanceName string) (*v1alpha1.RpaasInstance, error) { + return &v1alpha1.RpaasInstance{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: instanceName, + }, + Spec: v1alpha1.RpaasInstanceSpec{ + Blocks: map[v1alpha1.BlockType]v1alpha1.Value{ + "http": { + Value: "blah;", + }, + }, + }, + }, nil + }, + } + + stop := fakeValidationController(cli, false, "validation error from rpaas-operator", nil) + defer stop() + + validationMngr := New(baseManager, cli) + + err := validationMngr.DeleteBlock(context.TODO(), "blah", "http") + + require.Equal(t, &rpaas.ValidationError{Msg: "validation error from rpaas-operator"}, err) +} + +func TestCreateExtraFiles(t *testing.T) { + + cli := clientFake.NewClientBuilder().WithScheme(newScheme()).WithRuntimeObjects().Build() + + baseManager := &fake.RpaasManager{ + FakeCreateExtraFiles: func(instance string, files ...rpaas.File) error { + assert.Equal(t, instance, "blah") + assert.Equal(t, rpaas.File{ + Name: "myfile", + Content: []byte("mycontent"), + }, files[0]) + + return nil + }, + FakeGetInstance: func(instanceName string) (*v1alpha1.RpaasInstance, error) { + return &v1alpha1.RpaasInstance{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: instanceName, + }, + Spec: v1alpha1.RpaasInstanceSpec{}, + }, nil + }, + } + + preUpdate := func() { + defer recover() + configMaps := corev1.ConfigMapList{} + + err := cli.List(context.Background(), &configMaps) + + if err != nil { + fmt.Println("stop controller", err) + } + + require.Len(t, configMaps.Items, 1) + assert.True(t, strings.HasPrefix(configMaps.Items[0].Name, "blah-validation-file-")) + assert.Equal(t, map[string][]byte{ + "myfile": []byte("mycontent"), + }, configMaps.Items[0].BinaryData) + } + + stop := fakeValidationController(cli, true, "", preUpdate) + defer stop() + + validationMngr := New(baseManager, cli) + + err := validationMngr.CreateExtraFiles(context.TODO(), "blah", rpaas.File{ + Name: "myfile", + Content: []byte("mycontent"), + }) + + require.NoError(t, err) + + rpaas := v1alpha1.RpaasValidation{} + err = cli.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "blah"}, &rpaas) + require.NoError(t, err) + + require.Len(t, rpaas.Spec.Files, 1) + assert.Equal(t, "myfile", rpaas.Spec.Files[0].Name) + assert.Equal(t, "myfile", rpaas.Spec.Files[0].ConfigMap.Key) + assert.True(t, strings.HasPrefix(rpaas.Spec.Files[0].ConfigMap.LocalObjectReference.Name, "blah-validation-file-")) +} + +func TestUpdateExtraFiles(t *testing.T) { + + cli := clientFake.NewClientBuilder().WithScheme(newScheme()).WithRuntimeObjects().Build() + + baseManager := &fake.RpaasManager{ + FakeCreateExtraFiles: func(instance string, files ...rpaas.File) error { + assert.Equal(t, instance, "blah") + assert.Equal(t, rpaas.File{ + Name: "myfile", + Content: []byte("mycontent"), + }, files[0]) + + return nil + }, + FakeGetInstance: func(instanceName string) (*v1alpha1.RpaasInstance, error) { + return &v1alpha1.RpaasInstance{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: instanceName, + }, + Spec: v1alpha1.RpaasInstanceSpec{ + Files: []v1alpha1.File{ + { + Name: "myfile", + ConfigMap: &corev1.ConfigMapKeySelector{ + Key: "myfile", + LocalObjectReference: corev1.LocalObjectReference{ + Name: "myconfigmap", + }, + }, + }, + }, + }, + }, nil + }, + } + + preUpdate := func() { + defer recover() + configMaps := corev1.ConfigMapList{} + + err := cli.List(context.Background(), &configMaps) + + if err != nil { + fmt.Println("stop controller", err) + } + + require.Len(t, configMaps.Items, 1) + assert.True(t, strings.HasPrefix(configMaps.Items[0].Name, "blah-validation-file-")) + assert.Equal(t, map[string][]byte{ + "myfile": []byte("mycontent"), + }, configMaps.Items[0].BinaryData) + } + + stop := fakeValidationController(cli, true, "", preUpdate) + defer stop() + + validationMngr := New(baseManager, cli) + + err := validationMngr.UpdateExtraFiles(context.TODO(), "blah", rpaas.File{ + Name: "myfile", + Content: []byte("mycontent"), + }) + + require.NoError(t, err) + + rpaas := v1alpha1.RpaasValidation{} + err = cli.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "blah"}, &rpaas) + require.NoError(t, err) + + require.Len(t, rpaas.Spec.Files, 1) + assert.Equal(t, "myfile", rpaas.Spec.Files[0].Name) + assert.Equal(t, "myfile", rpaas.Spec.Files[0].ConfigMap.Key) + assert.True(t, strings.HasPrefix(rpaas.Spec.Files[0].ConfigMap.LocalObjectReference.Name, "blah-validation-file-")) +} + +func TestDeleteExtraFiles(t *testing.T) { + + cli := clientFake.NewClientBuilder().WithScheme(newScheme()).WithRuntimeObjects().Build() + + baseManager := &fake.RpaasManager{ + FakeCreateExtraFiles: func(instance string, files ...rpaas.File) error { + assert.Equal(t, instance, "blah") + assert.Equal(t, rpaas.File{ + Name: "myfile", + Content: []byte("mycontent"), + }, files[0]) + + return nil + }, + FakeGetInstance: func(instanceName string) (*v1alpha1.RpaasInstance, error) { + return &v1alpha1.RpaasInstance{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: instanceName, + }, + Spec: v1alpha1.RpaasInstanceSpec{ + Files: []v1alpha1.File{ + { + Name: "myfile", + ConfigMap: &corev1.ConfigMapKeySelector{ + Key: "myfile", + LocalObjectReference: corev1.LocalObjectReference{ + Name: "myconfigmap", + }, + }, + }, + }, + }, + }, nil + }, + } + + stop := fakeValidationController(cli, true, "", nil) + defer stop() + + validationMngr := New(baseManager, cli) + + err := validationMngr.DeleteExtraFiles(context.TODO(), "blah", "myfile") + + require.NoError(t, err) + + rpaas := v1alpha1.RpaasValidation{} + err = cli.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "blah"}, &rpaas) + require.NoError(t, err) + + require.Len(t, rpaas.Spec.Files, 0) +} + +func fakeValidationController(cli client.Client, valid bool, errorMesssage string, preUpdate func()) (stop func()) { + var stopped int32 + stop = func() { + atomic.StoreInt32(&stopped, 1) + } + + go func() { + for { + list := v1alpha1.RpaasValidationList{} + err := cli.List(context.Background(), &list, &client.ListOptions{}) + if err != nil { + fmt.Println("stop controller", err) + } + + itemsLoop: + for _, item := range list.Items { + if item.Status.Valid != nil { + continue itemsLoop + } + + if preUpdate != nil { + preUpdate() + } + + item.Status.Valid = &valid + item.Status.Error = errorMesssage + + cli.Update(context.Background(), &item) + if err != nil { + fmt.Println("stop controller", err) + } + } + + if atomic.LoadInt32(&stopped) == 1 { + break + } + + time.Sleep(time.Millisecond * 100) + } + }() + + return stop +} diff --git a/main.go b/main.go index fafd91caf..49ecf21f4 100644 --- a/main.go +++ b/main.go @@ -78,6 +78,14 @@ func main() { os.Exit(1) } + if err = (&controllers.RpaasValidationReconciler{ + Client: mgr.GetClient(), + Log: mgr.GetLogger().WithName("controllers").WithName("RpaasValidation"), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "RpaasValidation") + os.Exit(1) + } + if err = (&controllers.RpaasInstanceReconciler{ Client: mgr.GetClient(), SystemRateLimiter: controllers.NewSystemRolloutRateLimiter(opts.systemRateLimitOperations, opts.systemRateLimitInterval), diff --git a/pkg/web/target/multi-cluster.go b/pkg/web/target/multi-cluster.go index 97936d7b8..1577a409d 100644 --- a/pkg/web/target/multi-cluster.go +++ b/pkg/web/target/multi-cluster.go @@ -14,6 +14,7 @@ import ( "github.com/tsuru/rpaas-operator/internal/config" "github.com/tsuru/rpaas-operator/internal/pkg/rpaas" + "github.com/tsuru/rpaas-operator/internal/pkg/rpaas/validation" "github.com/tsuru/rpaas-operator/pkg/observability" extensionsruntime "github.com/tsuru/rpaas-operator/pkg/runtime" ) @@ -63,6 +64,7 @@ func NewMultiClustersFactory(clusters []config.ClusterConfig) Factory { func (m *multiClusterFactory) Manager(ctx context.Context, headers http.Header) (rpaas.RpaasManager, error) { clusterName := headers.Get("X-Tsuru-Cluster-Name") address := headers.Get("X-Tsuru-Cluster-Addresses") + disableValidation := headers.Get("X-RPaaS-Disable-Validation") != "" if address == "" { return nil, ErrNoClusterProvided @@ -91,6 +93,8 @@ func (m *multiClusterFactory) Manager(ctx context.Context, headers http.Header) return nil, err } + clusterValidationDisabled := m.validationDisabled(clusterName) + k8sClient, err := sigsk8sclient.New(kubernetesRestConfig, sigsk8sclient.Options{Scheme: extensionsruntime.NewScheme()}) if err != nil { return nil, err @@ -101,6 +105,10 @@ func (m *multiClusterFactory) Manager(ctx context.Context, headers http.Header) return nil, err } + if !disableValidation && !clusterValidationDisabled { + manager = validation.New(manager, k8sClient) + } + m.managersMutex.Lock() defer m.managersMutex.Unlock() @@ -108,6 +116,17 @@ func (m *multiClusterFactory) Manager(ctx context.Context, headers http.Header) return manager, nil } +func (m *multiClusterFactory) validationDisabled(name string) bool { + + for _, cluster := range m.clusters { + if cluster.Name == name { + return cluster.DisableValidation + } + } + + return false +} + func (m *multiClusterFactory) getKubeConfig(name, address string) (*rest.Config, error) { selectedCluster := config.ClusterConfig{} diff --git a/scripts/localkube-integration.sh b/scripts/localkube-integration.sh index a76765c60..6a85d3b80 100755 --- a/scripts/localkube-integration.sh +++ b/scripts/localkube-integration.sh @@ -19,7 +19,7 @@ readonly NAMESPACE=${NAMESPACE:-rpaasv2-system} readonly INSTALL_CERT_MANAGER=${INSTALL_CERT_MANAGER:-} readonly CHART_VERSION_CERT_MANAGER=${CHART_VERSION_CERT_MANAGER:-1.11.2} -readonly CHART_VERSION_RPAAS_OPERATOR=${CHART_VERSION_RPAAS_OPERATOR:-0.11.7} +readonly CHART_VERSION_RPAAS_OPERATOR=${CHART_VERSION_RPAAS_OPERATOR:-0.13.0} readonly CHART_VERSION_RPAAS_API=${CHART_VERSION_RPAAS_API:-0.2.2} function onerror() {