From 3bdbdb171b00dcb5c1f038ab7d50d1bde320d500 Mon Sep 17 00:00:00 2001 From: Fabian Kramm Date: Wed, 3 Apr 2024 09:33:40 +0200 Subject: [PATCH] refactor: add concrete webhook types --- chart/values.schema.json | 236 +++++++++++++++++++++++++++++++++- config/config.go | 236 +++++++++++++++++++++++++++++++++- pkg/config/validation.go | 2 +- pkg/config/validation_test.go | 42 +++--- 4 files changed, 489 insertions(+), 27 deletions(-) diff --git a/chart/values.schema.json b/chart/values.schema.json index 4a54f16c3..c27fb9249 100755 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -19,12 +19,16 @@ "CentralAdmission": { "properties": { "validatingWebhooks": { - "items": true, + "items": { + "$ref": "#/$defs/ValidatingWebhookConfiguration" + }, "type": "array", "description": "ValidatingWebhooks are validating webhooks that should be enforced in the virtual cluster" }, "mutatingWebhooks": { - "items": true, + "items": { + "$ref": "#/$defs/MutatingWebhookConfiguration" + }, "type": "array", "description": "MutatingWebhooks are mutating webhooks that should be enforced in the virtual cluster" } @@ -1662,6 +1666,88 @@ "additionalProperties": false, "type": "object" }, + "MutatingWebhook": { + "properties": { + "reinvocationPolicy": { + "type": "string", + "description": "reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation.\nAllowed values are \"Never\" and \"IfNeeded\"." + }, + "name": { + "type": "string", + "description": "The name of the admission webhook.\nName should be fully qualified, e.g., imagepolicy.kubernetes.io, where\n\"imagepolicy\" is the name of the webhook, and kubernetes.io is the name\nof the organization." + }, + "clientConfig": { + "$ref": "#/$defs/ValidatingWebhookClientConfig", + "description": "ClientConfig defines how to communicate with the hook." + }, + "rules": { + "items": true, + "type": "array", + "description": "Rules describes what operations on what resources/subresources the webhook cares about.\nThe webhook cares about an operation if it matches _any_ Rule." + }, + "failurePolicy": { + "type": "string", + "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled -\nallowed values are Ignore or Fail. Defaults to Fail." + }, + "matchPolicy": { + "type": "string", + "description": "matchPolicy defines how the \"rules\" list is used to match incoming requests.\nAllowed values are \"Exact\" or \"Equivalent\"." + }, + "namespaceSelector": { + "description": "NamespaceSelector decides whether to run the webhook on an object based\non whether the namespace for that object matches the selector. If the\nobject itself is a namespace, the matching is performed on\nobject.metadata.labels. If the object is another cluster scoped resource,\nit never skips the webhook." + }, + "objectSelector": { + "description": "ObjectSelector decides whether to run the webhook based on if the\nobject has matching labels. objectSelector is evaluated against both\nthe oldObject and newObject that would be sent to the webhook, and\nis considered to match if either object matches the selector." + }, + "sideEffects": { + "type": "string", + "description": "SideEffects states whether this webhook has side effects." + }, + "timeoutSeconds": { + "type": "integer", + "description": "TimeoutSeconds specifies the timeout for this webhook." + }, + "admissionReviewVersions": { + "items": { + "type": "string" + }, + "type": "array", + "description": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`\nversions the Webhook expects." + }, + "matchConditions": { + "items": true, + "type": "array", + "description": "MatchConditions is a list of conditions that must be met for a request to be sent to this\nwebhook. Match conditions filter requests that have already been matched by the rules,\nnamespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.\nThere are a maximum of 64 match conditions allowed." + } + }, + "additionalProperties": false, + "type": "object" + }, + "MutatingWebhookConfiguration": { + "properties": { + "kind": { + "type": "string", + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to." + }, + "apiVersion": { + "type": "string", + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values." + }, + "metadata": { + "$ref": "#/$defs/ObjectMeta", + "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + }, + "webhooks": { + "items": { + "$ref": "#/$defs/MutatingWebhook" + }, + "type": "array", + "description": "Webhooks is a list of webhooks and the affected resources and operations." + } + }, + "additionalProperties": false, + "type": "object" + }, "NetworkPolicy": { "properties": { "enabled": { @@ -1745,6 +1831,30 @@ "additionalProperties": false, "type": "object" }, + "ObjectMeta": { + "properties": { + "name": { + "type": "string", + "description": "Name must be unique within a namespace. Is required when creating resources, although\nsome resources may allow a client to request the generation of an appropriate name\nautomatically. Name is primarily intended for creation idempotence and configuration\ndefinition." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Map of string keys and values that can be used to organize and categorize\n(scope and select) objects. May match selectors of replication controllers\nand services." + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations is an unstructured key value map stored with a resource that may be\nset by external tools to store and retrieve arbitrary metadata." + } + }, + "additionalProperties": false, + "type": "object" + }, "Observability": { "properties": { "metrics": { @@ -2203,7 +2313,7 @@ "properties": { "enabled": { "type": "boolean", - "description": "Enabled" + "description": "Enabled defines if the role should be enabled or disabled." }, "extraRules": { "items": { @@ -2676,6 +2786,126 @@ "additionalProperties": false, "type": "object" }, + "ValidatingWebhook": { + "properties": { + "name": { + "type": "string", + "description": "The name of the admission webhook.\nName should be fully qualified, e.g., imagepolicy.kubernetes.io, where\n\"imagepolicy\" is the name of the webhook, and kubernetes.io is the name\nof the organization." + }, + "clientConfig": { + "$ref": "#/$defs/ValidatingWebhookClientConfig", + "description": "ClientConfig defines how to communicate with the hook." + }, + "rules": { + "items": true, + "type": "array", + "description": "Rules describes what operations on what resources/subresources the webhook cares about.\nThe webhook cares about an operation if it matches _any_ Rule." + }, + "failurePolicy": { + "type": "string", + "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled -\nallowed values are Ignore or Fail. Defaults to Fail." + }, + "matchPolicy": { + "type": "string", + "description": "matchPolicy defines how the \"rules\" list is used to match incoming requests.\nAllowed values are \"Exact\" or \"Equivalent\"." + }, + "namespaceSelector": { + "description": "NamespaceSelector decides whether to run the webhook on an object based\non whether the namespace for that object matches the selector. If the\nobject itself is a namespace, the matching is performed on\nobject.metadata.labels. If the object is another cluster scoped resource,\nit never skips the webhook." + }, + "objectSelector": { + "description": "ObjectSelector decides whether to run the webhook based on if the\nobject has matching labels. objectSelector is evaluated against both\nthe oldObject and newObject that would be sent to the webhook, and\nis considered to match if either object matches the selector." + }, + "sideEffects": { + "type": "string", + "description": "SideEffects states whether this webhook has side effects." + }, + "timeoutSeconds": { + "type": "integer", + "description": "TimeoutSeconds specifies the timeout for this webhook." + }, + "admissionReviewVersions": { + "items": { + "type": "string" + }, + "type": "array", + "description": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`\nversions the Webhook expects." + }, + "matchConditions": { + "items": true, + "type": "array", + "description": "MatchConditions is a list of conditions that must be met for a request to be sent to this\nwebhook. Match conditions filter requests that have already been matched by the rules,\nnamespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.\nThere are a maximum of 64 match conditions allowed." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ValidatingWebhookClientConfig": { + "properties": { + "url": { + "type": "string", + "description": "URL gives the location of the webhook, in standard URL form\n(`scheme://host:port/path`). Exactly one of `url` or `service`\nmust be specified." + }, + "service": { + "$ref": "#/$defs/ValidatingWebhookServiceReference", + "description": "Service is a reference to the service for this webhook. Either\n`service` or `url` must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`." + }, + "caBundle": { + "type": "string", + "contentEncoding": "base64", + "description": "CABundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate.\nIf unspecified, system trust roots on the apiserver are used." + } + }, + "additionalProperties": false, + "type": "object", + "description": "ValidatingWebhookClientConfig contains the information to make a TLS connection with the webhook" + }, + "ValidatingWebhookConfiguration": { + "properties": { + "kind": { + "type": "string", + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to." + }, + "apiVersion": { + "type": "string", + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values." + }, + "metadata": { + "$ref": "#/$defs/ObjectMeta", + "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata." + }, + "webhooks": { + "items": { + "$ref": "#/$defs/ValidatingWebhook" + }, + "type": "array", + "description": "Webhooks is a list of webhooks and the affected resources and operations." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ValidatingWebhookServiceReference": { + "properties": { + "namespace": { + "type": "string", + "description": "Namespace is the namespace of the service." + }, + "name": { + "type": "string", + "description": "Name is the name of the service." + }, + "path": { + "type": "string", + "description": "Path is an optional URL path which will be sent in any request to\nthis service." + }, + "port": { + "type": "integer", + "description": "If specified, the port on the service that hosting webhook.\nDefault to 443 for backward compatibility.\n`port` should be a valid port number (1-65535, inclusive)." + } + }, + "additionalProperties": false, + "type": "object" + }, "VirtualClusterKubeConfig": { "properties": { "kubeConfig": { diff --git a/config/config.go b/config/config.go index 44ed541a9..1f43c29bf 100644 --- a/config/config.go +++ b/config/config.go @@ -595,6 +595,7 @@ type DistroContainerEnabled struct { type Image struct { // Repository is the registry and repository of the container image, e.g. my-registry.com/my-repo/my-image Repository string `json:"repository,omitempty"` + // Tag is the tag of the container image, e.g. latest Tag string `json:"tag,omitempty"` } @@ -679,8 +680,10 @@ type EtcdDeploy struct { // StatefulSet holds options for the external etcd statefulSet. StatefulSet EtcdDeployStatefulSet `json:"statefulSet,omitempty"` + // Service holds options for the external etcd service. Service EtcdDeployService `json:"service,omitempty"` + // HeadlessService holds options for the external etcd headless service. HeadlessService EtcdDeployHeadlessService `json:"headlessService,omitempty"` } @@ -688,6 +691,7 @@ type EtcdDeploy struct { type EtcdDeployService struct { // Enabled defines if the etcd service should be deployed Enabled bool `json:"enabled,omitempty"` + // Annotations are extra annotations for the external etcd service Annotations map[string]string `json:"annotations,omitempty"` } @@ -695,6 +699,7 @@ type EtcdDeployService struct { type EtcdDeployHeadlessService struct { // Enabled defines if the etcd headless service should be deployed Enabled bool `json:"enabled,omitempty"` + // Annotations are extra annotations for the external etcd headless service Annotations map[string]string `json:"annotations,omitempty"` } @@ -702,25 +707,34 @@ type EtcdDeployHeadlessService struct { type EtcdDeployStatefulSet struct { // Enabled defines if the statefulSet should be deployed Enabled bool `json:"enabled,omitempty"` + // Image is the image to use for the external etcd statefulSet Image Image `json:"image,omitempty"` + // ImagePullPolicy is the pull policy for the external etcd image ImagePullPolicy string `json:"imagePullPolicy,omitempty"` + // Env are extra environment variables Env []map[string]interface{} `json:"env,omitempty"` + // ExtraArgs are appended to the etcd command. ExtraArgs []string `json:"extraArgs,omitempty"` // Resources the etcd can consume Resources Resources `json:"resources,omitempty"` + // Pods defines extra metadata for the etcd pods. Pods LabelsAndAnnotations `json:"pods,omitempty"` + // HighAvailability are high availability options HighAvailability ExternalEtcdHighAvailability `json:"highAvailability,omitempty"` + // Scheduling options for the etcd pods. Scheduling ControlPlaneScheduling `json:"scheduling,omitempty"` + // Security options for the etcd pods. Security ControlPlaneSecurity `json:"security,omitempty"` + // Persistence options for the etcd pods. Persistence ExternalEtcdPersistence `json:"persistence,omitempty"` @@ -730,6 +744,7 @@ type EtcdDeployStatefulSet struct { type Resources struct { // Limits are resource limits for the container Limits map[string]interface{} `json:"limits,omitempty"` + // Requests are minimal resources that will be consumed by the container Requests map[string]interface{} `json:"requests,omitempty"` } @@ -742,6 +757,7 @@ type ExternalEtcdHighAvailability struct { type HostPathMapper struct { // Enabled specifies if the host path mapper will be used Enabled bool `json:"enabled,omitempty"` + // Central specifies if the central host path mapper will be used Central bool `json:"central,omitempty" product:"pro"` } @@ -749,15 +765,19 @@ type HostPathMapper struct { type CoreDNS struct { // Enabled defines if coredns is enabled Enabled bool `json:"enabled,omitempty"` + // Embedded defines if vCluster will start the embedded coredns service Embedded bool `json:"embedded,omitempty" product:"pro"` + // Service holds extra options for the coredns service deployed within the virtual cluster Service CoreDNSService `json:"service,omitempty"` + // Deployment holds extra options for the coredns deployment deployed within the virtual cluster Deployment CoreDNSDeployment `json:"deployment,omitempty"` // OverwriteConfig can be used to overwrite the coredns config OverwriteConfig string `json:"overwriteConfig,omitempty"` + // OverwriteManifests can be used to overwrite the coredns manifests used to deploy coredns OverwriteManifests string `json:"overwriteManifests,omitempty"` } @@ -772,12 +792,16 @@ type CoreDNSService struct { type CoreDNSDeployment struct { // Image is the coredns image to use Image string `json:"image,omitempty"` + // Replicas is the amount of coredns pods to run. Replicas int `json:"replicas,omitempty"` + // NodeSelector is the node selector to use for coredns. NodeSelector map[string]string `json:"nodeSelector,omitempty"` + // Resources are the desired resources for coredns. Resources Resources `json:"resources,omitempty"` + // Pods is additional metadata for the coredns pods. Pods LabelsAndAnnotations `json:"pods,omitempty"` @@ -787,8 +811,10 @@ type CoreDNSDeployment struct { type ControlPlaneProxy struct { // BindAddress under which vCluster will expose the proxy. BindAddress string `json:"bindAddress,omitempty"` + // Port under which vCluster will expose the proxy. Port int `json:"port,omitempty"` + // ExtraSANs are extra hostnames to sign the vCluster proxy certificate for. ExtraSANs []string `json:"extraSANs,omitempty"` } @@ -799,8 +825,10 @@ type ControlPlaneService struct { // Spec allows you to configure extra service options. Spec map[string]interface{} `json:"spec,omitempty"` + // KubeletNodePort is the node port where the fake kubelet is exposed. Defaults to 0. KubeletNodePort int `json:"kubeletNodePort,omitempty"` + // HTTPSNodePort is the node port where https is exposed. Defaults to 0. HTTPSNodePort int `json:"httpsNodePort,omitempty"` @@ -813,8 +841,10 @@ type ControlPlaneIngress struct { // Host is the host where vCluster will be reachable Host string `json:"host,omitempty"` + // PathType is the path type of the ingress PathType string `json:"pathType,omitempty"` + // Spec allows you to configure extra ingress options. Spec map[string]interface{} `json:"spec,omitempty"` @@ -859,6 +889,7 @@ type ControlPlaneAdvanced struct { type ControlPlaneHeadlessService struct { // Annotations are extra annotations for this resource. Annotations map[string]string `json:"annotations,omitempty"` + // Labels are extra labels for this resource. Labels map[string]string `json:"labels,omitempty"` } @@ -866,10 +897,13 @@ type ControlPlaneHeadlessService struct { type ExternalEtcdPersistence struct { // VolumeClaim can be used to configure the persistent volume claim. VolumeClaim ExternalEtcdPersistenceVolumeClaim `json:"volumeClaim,omitempty"` + // VolumeClaimTemplates defines the volumeClaimTemplates for the statefulSet VolumeClaimTemplates []map[string]interface{} `json:"volumeClaimTemplates,omitempty"` + // AddVolumes defines extra volumes for the pod AddVolumes []map[string]interface{} `json:"addVolumes,omitempty"` + // AddVolumeMounts defines extra volume mounts for the container AddVolumeMounts []VolumeMount `json:"addVolumeMounts,omitempty"` } @@ -877,12 +911,16 @@ type ExternalEtcdPersistence struct { type ExternalEtcdPersistenceVolumeClaim struct { // Enabled enables deploying a persistent volume claim. Enabled bool `json:"enabled,omitempty"` + // AccessModes are the persistent volume claim access modes. AccessModes []string `json:"accessModes,omitempty"` + // RetentionPolicy is the persistent volume claim retention policy. RetentionPolicy string `json:"retentionPolicy,omitempty"` + // Size is the persistent volume claim storage size. Size string `json:"size,omitempty"` + // StorageClass is the persistent volume claim storage class. StorageClass string `json:"storageClass,omitempty"` } @@ -890,10 +928,13 @@ type ExternalEtcdPersistenceVolumeClaim struct { type ControlPlanePersistence struct { // VolumeClaim can be used to configure the persistent volume claim. VolumeClaim VolumeClaim `json:"volumeClaim,omitempty"` + // VolumeClaimTemplates defines the volumeClaimTemplates for the statefulSet VolumeClaimTemplates []map[string]interface{} `json:"volumeClaimTemplates,omitempty"` + // AddVolumes defines extra volumes for the pod AddVolumes []map[string]interface{} `json:"addVolumes,omitempty"` + // AddVolumeMounts defines extra volume mounts for the container AddVolumeMounts []VolumeMount `json:"addVolumeMounts,omitempty"` } @@ -902,12 +943,16 @@ type VolumeClaim struct { // Enabled enables deploying a persistent volume claim. If auto, vCluster will automatically determine // based on the chosen distro and other options if this is required. Enabled StrBool `json:"enabled,omitempty" jsonschema:"oneof_type=string;boolean"` + // AccessModes are the persistent volume claim access modes. AccessModes []string `json:"accessModes,omitempty"` + // RetentionPolicy is the persistent volume claim retention policy. RetentionPolicy string `json:"retentionPolicy,omitempty"` + // Size is the persistent volume claim storage size. Size string `json:"size,omitempty"` + // StorageClass is the persistent volume claim storage class. StorageClass string `json:"storageClass,omitempty"` } @@ -916,20 +961,25 @@ type VolumeClaim struct { type VolumeMount struct { // This must match the Name of a Volume. Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + // Mounted read-only if true, read-write otherwise (false or unspecified). // Defaults to false. ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,2,opt,name=readOnly"` + // Path within the container at which the volume should be mounted. Must // not contain ':'. MountPath string `json:"mountPath" protobuf:"bytes,3,opt,name=mountPath"` + // Path within the volume from which the container's volume should be mounted. // Defaults to "" (volume's root). SubPath string `json:"subPath,omitempty" protobuf:"bytes,4,opt,name=subPath"` + // 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. MountPropagation *string `json:"mountPropagation,omitempty" protobuf:"bytes,5,opt,name=mountPropagation,casttype=MountPropagationMode"` + // 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). @@ -940,14 +990,19 @@ type VolumeMount struct { type ControlPlaneScheduling struct { // NodeSelector is the node selector to apply to the pod. NodeSelector map[string]interface{} `json:"nodeSelector,omitempty"` + // Affinity is the affinity to apply to the pod. Affinity map[string]interface{} `json:"affinity,omitempty"` + // Tolerations are the tolerations to apply to the pod. Tolerations []interface{} `json:"tolerations,omitempty"` + // PriorityClassName is the priority class name for the the pod. PriorityClassName string `json:"priorityClassName,omitempty"` + // PodManagementPolicy is the statefulSet pod management policy. PodManagementPolicy string `json:"podManagementPolicy,omitempty"` + // TopologySpreadConstraints are the topology spread constraints for the pod. TopologySpreadConstraints []interface{} `json:"topologySpreadConstraints,omitempty"` } @@ -955,12 +1010,16 @@ type ControlPlaneScheduling struct { type ControlPlaneServiceAccount struct { // Enabled specifies if the service account should get deployed. Enabled bool `json:"enabled,omitempty"` + // Name specifies what name to use for the service account. Name string `json:"name,omitempty"` + // ImagePullSecrets defines extra image pull secrets for the service account. ImagePullSecrets []LocalObjectReference `json:"imagePullSecrets,omitempty"` + // Annotations are extra annotations for this resource. Annotations map[string]string `json:"annotations,omitempty"` + // Labels are extra labels for this resource. Labels map[string]string `json:"labels,omitempty"` } @@ -968,12 +1027,16 @@ type ControlPlaneServiceAccount struct { type ControlPlaneWorkloadServiceAccount struct { // Enabled specifies if the service account for the workloads should get deployed. Enabled bool `json:"enabled,omitempty"` + // Name specifies what name to use for the service account for the virtual cluster workloads. Name string `json:"name,omitempty"` + // ImagePullSecrets defines extra image pull secrets for the workload service account. ImagePullSecrets []LocalObjectReference `json:"imagePullSecrets,omitempty"` + // Annotations are extra annotations for this resource. Annotations map[string]string `json:"annotations,omitempty"` + // Labels are extra labels for this resource. Labels map[string]string `json:"labels,omitempty"` } @@ -981,8 +1044,10 @@ type ControlPlaneWorkloadServiceAccount struct { type ControlPlaneProbes struct { // LivenessProbe specifies if the liveness probe for the container should be enabled LivenessProbe EnableSwitch `json:"livenessProbe,omitempty"` + // ReadinessProbe specifies if the readiness probe for the container should be enabled ReadinessProbe EnableSwitch `json:"readinessProbe,omitempty"` + // StartupProbe specifies if the startup probe for the container should be enabled StartupProbe EnableSwitch `json:"startupProbe,omitempty"` } @@ -990,6 +1055,7 @@ type ControlPlaneProbes struct { type ControlPlaneSecurity struct { // PodSecurityContext specifies security context options on the pod level. PodSecurityContext map[string]interface{} `json:"podSecurityContext,omitempty"` + // ContainerSecurityContext specifies security context options on the container level. ContainerSecurityContext map[string]interface{} `json:"containerSecurityContext,omitempty"` } @@ -1002,6 +1068,7 @@ type ControlPlaneGlobalMetadata struct { type LabelsAndAnnotations struct { // Annotations are extra annotations for this resource. Annotations map[string]string `json:"annotations,omitempty"` + // Labels are extra labels for this resource. Labels map[string]string `json:"labels,omitempty"` } @@ -1009,12 +1076,16 @@ type LabelsAndAnnotations struct { type Policies struct { // NetworkPolicy specifies network policy options. NetworkPolicy NetworkPolicy `json:"networkPolicy,omitempty"` + // PodSecurityStandard that can be enforced can be one of: empty (""), baseline, restricted or privileged PodSecurityStandard string `json:"podSecurityStandard,omitempty"` + // ResourceQuota specifies resource quota options. ResourceQuota ResourceQuota `json:"resourceQuota,omitempty"` + // LimitRange specifies limit range options. LimitRange LimitRange `json:"limitRange,omitempty"` + // CentralAdmission defines what validating or mutating webhooks should be enforced within the virtual cluster. CentralAdmission CentralAdmission `json:"centralAdmission,omitempty" product:"pro"` } @@ -1022,10 +1093,13 @@ type Policies struct { type ResourceQuota struct { // Enabled defines if the resource quota should be enabled. Enabled bool `json:"enabled,omitempty"` + // Quota are the quota options Quota map[string]interface{} `json:"quota,omitempty"` + // ScopeSelector is the resource quota scope selector ScopeSelector ScopeSelector `json:"scopeSelector,omitempty"` + // Scopes are the resource quota scopes Scopes []string `json:"scopes,omitempty"` @@ -1039,9 +1113,11 @@ type ScopeSelector struct { type LabelSelectorRequirement struct { // key is the label key that the selector applies to. Key string `json:"key"` + // operator represents a key's relationship to a set of values. // Valid operators are In, NotIn, Exists and DoesNotExist. Operator string `json:"operator"` + // 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 @@ -1055,6 +1131,7 @@ type LimitRange struct { // Default are the default limits for the limit range Default map[string]interface{} `json:"default,omitempty"` + // DefaultRequest are the default request options for the limit range DefaultRequest map[string]interface{} `json:"defaultRequest,omitempty"` @@ -1092,14 +1169,163 @@ type IPBlock struct { type CentralAdmission struct { // ValidatingWebhooks are validating webhooks that should be enforced in the virtual cluster - ValidatingWebhooks []interface{} `json:"validatingWebhooks,omitempty"` + ValidatingWebhooks []ValidatingWebhookConfiguration `json:"validatingWebhooks,omitempty"` + // MutatingWebhooks are mutating webhooks that should be enforced in the virtual cluster - MutatingWebhooks []interface{} `json:"mutatingWebhooks,omitempty"` + MutatingWebhooks []MutatingWebhookConfiguration `json:"mutatingWebhooks,omitempty"` +} + +type MutatingWebhookConfiguration struct { + // Kind is a string value representing the REST resource this object represents. + // Servers may infer this from the endpoint the client submits requests to. + Kind string `json:"kind,omitempty"` + + // 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. + APIVersion string `json:"apiVersion,omitempty"` + + // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + Metadata ObjectMeta `json:"metadata,omitempty"` + + // Webhooks is a list of webhooks and the affected resources and operations. + Webhooks []MutatingWebhook `json:"webhooks,omitempty"` +} + +type MutatingWebhook struct { + // reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation. + // Allowed values are "Never" and "IfNeeded". + ReinvocationPolicy *string `json:"reinvocationPolicy,omitempty" protobuf:"bytes,10,opt,name=reinvocationPolicy,casttype=ReinvocationPolicyType"` + + ValidatingWebhook `json:",inline"` +} + +type ValidatingWebhookConfiguration struct { + // Kind is a string value representing the REST resource this object represents. + // Servers may infer this from the endpoint the client submits requests to. + Kind string `json:"kind,omitempty"` + + // 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. + APIVersion string `json:"apiVersion,omitempty"` + + // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. + Metadata ObjectMeta `json:"metadata,omitempty"` + + // Webhooks is a list of webhooks and the affected resources and operations. + Webhooks []ValidatingWebhook `json:"webhooks,omitempty"` +} + +type ValidatingWebhook struct { + // The name of the admission webhook. + // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where + // "imagepolicy" is the name of the webhook, and kubernetes.io is the name + // of the organization. + Name string `json:"name"` + + // ClientConfig defines how to communicate with the hook. + ClientConfig ValidatingWebhookClientConfig `json:"clientConfig"` + + // Rules describes what operations on what resources/subresources the webhook cares about. + // The webhook cares about an operation if it matches _any_ Rule. + Rules []interface{} `json:"rules,omitempty"` + + // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // allowed values are Ignore or Fail. Defaults to Fail. + FailurePolicy *string `json:"failurePolicy,omitempty"` + + // matchPolicy defines how the "rules" list is used to match incoming requests. + // Allowed values are "Exact" or "Equivalent". + MatchPolicy *string `json:"matchPolicy,omitempty"` + + // NamespaceSelector decides whether to run the webhook on an object based + // on whether the namespace for that object matches the selector. If the + // object itself is a namespace, the matching is performed on + // object.metadata.labels. If the object is another cluster scoped resource, + // it never skips the webhook. + NamespaceSelector interface{} `json:"namespaceSelector,omitempty"` + + // ObjectSelector decides whether to run the webhook based on if the + // object has matching labels. objectSelector is evaluated against both + // the oldObject and newObject that would be sent to the webhook, and + // is considered to match if either object matches the selector. + ObjectSelector interface{} `json:"objectSelector,omitempty"` + + // SideEffects states whether this webhook has side effects. + SideEffects *string `json:"sideEffects"` + + // TimeoutSeconds specifies the timeout for this webhook. + TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"` + + // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` + // versions the Webhook expects. + AdmissionReviewVersions []string `json:"admissionReviewVersions"` + + // MatchConditions is a list of conditions that must be met for a request to be sent to this + // webhook. Match conditions filter requests that have already been matched by the rules, + // namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. + // There are a maximum of 64 match conditions allowed. + MatchConditions []interface{} `json:"matchConditions,omitempty"` +} + +// ValidatingWebhookClientConfig contains the information to make a TLS +// connection with the webhook +type ValidatingWebhookClientConfig struct { + // URL gives the location of the webhook, in standard URL form + // (`scheme://host:port/path`). Exactly one of `url` or `service` + // must be specified. + URL *string `json:"url,omitempty"` + + // Service is a reference to the service for this webhook. Either + // `service` or `url` must be specified. + // + // If the webhook is running within the cluster, then you should use `service`. + Service *ValidatingWebhookServiceReference `json:"service,omitempty"` + + // CABundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + // If unspecified, system trust roots on the apiserver are used. + CABundle []byte `json:"caBundle,omitempty"` +} + +type ValidatingWebhookServiceReference struct { + // Namespace is the namespace of the service. + Namespace string `json:"namespace"` + + // Name is the name of the service. + Name string `json:"name"` + + // Path is an optional URL path which will be sent in any request to + // this service. + Path *string `json:"path,omitempty"` + + // If specified, the port on the service that hosting webhook. + // Default to 443 for backward compatibility. + // `port` should be a valid port number (1-65535, inclusive). + Port *int32 `json:"port,omitempty"` +} + +type ObjectMeta struct { + // Name must be unique within a namespace. Is required when creating resources, although + // some resources may allow a client to request the generation of an appropriate name + // automatically. Name is primarily intended for creation idempotence and configuration + // definition. + Name string `json:"name,omitempty"` + + // Map of string keys and values that can be used to organize and categorize + // (scope and select) objects. May match selectors of replication controllers + // and services. + Labels map[string]string `json:"labels,omitempty"` + + // Annotations is an unstructured key value map stored with a resource that may be + // set by external tools to store and retrieve arbitrary metadata. + Annotations map[string]string `json:"annotations,omitempty"` } type RBAC struct { // Role holds virtual cluster role configuration Role RBACRole `json:"role,omitempty"` + // ClusterRole holds virtual cluster cluster role configuration ClusterRole RBACClusterRole `json:"clusterRole,omitempty"` } @@ -1107,17 +1333,21 @@ type RBAC struct { type RBACClusterRole struct { // Enabled defines if the cluster role should be enabled or disabled. If auto, vCluster automatically determines whether the virtual cluster requires a cluster role. Enabled StrBool `json:"enabled,omitempty" jsonschema:"oneof_type=string;boolean"` + // ExtraRules will add rules to the cluster role. ExtraRules []map[string]interface{} `json:"extraRules,omitempty"` + // OverwriteRules will overwrite the cluster role rules completely. OverwriteRules []map[string]interface{} `json:"overwriteRules,omitempty"` } type RBACRole struct { - // Enabled + // Enabled defines if the role should be enabled or disabled. Enabled bool `json:"enabled,omitempty"` + // ExtraRules will add rules to the role. ExtraRules []map[string]interface{} `json:"extraRules,omitempty"` + // OverwriteRules will overwrite the role rules completely. OverwriteRules []map[string]interface{} `json:"overwriteRules,omitempty"` } diff --git a/pkg/config/validation.go b/pkg/config/validation.go index 225caf549..7dadd9bd9 100644 --- a/pkg/config/validation.go +++ b/pkg/config/validation.go @@ -288,7 +288,7 @@ func validateCentralAdmissionControl(config *VirtualClusterConfig) error { return err } -func ParseExtraHooks(valHooks, mutHooks []interface{}) ([]admissionregistrationv1.ValidatingWebhookConfiguration, []admissionregistrationv1.MutatingWebhookConfiguration, error) { +func ParseExtraHooks(valHooks []config.ValidatingWebhookConfiguration, mutHooks []config.MutatingWebhookConfiguration) ([]admissionregistrationv1.ValidatingWebhookConfiguration, []admissionregistrationv1.MutatingWebhookConfiguration, error) { decodedVal := make([]string, 0, len(valHooks)) for _, v := range valHooks { bytes, err := yaml.Marshal(v) diff --git a/pkg/config/validation_test.go b/pkg/config/validation_test.go index 9797ed758..7178eae00 100644 --- a/pkg/config/validation_test.go +++ b/pkg/config/validation_test.go @@ -3,7 +3,7 @@ package config import ( "testing" - admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "github.com/loft-sh/vcluster/config" ) func Test(t *testing.T) { @@ -50,44 +50,44 @@ HYMfRsCbvUOZ58SWLs5fyQ== testCases := []struct { name string wantErr string - valHook []interface{} - mutHook []interface{} + valHook []config.ValidatingWebhookConfiguration + mutHook []config.MutatingWebhookConfiguration }{ { name: "valid valhook", - valHook: []interface{}{valHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test", Name: "service"}})}, + valHook: []config.ValidatingWebhookConfiguration{valHook(config.ValidatingWebhookClientConfig{Service: &config.ValidatingWebhookServiceReference{Namespace: "test", Name: "service"}})}, }, { name: "valid muthook", - mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test", Name: "service"}})}, + mutHook: []config.MutatingWebhookConfiguration{mutHook(config.ValidatingWebhookClientConfig{Service: &config.ValidatingWebhookServiceReference{Namespace: "test", Name: "service"}})}, }, { name: "invalid valhook", - valHook: []interface{}{valHookToBase64(admissionregistrationv1.WebhookClientConfig{})}, + valHook: []config.ValidatingWebhookConfiguration{valHook(config.ValidatingWebhookClientConfig{})}, wantErr: "webhook client config was not valid for ValidatingWebhookConfiguration test: there is no service config", }, { name: "invalid muthook", - mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{})}, + mutHook: []config.MutatingWebhookConfiguration{mutHook(config.ValidatingWebhookClientConfig{})}, wantErr: "webhook client config was not valid for MutatingWebhookConfiguration test: there is no service config", }, { name: "invalid service", - mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test"}})}, + mutHook: []config.MutatingWebhookConfiguration{mutHook(config.ValidatingWebhookClientConfig{Service: &config.ValidatingWebhookServiceReference{Namespace: "test"}})}, wantErr: "webhook client config was not valid for MutatingWebhookConfiguration test: namespace or name of the service is missing", }, { name: "valid url", - mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{URL: &validURL})}, + mutHook: []config.MutatingWebhookConfiguration{mutHook(config.ValidatingWebhookClientConfig{URL: &validURL})}, }, { name: "invalid bundle", - mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test"}, CABundle: []byte("HAZAA")})}, + mutHook: []config.MutatingWebhookConfiguration{mutHook(config.ValidatingWebhookClientConfig{Service: &config.ValidatingWebhookServiceReference{Namespace: "test"}, CABundle: []byte("HAZAA")})}, wantErr: "webhook client config was not valid for MutatingWebhookConfiguration test: could not parse the CABundle", }, { name: "valid bundle", - mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test", Name: "test"}, CABundle: []byte(validCABUNDLE)})}, + mutHook: []config.MutatingWebhookConfiguration{mutHook(config.ValidatingWebhookClientConfig{Service: &config.ValidatingWebhookServiceReference{Namespace: "test", Name: "test"}, CABundle: []byte(validCABUNDLE)})}, }, } for _, tt := range testCases { @@ -102,24 +102,26 @@ HYMfRsCbvUOZ58SWLs5fyQ== } } -func valHookToBase64(clientCfg admissionregistrationv1.WebhookClientConfig) interface{} { - hook := &admissionregistrationv1.ValidatingWebhookConfiguration{} +func valHook(clientCfg config.ValidatingWebhookClientConfig) config.ValidatingWebhookConfiguration { + hook := config.ValidatingWebhookConfiguration{} hook.APIVersion = "v1" hook.Kind = "ValidatingWebhookConfiguration" - hook.Name = "test" - hook.Webhooks = []admissionregistrationv1.ValidatingWebhook{ + hook.Metadata.Name = "test" + hook.Webhooks = []config.ValidatingWebhook{ {Name: "test", ClientConfig: clientCfg}, } return hook } -func mutHookToBase64(clientCfg admissionregistrationv1.WebhookClientConfig) interface{} { - hook := &admissionregistrationv1.MutatingWebhookConfiguration{} +func mutHook(clientCfg config.ValidatingWebhookClientConfig) config.MutatingWebhookConfiguration { + hook := config.MutatingWebhookConfiguration{} hook.APIVersion = "v1" hook.Kind = "MutatingWebhookConfiguration" - hook.Name = "test" - hook.Webhooks = []admissionregistrationv1.MutatingWebhook{ - {Name: "test", ClientConfig: clientCfg}, + hook.Metadata.Name = "test" + hook.Webhooks = []config.MutatingWebhook{ + { + ValidatingWebhook: config.ValidatingWebhook{Name: "test", ClientConfig: clientCfg}, + }, } return hook }