mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
fix: policy match Kind case-senstive (#6008)
Signed-off-by: Vyom-Yadav <jackhammervyom@gmail.com> Signed-off-by: Vyom-Yadav <jackhammervyom@gmail.com>
This commit is contained in:
parent
e75c745191
commit
c2dfd1d130
13 changed files with 795 additions and 14 deletions
|
@ -881,16 +881,16 @@ func TestMatchesResourceDescription(t *testing.T) {
|
||||||
},
|
},
|
||||||
Resource: []byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "myapp-pod2", "labels": { "app": "myapp2" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx" } ] } }`),
|
Resource: []byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "myapp-pod2", "labels": { "app": "myapp2" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx" } ] } }`),
|
||||||
Policy: []byte(`{ "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "disallow-latest-tag", "annotations": { "policies.kyverno.io/category": "Workload Isolation", "policies.kyverno.io/description": "The ':latest' tag is mutable and can lead to unexpected errors if the image changes. A best practice is to use an immutable tag that maps to a specific version of an application pod." } }, "spec": { "validationFailureAction": "enforce", "rules": [ { "name": "require-image-tag", "match": { "resources": { "kinds": [ "pod" ] } }, "validate": { "message": "An image tag is required", "pattern": { "spec": { "containers": [ { "image": "*:*" } ] } } } } ] } }`),
|
Policy: []byte(`{ "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "disallow-latest-tag", "annotations": { "policies.kyverno.io/category": "Workload Isolation", "policies.kyverno.io/description": "The ':latest' tag is mutable and can lead to unexpected errors if the image changes. A best practice is to use an immutable tag that maps to a specific version of an application pod." } }, "spec": { "validationFailureAction": "enforce", "rules": [ { "name": "require-image-tag", "match": { "resources": { "kinds": [ "pod" ] } }, "validate": { "message": "An image tag is required", "pattern": { "spec": { "containers": [ { "image": "*:*" } ] } } } } ] } }`),
|
||||||
areErrorsExpected: false,
|
areErrorsExpected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Description: "Test should pass for GVK case sensitive",
|
Description: "Test should fail for GVK case sensitive",
|
||||||
AdmissionInfo: v1beta1.RequestInfo{
|
AdmissionInfo: v1beta1.RequestInfo{
|
||||||
ClusterRoles: []string{"admin"},
|
ClusterRoles: []string{"admin"},
|
||||||
},
|
},
|
||||||
Resource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "name": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}}`),
|
Resource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "name": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}}`),
|
||||||
Policy: []byte(`{ "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "policy-qos" }, "spec": { "validationFailureAction": "enforce", "rules": [ { "name": "add-memory-limit", "match": { "resources": { "kinds": [ "apps/v1/deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "mutate": { "overlay": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "+(memory)": "300Mi", "+(cpu)": "100" } } } ] } } } } } }, { "name": "check-cpu-memory-limits", "match": { "resources": { "kinds": [ "apps/v1/Deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "validate": { "message": "Resource limits are required for CPU and memory", "pattern": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "memory": "?*", "cpu": "?*" } } } ] } } } } } } ] } }`),
|
Policy: []byte(`{ "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "policy-qos" }, "spec": { "validationFailureAction": "enforce", "rules": [ { "name": "add-memory-limit", "match": { "resources": { "kinds": [ "apps/v1/deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "mutate": { "overlay": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "+(memory)": "300Mi", "+(cpu)": "100" } } } ] } } } } } } ] } }`),
|
||||||
areErrorsExpected: false,
|
areErrorsExpected: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1786,16 +1786,16 @@ func TestMatchesResourceDescription_GenerateName(t *testing.T) {
|
||||||
},
|
},
|
||||||
Resource: []byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "generateName": "myapp-pod2", "labels": { "app": "myapp2" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx" } ] } }`),
|
Resource: []byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "generateName": "myapp-pod2", "labels": { "app": "myapp2" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx" } ] } }`),
|
||||||
Policy: []byte(`{ "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "disallow-latest-tag", "annotations": { "policies.kyverno.io/category": "Workload Isolation", "policies.kyverno.io/description": "The ':latest' tag is mutable and can lead to unexpected errors if the image changes. A best practice is to use an immutable tag that maps to a specific version of an application pod." } }, "spec": { "validationFailureAction": "enforce", "rules": [ { "name": "require-image-tag", "match": { "resources": { "kinds": [ "pod" ] } }, "validate": { "message": "An image tag is required", "pattern": { "spec": { "containers": [ { "image": "*:*" } ] } } } } ] } }`),
|
Policy: []byte(`{ "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "disallow-latest-tag", "annotations": { "policies.kyverno.io/category": "Workload Isolation", "policies.kyverno.io/description": "The ':latest' tag is mutable and can lead to unexpected errors if the image changes. A best practice is to use an immutable tag that maps to a specific version of an application pod." } }, "spec": { "validationFailureAction": "enforce", "rules": [ { "name": "require-image-tag", "match": { "resources": { "kinds": [ "pod" ] } }, "validate": { "message": "An image tag is required", "pattern": { "spec": { "containers": [ { "image": "*:*" } ] } } } } ] } }`),
|
||||||
areErrorsExpected: false,
|
areErrorsExpected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Description: "Test should pass for GVK case sensitive",
|
Description: "Test should fail for GVK case sensitive",
|
||||||
AdmissionInfo: v1beta1.RequestInfo{
|
AdmissionInfo: v1beta1.RequestInfo{
|
||||||
ClusterRoles: []string{"admin"},
|
ClusterRoles: []string{"admin"},
|
||||||
},
|
},
|
||||||
Resource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "generateName": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}}`),
|
Resource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "generateName": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}}`),
|
||||||
Policy: []byte(`{ "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "policy-qos" }, "spec": { "validationFailureAction": "enforce", "rules": [ { "name": "add-memory-limit", "match": { "resources": { "kinds": [ "apps/v1/deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "mutate": { "overlay": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "+(memory)": "300Mi", "+(cpu)": "100" } } } ] } } } } } }, { "name": "check-cpu-memory-limits", "match": { "resources": { "kinds": [ "apps/v1/Deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "validate": { "message": "Resource limits are required for CPU and memory", "pattern": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "memory": "?*", "cpu": "?*" } } } ] } } } } } } ] } }`),
|
Policy: []byte(`{ "apiVersion": "kyverno.io/v1", "kind": "ClusterPolicy", "metadata": { "name": "policy-qos" }, "spec": { "validationFailureAction": "enforce", "rules": [ { "name": "add-memory-limit", "match": { "resources": { "kinds": [ "apps/v1/deployment" ], "selector": { "matchLabels": { "test": "qos" } } } }, "mutate": { "overlay": { "spec": { "template": { "spec": { "containers": [ { "(name)": "*", "resources": { "limits": { "+(memory)": "300Mi", "+(cpu)": "100" } } } ] } } } } } } ] } }`),
|
||||||
areErrorsExpected: false,
|
areErrorsExpected: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
@ -14,7 +12,6 @@ import (
|
||||||
// present in the subresourceGVKToAPIResource map. Set allowEphemeralContainers to true to allow ephemeral containers to be matched even when the
|
// present in the subresourceGVKToAPIResource map. Set allowEphemeralContainers to true to allow ephemeral containers to be matched even when the
|
||||||
// policy does not explicitly match on ephemeral containers and only matches on pods.
|
// policy does not explicitly match on ephemeral containers and only matches on pods.
|
||||||
func CheckKind(subresourceGVKToAPIResource map[string]*metav1.APIResource, kinds []string, gvk schema.GroupVersionKind, subresourceInAdmnReview string, allowEphemeralContainers bool) bool {
|
func CheckKind(subresourceGVKToAPIResource map[string]*metav1.APIResource, kinds []string, gvk schema.GroupVersionKind, subresourceInAdmnReview string, allowEphemeralContainers bool) bool {
|
||||||
title := cases.Title(language.Und, cases.NoLower)
|
|
||||||
result := false
|
result := false
|
||||||
for _, k := range kinds {
|
for _, k := range kinds {
|
||||||
if k != "*" {
|
if k != "*" {
|
||||||
|
@ -23,7 +20,7 @@ func CheckKind(subresourceGVKToAPIResource map[string]*metav1.APIResource, kinds
|
||||||
if ok {
|
if ok {
|
||||||
result = apiResource.Group == gvk.Group && (apiResource.Version == gvk.Version || strings.Contains(gv, "*")) && apiResource.Kind == gvk.Kind
|
result = apiResource.Group == gvk.Group && (apiResource.Version == gvk.Version || strings.Contains(gv, "*")) && apiResource.Kind == gvk.Kind
|
||||||
} else { // if the kind is not found in the subresourceGVKToAPIResource, then it is not a subresource
|
} else { // if the kind is not found in the subresourceGVKToAPIResource, then it is not a subresource
|
||||||
result = title.String(kind) == gvk.Kind &&
|
result = kind == gvk.Kind &&
|
||||||
(subresourceInAdmnReview == "" ||
|
(subresourceInAdmnReview == "" ||
|
||||||
(allowEphemeralContainers && subresourceInAdmnReview == "ephemeralcontainers"))
|
(allowEphemeralContainers && subresourceInAdmnReview == "ephemeralcontainers"))
|
||||||
if gv != "" {
|
if gv != "" {
|
||||||
|
|
|
@ -37,6 +37,12 @@ func Test_CheckKind(t *testing.T) {
|
||||||
match = CheckKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "ephemeralcontainers", false)
|
match = CheckKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "ephemeralcontainers", false)
|
||||||
assert.Equal(t, match, false)
|
assert.Equal(t, match, false)
|
||||||
|
|
||||||
|
match = CheckKind(subresourceGVKToAPIResource, []string{"postgresdb"}, schema.GroupVersionKind{Kind: "postgresdb", Group: "acid.zalan.do", Version: "v1"}, "", false)
|
||||||
|
assert.Equal(t, match, true)
|
||||||
|
|
||||||
|
match = CheckKind(subresourceGVKToAPIResource, []string{"Postgresdb"}, schema.GroupVersionKind{Kind: "postgresdb", Group: "acid.zalan.do", Version: "v1"}, "", false)
|
||||||
|
assert.Equal(t, match, false)
|
||||||
|
|
||||||
subresourceGVKToAPIResource["networking.k8s.io/v1/NetworkPolicy/status"] = &metav1.APIResource{
|
subresourceGVKToAPIResource["networking.k8s.io/v1/NetworkPolicy/status"] = &metav1.APIResource{
|
||||||
Name: "networkpolicies/status",
|
Name: "networkpolicies/status",
|
||||||
SingularName: "",
|
SingularName: "",
|
||||||
|
|
|
@ -24,7 +24,7 @@ spec:
|
||||||
match:
|
match:
|
||||||
resources:
|
resources:
|
||||||
kinds:
|
kinds:
|
||||||
- pod
|
- Pod
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
protected: "*-test"
|
protected: "*-test"
|
||||||
|
@ -38,7 +38,7 @@ spec:
|
||||||
match:
|
match:
|
||||||
resources:
|
resources:
|
||||||
kinds:
|
kinds:
|
||||||
- pod
|
- Pod
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
protected: "test-*"
|
protected: "test-*"
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- postgresqls.yaml
|
||||||
|
assert:
|
||||||
|
- postgresqls-ready.yaml
|
|
@ -0,0 +1,14 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- reason: Succeeded
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: test-validate
|
|
@ -0,0 +1,24 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: test-validate
|
||||||
|
---
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
spec:
|
||||||
|
validationFailureAction: Enforce
|
||||||
|
rules:
|
||||||
|
- name: test-rule
|
||||||
|
match:
|
||||||
|
any:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- "acid.zalan.do/v1/postgresql"
|
||||||
|
validate:
|
||||||
|
message: "The label app=foo is required"
|
||||||
|
pattern:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: foo
|
|
@ -0,0 +1,12 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
commands:
|
||||||
|
- script: |
|
||||||
|
if kubectl apply -f resource.yaml 2>&1 | grep -q 'validation error: The label app=foo is required'
|
||||||
|
then
|
||||||
|
echo "Test succeeded. Resource creation was blocked."
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Tested failed. Resource was created."
|
||||||
|
exit 1
|
||||||
|
fi
|
|
@ -0,0 +1,7 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
commands:
|
||||||
|
- command: kubectl delete postgresql acid-minimal-cluster -n test-validate --force --wait=true --ignore-not-found=true
|
||||||
|
- command: kubectl delete cpol test --force --wait=true --ignore-not-found=true
|
||||||
|
- command: kubectl delete ns test-validate --force --ignore-not-found=true
|
||||||
|
- command: kubectl delete crd postgresqls.acid.zalan.do --force --wait=true
|
|
@ -0,0 +1,11 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This test validates that CRD with lowercase kind is supported.
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
A resource with kind `postgresql` should have the label `app=foo`.
|
||||||
|
|
||||||
|
## Reference Issue(s)
|
||||||
|
|
||||||
|
5989
|
|
@ -0,0 +1,27 @@
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: postgresqls.acid.zalan.do
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: postgres-operator
|
||||||
|
status:
|
||||||
|
acceptedNames:
|
||||||
|
categories:
|
||||||
|
- all
|
||||||
|
kind: postgresql
|
||||||
|
listKind: postgresqlList
|
||||||
|
plural: postgresqls
|
||||||
|
shortNames:
|
||||||
|
- pg
|
||||||
|
singular: postgresql
|
||||||
|
conditions:
|
||||||
|
- message: no conflicts found
|
||||||
|
reason: NoConflicts
|
||||||
|
status: "True"
|
||||||
|
type: NamesAccepted
|
||||||
|
- message: the initial names have been accepted
|
||||||
|
reason: InitialNamesAccepted
|
||||||
|
status: "True"
|
||||||
|
type: Established
|
||||||
|
storedVersions:
|
||||||
|
- v1
|
|
@ -0,0 +1,656 @@
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: postgresqls.acid.zalan.do
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: postgres-operator
|
||||||
|
spec:
|
||||||
|
group: acid.zalan.do
|
||||||
|
names:
|
||||||
|
kind: postgresql
|
||||||
|
listKind: postgresqlList
|
||||||
|
plural: postgresqls
|
||||||
|
singular: postgresql
|
||||||
|
shortNames:
|
||||||
|
- pg
|
||||||
|
categories:
|
||||||
|
- all
|
||||||
|
scope: Namespaced
|
||||||
|
versions:
|
||||||
|
- name: v1
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
subresources:
|
||||||
|
status: {}
|
||||||
|
additionalPrinterColumns:
|
||||||
|
- name: Team
|
||||||
|
type: string
|
||||||
|
description: Team responsible for Postgres cluster
|
||||||
|
jsonPath: .spec.teamId
|
||||||
|
- name: Version
|
||||||
|
type: string
|
||||||
|
description: PostgreSQL version
|
||||||
|
jsonPath: .spec.postgresql.version
|
||||||
|
- name: Pods
|
||||||
|
type: integer
|
||||||
|
description: Number of Pods per Postgres cluster
|
||||||
|
jsonPath: .spec.numberOfInstances
|
||||||
|
- name: Volume
|
||||||
|
type: string
|
||||||
|
description: Size of the bound volume
|
||||||
|
jsonPath: .spec.volume.size
|
||||||
|
- name: CPU-Request
|
||||||
|
type: string
|
||||||
|
description: Requested CPU for Postgres containers
|
||||||
|
jsonPath: .spec.resources.requests.cpu
|
||||||
|
- name: Memory-Request
|
||||||
|
type: string
|
||||||
|
description: Requested memory for Postgres containers
|
||||||
|
jsonPath: .spec.resources.requests.memory
|
||||||
|
- name: Age
|
||||||
|
type: date
|
||||||
|
jsonPath: .metadata.creationTimestamp
|
||||||
|
- name: Status
|
||||||
|
type: string
|
||||||
|
description: Current sync status of postgresql resource
|
||||||
|
jsonPath: .status.PostgresClusterStatus
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- kind
|
||||||
|
- apiVersion
|
||||||
|
- spec
|
||||||
|
properties:
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- postgresql
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- acid.zalan.do/v1
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- numberOfInstances
|
||||||
|
- teamId
|
||||||
|
- postgresql
|
||||||
|
- volume
|
||||||
|
properties:
|
||||||
|
additionalVolumes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- mountPath
|
||||||
|
- volumeSource
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
mountPath:
|
||||||
|
type: string
|
||||||
|
targetContainers:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
volumeSource:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
subPath:
|
||||||
|
type: string
|
||||||
|
allowedSourceRanges:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
pattern: '^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])$'
|
||||||
|
clone:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- cluster
|
||||||
|
properties:
|
||||||
|
cluster:
|
||||||
|
type: string
|
||||||
|
s3_endpoint:
|
||||||
|
type: string
|
||||||
|
s3_access_key_id:
|
||||||
|
type: string
|
||||||
|
s3_secret_access_key:
|
||||||
|
type: string
|
||||||
|
s3_force_path_style:
|
||||||
|
type: boolean
|
||||||
|
s3_wal_path:
|
||||||
|
type: string
|
||||||
|
timestamp:
|
||||||
|
type: string
|
||||||
|
pattern: '^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$'
|
||||||
|
# The regexp matches the date-time format (RFC 3339 Section 5.6) that specifies a timezone as an offset relative to UTC
|
||||||
|
# Example: 1996-12-19T16:39:57-08:00
|
||||||
|
# Note: this field requires a timezone
|
||||||
|
uid:
|
||||||
|
format: uuid
|
||||||
|
type: string
|
||||||
|
connectionPooler:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
dockerImage:
|
||||||
|
type: string
|
||||||
|
maxDBConnections:
|
||||||
|
type: integer
|
||||||
|
mode:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- "session"
|
||||||
|
- "transaction"
|
||||||
|
numberOfInstances:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
resources:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
limits:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
type: string
|
||||||
|
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
|
||||||
|
memory:
|
||||||
|
type: string
|
||||||
|
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
|
||||||
|
requests:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
type: string
|
||||||
|
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
|
||||||
|
memory:
|
||||||
|
type: string
|
||||||
|
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
databases:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
# Note: usernames specified here as database owners must be declared in the users key of the spec key.
|
||||||
|
dockerImage:
|
||||||
|
type: string
|
||||||
|
enableConnectionPooler:
|
||||||
|
type: boolean
|
||||||
|
enableReplicaConnectionPooler:
|
||||||
|
type: boolean
|
||||||
|
enableLogicalBackup:
|
||||||
|
type: boolean
|
||||||
|
enableMasterLoadBalancer:
|
||||||
|
type: boolean
|
||||||
|
enableMasterPoolerLoadBalancer:
|
||||||
|
type: boolean
|
||||||
|
enableReplicaLoadBalancer:
|
||||||
|
type: boolean
|
||||||
|
enableReplicaPoolerLoadBalancer:
|
||||||
|
type: boolean
|
||||||
|
enableShmVolume:
|
||||||
|
type: boolean
|
||||||
|
env:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
init_containers:
|
||||||
|
type: array
|
||||||
|
description: deprecated
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
initContainers:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
logicalBackupSchedule:
|
||||||
|
type: string
|
||||||
|
pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
|
||||||
|
maintenanceWindows:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
pattern: '^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$'
|
||||||
|
masterServiceAnnotations:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
nodeAffinity:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- preference
|
||||||
|
- weight
|
||||||
|
properties:
|
||||||
|
preference:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
matchExpressions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
- operator
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
type: string
|
||||||
|
values:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
matchFields:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
- operator
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
type: string
|
||||||
|
values:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
weight:
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- nodeSelectorTerms
|
||||||
|
properties:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
matchExpressions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
- operator
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
type: string
|
||||||
|
values:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
matchFields:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
- operator
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
type: string
|
||||||
|
values:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
numberOfInstances:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
patroni:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
failsafe_mode:
|
||||||
|
type: boolean
|
||||||
|
initdb:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
loop_wait:
|
||||||
|
type: integer
|
||||||
|
maximum_lag_on_failover:
|
||||||
|
type: integer
|
||||||
|
pg_hba:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
retry_timeout:
|
||||||
|
type: integer
|
||||||
|
slots:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
synchronous_mode:
|
||||||
|
type: boolean
|
||||||
|
synchronous_mode_strict:
|
||||||
|
type: boolean
|
||||||
|
synchronous_node_count:
|
||||||
|
type: integer
|
||||||
|
ttl:
|
||||||
|
type: integer
|
||||||
|
podAnnotations:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
pod_priority_class_name:
|
||||||
|
type: string
|
||||||
|
description: deprecated
|
||||||
|
podPriorityClassName:
|
||||||
|
type: string
|
||||||
|
postgresql:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- version
|
||||||
|
properties:
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- "10"
|
||||||
|
- "11"
|
||||||
|
- "12"
|
||||||
|
- "13"
|
||||||
|
- "14"
|
||||||
|
- "15"
|
||||||
|
parameters:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
preparedDatabases:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
defaultUsers:
|
||||||
|
type: boolean
|
||||||
|
extensions:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
schemas:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
defaultUsers:
|
||||||
|
type: boolean
|
||||||
|
defaultRoles:
|
||||||
|
type: boolean
|
||||||
|
secretNamespace:
|
||||||
|
type: string
|
||||||
|
replicaLoadBalancer:
|
||||||
|
type: boolean
|
||||||
|
description: deprecated
|
||||||
|
replicaServiceAnnotations:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
resources:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
limits:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
type: string
|
||||||
|
# Decimal natural followed by m, or decimal natural followed by
|
||||||
|
# dot followed by up to three decimal digits.
|
||||||
|
#
|
||||||
|
# This is because the Kubernetes CPU resource has millis as the
|
||||||
|
# maximum precision. The actual values are checked in code
|
||||||
|
# because the regular expression would be huge and horrible and
|
||||||
|
# not very helpful in validation error messages; this one checks
|
||||||
|
# only the format of the given number.
|
||||||
|
#
|
||||||
|
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu
|
||||||
|
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
|
||||||
|
# Note: the value specified here must not be zero or be lower
|
||||||
|
# than the corresponding request.
|
||||||
|
memory:
|
||||||
|
type: string
|
||||||
|
# You can express memory as a plain integer or as a fixed-point
|
||||||
|
# integer using one of these suffixes: E, P, T, G, M, k. You can
|
||||||
|
# also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki
|
||||||
|
#
|
||||||
|
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory
|
||||||
|
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
|
||||||
|
# Note: the value specified here must not be zero or be higher
|
||||||
|
# than the corresponding limit.
|
||||||
|
requests:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
type: string
|
||||||
|
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
|
||||||
|
memory:
|
||||||
|
type: string
|
||||||
|
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
|
||||||
|
schedulerName:
|
||||||
|
type: string
|
||||||
|
serviceAnnotations:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
sidecars:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
spiloRunAsUser:
|
||||||
|
type: integer
|
||||||
|
spiloRunAsGroup:
|
||||||
|
type: integer
|
||||||
|
spiloFSGroup:
|
||||||
|
type: integer
|
||||||
|
standby:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
s3_wal_path:
|
||||||
|
type: string
|
||||||
|
gs_wal_path:
|
||||||
|
type: string
|
||||||
|
standby_host:
|
||||||
|
type: string
|
||||||
|
standby_port:
|
||||||
|
type: string
|
||||||
|
oneOf:
|
||||||
|
- required:
|
||||||
|
- s3_wal_path
|
||||||
|
- required:
|
||||||
|
- gs_wal_path
|
||||||
|
- required:
|
||||||
|
- standby_host
|
||||||
|
streams:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- applicationId
|
||||||
|
- database
|
||||||
|
- tables
|
||||||
|
properties:
|
||||||
|
applicationId:
|
||||||
|
type: string
|
||||||
|
batchSize:
|
||||||
|
type: integer
|
||||||
|
database:
|
||||||
|
type: string
|
||||||
|
filter:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
tables:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- eventType
|
||||||
|
properties:
|
||||||
|
eventType:
|
||||||
|
type: string
|
||||||
|
idColumn:
|
||||||
|
type: string
|
||||||
|
payloadColumn:
|
||||||
|
type: string
|
||||||
|
teamId:
|
||||||
|
type: string
|
||||||
|
tls:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- secretName
|
||||||
|
properties:
|
||||||
|
secretName:
|
||||||
|
type: string
|
||||||
|
certificateFile:
|
||||||
|
type: string
|
||||||
|
privateKeyFile:
|
||||||
|
type: string
|
||||||
|
caFile:
|
||||||
|
type: string
|
||||||
|
caSecretName:
|
||||||
|
type: string
|
||||||
|
tolerations:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- Equal
|
||||||
|
- Exists
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
effect:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- NoExecute
|
||||||
|
- NoSchedule
|
||||||
|
- PreferNoSchedule
|
||||||
|
tolerationSeconds:
|
||||||
|
type: integer
|
||||||
|
useLoadBalancer:
|
||||||
|
type: boolean
|
||||||
|
description: deprecated
|
||||||
|
users:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- bypassrls
|
||||||
|
- BYPASSRLS
|
||||||
|
- nobypassrls
|
||||||
|
- NOBYPASSRLS
|
||||||
|
- createdb
|
||||||
|
- CREATEDB
|
||||||
|
- nocreatedb
|
||||||
|
- NOCREATEDB
|
||||||
|
- createrole
|
||||||
|
- CREATEROLE
|
||||||
|
- nocreaterole
|
||||||
|
- NOCREATEROLE
|
||||||
|
- inherit
|
||||||
|
- INHERIT
|
||||||
|
- noinherit
|
||||||
|
- NOINHERIT
|
||||||
|
- login
|
||||||
|
- LOGIN
|
||||||
|
- nologin
|
||||||
|
- NOLOGIN
|
||||||
|
- replication
|
||||||
|
- REPLICATION
|
||||||
|
- noreplication
|
||||||
|
- NOREPLICATION
|
||||||
|
- superuser
|
||||||
|
- SUPERUSER
|
||||||
|
- nosuperuser
|
||||||
|
- NOSUPERUSER
|
||||||
|
usersWithInPlaceSecretRotation:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
usersWithSecretRotation:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
volume:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- size
|
||||||
|
properties:
|
||||||
|
iops:
|
||||||
|
type: integer
|
||||||
|
selector:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
matchExpressions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
- operator
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- DoesNotExist
|
||||||
|
- Exists
|
||||||
|
- In
|
||||||
|
- NotIn
|
||||||
|
values:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
matchLabels:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
size:
|
||||||
|
type: string
|
||||||
|
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
|
||||||
|
# Note: the value specified here must not be zero.
|
||||||
|
storageClass:
|
||||||
|
type: string
|
||||||
|
subPath:
|
||||||
|
type: string
|
||||||
|
throughput:
|
||||||
|
type: integer
|
||||||
|
status:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
|
@ -0,0 +1,21 @@
|
||||||
|
apiVersion: "acid.zalan.do/v1"
|
||||||
|
kind: postgresql
|
||||||
|
metadata:
|
||||||
|
name: acid-minimal-cluster
|
||||||
|
namespace: test-validate
|
||||||
|
spec:
|
||||||
|
teamId: "acid"
|
||||||
|
volume:
|
||||||
|
size: 1Gi
|
||||||
|
numberOfInstances: 2
|
||||||
|
users:
|
||||||
|
# database owner
|
||||||
|
zalando:
|
||||||
|
- superuser
|
||||||
|
- createdb
|
||||||
|
|
||||||
|
#databases: name->owner
|
||||||
|
databases:
|
||||||
|
foo: zalando
|
||||||
|
postgresql:
|
||||||
|
version: "15"
|
Loading…
Add table
Reference in a new issue