From d55b67b349021b6c46fc6ce78f2a36bd4217145f Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Tue, 2 May 2023 00:36:15 -0700 Subject: [PATCH] Clean up brace whitespace in **/validation_test.go This was making my eyes bleed as I read over code. I used the following in vim. I made them up on the fly, but they seemed to pass manual inspection. :g/},\n\s*{$/s//}, {/ :w :g/{$\n\s*{$/s//{{/ :w :g/^\(\s*\)},\n\1},$/s//}},/ :w :g/^\(\s*\)},$\n\1}$/s//}}/ :w --- .../validation/validation_test.go | 6891 +++---- .../validation/validation_test.go | 466 +- pkg/apis/apps/validation/validation_test.go | 1552 +- .../validation/validation_test.go | 129 +- .../autoscaling/validation/validation_test.go | 2326 +-- pkg/apis/batch/validation/validation_test.go | 481 +- .../validation/validation_test.go | 1164 +- .../core/v1/validation/validation_test.go | 472 +- pkg/apis/core/validation/validation_test.go | 16011 +++++++--------- .../flowcontrol/validation/validation_test.go | 2289 +-- .../networking/validation/validation_test.go | 368 +- pkg/apis/node/validation/validation_test.go | 30 +- pkg/apis/policy/validation/validation_test.go | 291 +- pkg/apis/rbac/validation/validation_test.go | 78 +- .../storage/validation/validation_test.go | 2775 ++- .../apis/config/validation/validation_test.go | 818 +- .../apis/config/validation/validation_test.go | 326 +- .../apis/config/validation/validation_test.go | 391 +- .../validation/validation_test.go | 250 +- .../validation/validation_test.go | 55 +- .../validation/validation_test.go | 266 +- .../meta/v1/validation/validation_test.go | 428 +- .../pkg/util/validation/validation_test.go | 48 +- .../validation/validation_test.go | 28 +- .../apis/audit/validation/validation_test.go | 8 +- .../apis/config/validation/validation_test.go | 2112 +- .../tools/clientcmd/validation_test.go | 56 +- 27 files changed, 18026 insertions(+), 22083 deletions(-) diff --git a/pkg/apis/admissionregistration/validation/validation_test.go b/pkg/apis/admissionregistration/validation/validation_test.go index b73dc988734..68b063ea351 100644 --- a/pkg/apis/admissionregistration/validation/validation_test.go +++ b/pkg/apis/admissionregistration/validation/validation_test.go @@ -56,926 +56,759 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { name string config *admissionregistration.ValidatingWebhookConfiguration expectedError string - }{ - { - name: "AdmissionReviewVersions are required", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, false), - expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`, + }{{ + name: "AdmissionReviewVersions are required", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, false), + expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`, + }, { + name: "should fail on bad AdmissionReviewVersion value", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + AdmissionReviewVersions: []string{"0v"}, + }, + }, true), + expectedError: `Invalid value: "0v": a DNS-1035 label`, + }, { + name: "should pass on valid AdmissionReviewVersion", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + AdmissionReviewVersions: []string{"v1beta1"}, + }, + }, true), + expectedError: ``, + }, { + name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + AdmissionReviewVersions: []string{"v1beta1", "invalid-version"}, + }, + }, true), + expectedError: ``, + }, { + name: "should fail on invalid AdmissionReviewVersion", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + AdmissionReviewVersions: []string{"invalidVersion"}, + }, + }, true), + expectedError: `Invalid value: []string{"invalidVersion"}`, + }, { + name: "should fail on duplicate AdmissionReviewVersion", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + AdmissionReviewVersions: []string{"v1beta1", "v1beta1"}, + }, + }, true), + expectedError: `Invalid value: "v1beta1": duplicate version`, + }, { + name: "all Webhooks must have a fully qualified name", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, }, { - name: "should fail on bad AdmissionReviewVersion value", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - AdmissionReviewVersions: []string{"0v"}, - }, - }, true), - expectedError: `Invalid value: "0v": a DNS-1035 label`, - }, - { - name: "should pass on valid AdmissionReviewVersion", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - AdmissionReviewVersions: []string{"v1beta1"}, - }, - }, true), - expectedError: ``, - }, - { - name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - AdmissionReviewVersions: []string{"v1beta1", "invalid-version"}, - }, - }, true), - expectedError: ``, - }, - { - name: "should fail on invalid AdmissionReviewVersion", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - AdmissionReviewVersions: []string{"invalidVersion"}, - }, - }, true), - expectedError: `Invalid value: []string{"invalidVersion"}`, - }, - { - name: "should fail on duplicate AdmissionReviewVersion", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - AdmissionReviewVersions: []string{"v1beta1", "v1beta1"}, - }, - }, true), - expectedError: `Invalid value: "v1beta1": duplicate version`, - }, - { - name: "all Webhooks must have a fully qualified name", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - }, - { - Name: "k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - }, - { - Name: "", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - }, - }, true), - expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`, - }, - { - name: "Webhooks must have unique names when created", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `webhooks[1].name: Duplicate value: "webhook.k8s.io"`, - }, - { - name: "Operations must not be empty or nil", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - { - Operations: nil, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, true), - expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`, - }, - { - name: "\"\" is NOT a valid operation", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE", ""}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, true), - expectedError: `Unsupported value: ""`, - }, - { - name: "operation must be either create/update/delete/connect", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"PATCH"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, true), - expectedError: `Unsupported value: "PATCH"`, - }, - { - name: "wildcard operation cannot be mixed with other strings", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE", "*"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, true), - expectedError: `if '*' is present, must not specify other operations`, - }, - { - name: `resource "*" can co-exist with resources that have subresources`, - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*", "a/b", "a/*", "*/b"}, - }, - }, - }, - }, - }, true), - }, - { - name: `resource "*" cannot mix with resources that don't have subresources`, - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*", "a"}, - }, - }, - }, - }, - }, true), - expectedError: `if '*' is present, must not specify other resources without subresources`, - }, - { - name: "resource a/* cannot mix with a/x", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a/*", "a/x"}, - }, - }, - }, - }, - }, true), - expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, - }, - { - name: "resource a/* can mix with a", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a/*", "a"}, - }, - }, - }, - }, - }, true), - }, - { - name: "resource */a cannot mix with x/a", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/a", "x/a"}, - }, - }, - }, - }, - }, true), - expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, - }, - { - name: "resource */* cannot mix with other resources", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/*", "a"}, - }, - }, - }, - }, - }, true), - expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, - }, - { - name: "FailurePolicy can only be \"Ignore\" or \"Fail\"", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("other") - return &r - }(), - }, - }, true), - expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, - }, - { - name: "AdmissionReviewVersions are required", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, false), - expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`, - }, - { - name: "SideEffects are required", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: nil, - }, - }, true), - expectedError: `webhooks[0].sideEffects: Required value: must specify one of None, NoneOnDryRun`, - }, - { - name: "SideEffects can only be \"None\" or \"NoneOnDryRun\" when created", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: func() *admissionregistration.SideEffectClass { - r := admissionregistration.SideEffectClass("other") - return &r - }(), - }, - }, true), - expectedError: `webhooks[0].sideEffects: Unsupported value: "other": supported values: "None", "NoneOnDryRun"`, - }, - { - name: "both service and URL missing", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{}, - }, - }, true), - expectedError: `exactly one of`, - }, - { - name: "both service and URL provided", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Port: 443, - }, - URL: strPtr("example.com/k8s/webhook"), - }, - }, - }, true), - expectedError: `[0].clientConfig: Required value: exactly one of url or service is required`, - }, - { - name: "blank URL", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr(""), - }, - }, - }, true), - expectedError: `[0].clientConfig.url: Invalid value: "": host must be specified`, - }, - { - name: "wrong scheme", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("http://example.com"), - }, - }, - }, true), - expectedError: `https`, - }, - { - name: "missing host", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("https:///fancy/webhook"), - }, - }, - }, true), - expectedError: `host must be specified`, - }, - { - name: "fragment", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("https://example.com/#bookmark"), - }, - }, - }, true), - expectedError: `"bookmark": fragments are not permitted`, - }, - { - name: "query", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("https://example.com?arg=value"), - }, - }, - }, true), - expectedError: `"arg=value": query parameters are not permitted`, - }, - { - name: "user", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("https://harry.potter@example.com/"), - }, - }, - }, true), - expectedError: `"harry.potter": user information is not permitted`, - }, - { - name: "just totally wrong", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"), - }, - }, - }, true), - expectedError: `host must be specified`, - }, - { - name: "path must start with slash", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("foo/"), - Port: 443, - }, - }, - }, - }, true), - expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`, - }, - { - name: "path accepts slash", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("/"), - Port: 443, - }, - }, - SideEffects: &noSideEffect, - }, - }, true), - expectedError: ``, - }, - { - name: "path accepts no trailing slash", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("/foo"), - Port: 443, - }, - }, - SideEffects: &noSideEffect, - }, - }, true), - expectedError: ``, - }, - { - name: "path fails //", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("//"), - Port: 443, - }, - }, - SideEffects: &noSideEffect, - }, - }, true), - expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`, - }, - { - name: "path no empty step", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("/foo//bar/"), - Port: 443, - }, - }, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`, + Name: "k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, }, { - name: "path no empty step 2", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("/foo/bar//"), - Port: 443, - }, - }, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`, + Name: "", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, }, - { - name: "path no non-subdomain", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("/apis/foo.bar/v1alpha1/--bad"), - Port: 443, - }, - }, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a lowercase RFC 1123 subdomain`, + }, true), + expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`, + }, { + name: "Webhooks must have unique names when created", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, { + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, }, - { - name: "invalid port 0", - config: newValidatingWebhookConfiguration( - []admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("https://apis/foo.bar"), - Port: 0, - }, - }, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `Invalid value: 0: port is not valid: must be between 1 and 65535, inclusive`, - }, - { - name: "invalid port >65535", - config: newValidatingWebhookConfiguration( - []admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("https://apis/foo.bar"), - Port: 65536, - }, - }, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `Invalid value: 65536: port is not valid: must be between 1 and 65535, inclusive`, - }, - { - name: "timeout seconds cannot be greater than 30", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - TimeoutSeconds: int32Ptr(31), + }, true), + expectedError: `webhooks[1].name: Duplicate value: "webhook.k8s.io"`, + }, { + name: "Operations must not be empty or nil", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, - }, true), - expectedError: `webhooks[0].timeoutSeconds: Invalid value: 31: the timeout value must be between 1 and 30 seconds`, + }, { + Operations: nil, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }}, }, - { - name: "timeout seconds cannot be smaller than 1", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - TimeoutSeconds: int32Ptr(0), + }, true), + expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`, + }, { + name: "\"\" is NOT a valid operation", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE", ""}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, - }, true), - expectedError: `webhooks[0].timeoutSeconds: Invalid value: 0: the timeout value must be between 1 and 30 seconds`, + }}, }, - { - name: "timeout seconds must be positive", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - TimeoutSeconds: int32Ptr(-1), + }, true), + expectedError: `Unsupported value: ""`, + }, { + name: "operation must be either create/update/delete/connect", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"PATCH"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, - }, true), - expectedError: `webhooks[0].timeoutSeconds: Invalid value: -1: the timeout value must be between 1 and 30 seconds`, + }}, }, - { - name: "valid timeout seconds", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - TimeoutSeconds: int32Ptr(1), + }, true), + expectedError: `Unsupported value: "PATCH"`, + }, { + name: "wildcard operation cannot be mixed with other strings", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE", "*"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, - { - Name: "webhook2.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - TimeoutSeconds: int32Ptr(15), - }, - { - Name: "webhook3.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - TimeoutSeconds: int32Ptr(30), - }, - }, true), + }}, }, - { - name: "single match condition must have a name", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Expression: "true", - }, + }, true), + expectedError: `if '*' is present, must not specify other operations`, + }, { + name: `resource "*" can co-exist with resources that have subresources`, + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*", "a/b", "a/*", "*/b"}, + }, + }}, + }, + }, true), + }, { + name: `resource "*" cannot mix with resources that don't have subresources`, + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*", "a"}, + }, + }}, + }, + }, true), + expectedError: `if '*' is present, must not specify other resources without subresources`, + }, { + name: "resource a/* cannot mix with a/x", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a/*", "a/x"}, + }, + }}, + }, + }, true), + expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, + }, { + name: "resource a/* can mix with a", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a/*", "a"}, + }, + }}, + }, + }, true), + }, { + name: "resource */a cannot mix with x/a", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/a", "x/a"}, + }, + }}, + }, + }, true), + expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, + }, { + name: "resource */* cannot mix with other resources", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/*", "a"}, + }, + }}, + }, + }, true), + expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, + }, { + name: "FailurePolicy can only be \"Ignore\" or \"Fail\"", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("other") + return &r + }(), + }, + }, true), + expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, + }, { + name: "AdmissionReviewVersions are required", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, false), + expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`, + }, { + name: "SideEffects are required", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: nil, + }, + }, true), + expectedError: `webhooks[0].sideEffects: Required value: must specify one of None, NoneOnDryRun`, + }, { + name: "SideEffects can only be \"None\" or \"NoneOnDryRun\" when created", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: func() *admissionregistration.SideEffectClass { + r := admissionregistration.SideEffectClass("other") + return &r + }(), + }, + }, true), + expectedError: `webhooks[0].sideEffects: Unsupported value: "other": supported values: "None", "NoneOnDryRun"`, + }, { + name: "both service and URL missing", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{}, + }, + }, true), + expectedError: `exactly one of`, + }, { + name: "both service and URL provided", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Port: 443, + }, + URL: strPtr("example.com/k8s/webhook"), + }, + }, + }, true), + expectedError: `[0].clientConfig: Required value: exactly one of url or service is required`, + }, { + name: "blank URL", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr(""), + }, + }, + }, true), + expectedError: `[0].clientConfig.url: Invalid value: "": host must be specified`, + }, { + name: "wrong scheme", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("http://example.com"), + }, + }, + }, true), + expectedError: `https`, + }, { + name: "missing host", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https:///fancy/webhook"), + }, + }, + }, true), + expectedError: `host must be specified`, + }, { + name: "fragment", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https://example.com/#bookmark"), + }, + }, + }, true), + expectedError: `"bookmark": fragments are not permitted`, + }, { + name: "query", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https://example.com?arg=value"), + }, + }, + }, true), + expectedError: `"arg=value": query parameters are not permitted`, + }, { + name: "user", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https://harry.potter@example.com/"), + }, + }, + }, true), + expectedError: `"harry.potter": user information is not permitted`, + }, { + name: "just totally wrong", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"), + }, + }, + }, true), + expectedError: `host must be specified`, + }, { + name: "path must start with slash", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("foo/"), + Port: 443, + }, + }, + }, + }, true), + expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`, + }, { + name: "path accepts slash", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/"), + Port: 443, + }, + }, + SideEffects: &noSideEffect, + }, + }, true), + expectedError: ``, + }, { + name: "path accepts no trailing slash", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo"), + Port: 443, + }, + }, + SideEffects: &noSideEffect, + }, + }, true), + expectedError: ``, + }, { + name: "path fails //", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("//"), + Port: 443, + }, + }, + SideEffects: &noSideEffect, + }, + }, true), + expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`, + }, { + name: "path no empty step", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo//bar/"), + Port: 443, + }, + }, + SideEffects: &unknownSideEffect, + }, + }, true), + expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`, + }, { + name: "path no empty step 2", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo/bar//"), + Port: 443, + }, + }, + SideEffects: &unknownSideEffect, + }, + }, true), + expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`, + }, { + name: "path no non-subdomain", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/apis/foo.bar/v1alpha1/--bad"), + Port: 443, + }, + }, + SideEffects: &unknownSideEffect, + }, + }, true), + expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a lowercase RFC 1123 subdomain`, + }, { + name: "invalid port 0", + config: newValidatingWebhookConfiguration( + []admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("https://apis/foo.bar"), + Port: 0, }, }, + SideEffects: &unknownSideEffect, + }, }, true), - expectedError: `webhooks[0].matchConditions[0].name: Required value`, - }, - { - name: "all match conditions must have a name", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Expression: "true", - }, - { - Expression: "true", - }, - }, - }, - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "", - Expression: "true", - }, + expectedError: `Invalid value: 0: port is not valid: must be between 1 and 65535, inclusive`, + }, { + name: "invalid port >65535", + config: newValidatingWebhookConfiguration( + []admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("https://apis/foo.bar"), + Port: 65536, }, }, + SideEffects: &unknownSideEffect, + }, }, true), - expectedError: `webhooks[0].matchConditions[0].name: Required value, webhooks[0].matchConditions[1].name: Required value, webhooks[1].matchConditions[0].name: Required value`, + expectedError: `Invalid value: 65536: port is not valid: must be between 1 and 65535, inclusive`, + }, { + name: "timeout seconds cannot be greater than 30", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + TimeoutSeconds: int32Ptr(31), }, - { - name: "single match condition must have a qualified name", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "-hello", - Expression: "true", - }, - }, - }, - }, true), - expectedError: `webhooks[0].matchConditions[0].name: Invalid value: "-hello": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`, + }, true), + expectedError: `webhooks[0].timeoutSeconds: Invalid value: 31: the timeout value must be between 1 and 30 seconds`, + }, { + name: "timeout seconds cannot be smaller than 1", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + TimeoutSeconds: int32Ptr(0), }, - { - name: "all match conditions must have qualified names", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: ".io", - Expression: "true", - }, - { - Name: "thing.test.com", - Expression: "true", - }, - }, - }, - { - Name: "webhook2.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "some name", - Expression: "true", - }, - }, - }, - }, true), - expectedError: `[webhooks[0].matchConditions[0].name: Invalid value: ".io": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]'), webhooks[1].matchConditions[0].name: Invalid value: "some name": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')]`, + }, true), + expectedError: `webhooks[0].timeoutSeconds: Invalid value: 0: the timeout value must be between 1 and 30 seconds`, + }, { + name: "timeout seconds must be positive", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + TimeoutSeconds: int32Ptr(-1), }, - { - name: "expression is required", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - }, - }, - }, - }, true), - expectedError: `webhooks[0].matchConditions[0].expression: Required value`, + }, true), + expectedError: `webhooks[0].timeoutSeconds: Invalid value: -1: the timeout value must be between 1 and 30 seconds`, + }, { + name: "valid timeout seconds", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + TimeoutSeconds: int32Ptr(1), + }, { + Name: "webhook2.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + TimeoutSeconds: int32Ptr(15), + }, { + Name: "webhook3.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + TimeoutSeconds: int32Ptr(30), }, - { - name: "expression is required to have some value", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "", - }, - }, - }, - }, true), - expectedError: `webhooks[0].matchConditions[0].expression: Required value`, + }, true), + }, { + name: "single match condition must have a name", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Expression: "true", + }}, }, - { - name: "invalid expression", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "object.x in [1, 2, ", - }, - }, - }, - }, true), - expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "object.x in [1, 2,": compilation failed: ERROR: :1:19: Syntax error: missing ']' at ''`, + }, true), + expectedError: `webhooks[0].matchConditions[0].name: Required value`, + }, { + name: "all match conditions must have a name", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Expression: "true", + }, { + Expression: "true", + }}, + }, { + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "", + Expression: "true", + }}, }, - { - name: "unique names same hook", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "true", - }, - { - Name: "webhook.k8s.io", - Expression: "true", - }, - }, - }, - }, true), - expectedError: `matchConditions[1].name: Duplicate value: "webhook.k8s.io"`, + }, true), + expectedError: `webhooks[0].matchConditions[0].name: Required value, webhooks[0].matchConditions[1].name: Required value, webhooks[1].matchConditions[0].name: Required value`, + }, { + name: "single match condition must have a qualified name", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "-hello", + Expression: "true", + }}, }, - { - name: "repeat names allowed across different hooks", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "true", - }, - }, - }, - { - Name: "webhook2.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "true", - }, - }, - }, - }, true), - expectedError: ``, + }, true), + expectedError: `webhooks[0].matchConditions[0].name: Invalid value: "-hello": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`, + }, { + name: "all match conditions must have qualified names", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: ".io", + Expression: "true", + }, { + Name: "thing.test.com", + Expression: "true", + }}, + }, { + Name: "webhook2.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "some name", + Expression: "true", + }}, }, - { - name: "must evaluate to bool", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "6", - }, - }, - }, - }, true), - expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "6": must evaluate to bool`, + }, true), + expectedError: `[webhooks[0].matchConditions[0].name: Invalid value: ".io": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]'), webhooks[1].matchConditions[0].name: Invalid value: "some name": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')]`, + }, { + name: "expression is required", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + }}, }, - { - name: "max of 64 match conditions", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: get65MatchConditions(), - }, - }, true), - expectedError: `webhooks[0].matchConditions: Too many: 65: must have at most 64 items`, + }, true), + expectedError: `webhooks[0].matchConditions[0].expression: Required value`, + }, { + name: "expression is required to have some value", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "", + }}, }, - } + }, true), + expectedError: `webhooks[0].matchConditions[0].expression: Required value`, + }, { + name: "invalid expression", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "object.x in [1, 2, ", + }}, + }, + }, true), + expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "object.x in [1, 2,": compilation failed: ERROR: :1:19: Syntax error: missing ']' at ''`, + }, { + name: "unique names same hook", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "true", + }, { + Name: "webhook.k8s.io", + Expression: "true", + }}, + }, + }, true), + expectedError: `matchConditions[1].name: Duplicate value: "webhook.k8s.io"`, + }, { + name: "repeat names allowed across different hooks", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "true", + }}, + }, { + Name: "webhook2.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "true", + }}, + }, + }, true), + expectedError: ``, + }, { + name: "must evaluate to bool", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "6", + }}, + }, + }, true), + expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "6": must evaluate to bool`, + }, { + name: "max of 64 match conditions", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: get65MatchConditions(), + }, + }, true), + expectedError: `webhooks[0].matchConditions: Too many: 65: must have at most 64 items`, + }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { errs := ValidateValidatingWebhookConfiguration(test.config) @@ -1004,137 +837,115 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) { oldconfig *admissionregistration.ValidatingWebhookConfiguration config *admissionregistration.ValidatingWebhookConfiguration expectedError string - }{ - { - name: "should pass on valid new AdmissionReviewVersion", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"v1beta1"}, - }, - }, true), - oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: ``, + }{{ + name: "should pass on valid new AdmissionReviewVersion", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"v1beta1"}, }, - { - name: "should pass on invalid AdmissionReviewVersion with invalid previous versions", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"}, - }, - }, true), - oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"invalid-v0"}, - }, - }, true), - expectedError: ``, + }, true), + oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, }, - { - name: "should fail on invalid AdmissionReviewVersion with valid previous versions", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"invalid-v1"}, - }, - }, true), - oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"v1beta1", "invalid-v1"}, - }, - }, true), - expectedError: `Invalid value: []string{"invalid-v1"}`, + }, true), + expectedError: ``, + }, { + name: "should pass on invalid AdmissionReviewVersion with invalid previous versions", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"}, }, - { - name: "should fail on invalid AdmissionReviewVersion with missing previous versions", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"invalid-v1"}, - }, - }, true), - oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, false), - expectedError: `Invalid value: []string{"invalid-v1"}`, + }, true), + oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"invalid-v0"}, }, - { - name: "Webhooks must have unique names when old config has unique names", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, false), - expectedError: `webhooks[1].name: Duplicate value: "webhook.k8s.io"`, + }, true), + expectedError: ``, + }, { + name: "should fail on invalid AdmissionReviewVersion with valid previous versions", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"invalid-v1"}, }, - { - name: "Webhooks can have duplicate names when old config has duplicate names", - config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: ``, + }, true), + oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"v1beta1", "invalid-v1"}, }, - } + }, true), + expectedError: `Invalid value: []string{"invalid-v1"}`, + }, { + name: "should fail on invalid AdmissionReviewVersion with missing previous versions", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"invalid-v1"}, + }, + }, true), + oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, false), + expectedError: `Invalid value: []string{"invalid-v1"}`, + }, { + name: "Webhooks must have unique names when old config has unique names", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, { + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, true), + oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, false), + expectedError: `webhooks[1].name: Duplicate value: "webhook.k8s.io"`, + }, { + name: "Webhooks can have duplicate names when old config has duplicate names", + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, { + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, true), + oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, { + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, true), + expectedError: ``, + }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { errs := ValidateValidatingWebhookConfigurationUpdate(test.config, test.oldconfig) @@ -1179,926 +990,759 @@ func TestValidateMutatingWebhookConfiguration(t *testing.T) { name string config *admissionregistration.MutatingWebhookConfiguration expectedError string - }{ - { - name: "AdmissionReviewVersions are required", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, false), - expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`, + }{{ + name: "AdmissionReviewVersions are required", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, false), + expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`, + }, { + name: "should fail on bad AdmissionReviewVersion value", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + AdmissionReviewVersions: []string{"0v"}, + }, + }, true), + expectedError: `Invalid value: "0v": a DNS-1035 label`, + }, { + name: "should pass on valid AdmissionReviewVersion", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + AdmissionReviewVersions: []string{"v1beta1"}, + }, + }, true), + expectedError: ``, + }, { + name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + AdmissionReviewVersions: []string{"v1beta1", "invalid-version"}, + }, + }, true), + expectedError: ``, + }, { + name: "should fail on invalid AdmissionReviewVersion", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + AdmissionReviewVersions: []string{"invalidVersion"}, + }, + }, true), + expectedError: `Invalid value: []string{"invalidVersion"}`, + }, { + name: "should fail on duplicate AdmissionReviewVersion", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + AdmissionReviewVersions: []string{"v1beta1", "v1beta1"}, + }, + }, true), + expectedError: `Invalid value: "v1beta1": duplicate version`, + }, { + name: "all Webhooks must have a fully qualified name", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, }, { - name: "should fail on bad AdmissionReviewVersion value", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - AdmissionReviewVersions: []string{"0v"}, - }, - }, true), - expectedError: `Invalid value: "0v": a DNS-1035 label`, - }, - { - name: "should pass on valid AdmissionReviewVersion", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - AdmissionReviewVersions: []string{"v1beta1"}, - }, - }, true), - expectedError: ``, - }, - { - name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - AdmissionReviewVersions: []string{"v1beta1", "invalid-version"}, - }, - }, true), - expectedError: ``, - }, - { - name: "should fail on invalid AdmissionReviewVersion", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - AdmissionReviewVersions: []string{"invalidVersion"}, - }, - }, true), - expectedError: `Invalid value: []string{"invalidVersion"}`, - }, - { - name: "should fail on duplicate AdmissionReviewVersion", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - AdmissionReviewVersions: []string{"v1beta1", "v1beta1"}, - }, - }, true), - expectedError: `Invalid value: "v1beta1": duplicate version`, - }, - { - name: "all Webhooks must have a fully qualified name", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - }, - { - Name: "k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - }, - { - Name: "", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - }, - }, true), - expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`, - }, - { - name: "Webhooks must have unique names when created", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `webhooks[1].name: Duplicate value: "webhook.k8s.io"`, - }, - { - name: "Operations must not be empty or nil", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - { - Operations: nil, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, true), - expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`, - }, - { - name: "\"\" is NOT a valid operation", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE", ""}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, true), - expectedError: `Unsupported value: ""`, - }, - { - name: "operation must be either create/update/delete/connect", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"PATCH"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, true), - expectedError: `Unsupported value: "PATCH"`, - }, - { - name: "wildcard operation cannot be mixed with other strings", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE", "*"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, true), - expectedError: `if '*' is present, must not specify other operations`, - }, - { - name: `resource "*" can co-exist with resources that have subresources`, - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*", "a/b", "a/*", "*/b"}, - }, - }, - }, - }, - }, true), - }, - { - name: `resource "*" cannot mix with resources that don't have subresources`, - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*", "a"}, - }, - }, - }, - }, - }, true), - expectedError: `if '*' is present, must not specify other resources without subresources`, - }, - { - name: "resource a/* cannot mix with a/x", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a/*", "a/x"}, - }, - }, - }, - }, - }, true), - expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, - }, - { - name: "resource a/* can mix with a", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a/*", "a"}, - }, - }, - }, - }, - }, true), - }, - { - name: "resource */a cannot mix with x/a", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/a", "x/a"}, - }, - }, - }, - }, - }, true), - expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, - }, - { - name: "resource */* cannot mix with other resources", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - Rules: []admissionregistration.RuleWithOperations{ - { - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/*", "a"}, - }, - }, - }, - }, - }, true), - expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, - }, - { - name: "FailurePolicy can only be \"Ignore\" or \"Fail\"", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("other") - return &r - }(), - }, - }, true), - expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, - }, - { - name: "AdmissionReviewVersions are required", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, false), - expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`, - }, - { - name: "SideEffects are required", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: nil, - }, - }, true), - expectedError: `webhooks[0].sideEffects: Required value: must specify one of None, NoneOnDryRun`, - }, - { - name: "SideEffects can only be \"None\" or \"NoneOnDryRun\" when created", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: func() *admissionregistration.SideEffectClass { - r := admissionregistration.SideEffectClass("other") - return &r - }(), - }, - }, true), - expectedError: `webhooks[0].sideEffects: Unsupported value: "other": supported values: "None", "NoneOnDryRun"`, - }, - { - name: "both service and URL missing", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{}, - }, - }, true), - expectedError: `exactly one of`, - }, - { - name: "both service and URL provided", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Port: 443, - }, - URL: strPtr("example.com/k8s/webhook"), - }, - }, - }, true), - expectedError: `[0].clientConfig: Required value: exactly one of url or service is required`, - }, - { - name: "blank URL", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr(""), - }, - }, - }, true), - expectedError: `[0].clientConfig.url: Invalid value: "": host must be specified`, - }, - { - name: "wrong scheme", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("http://example.com"), - }, - }, - }, true), - expectedError: `https`, - }, - { - name: "missing host", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("https:///fancy/webhook"), - }, - }, - }, true), - expectedError: `host must be specified`, - }, - { - name: "fragment", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("https://example.com/#bookmark"), - }, - }, - }, true), - expectedError: `"bookmark": fragments are not permitted`, - }, - { - name: "query", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("https://example.com?arg=value"), - }, - }, - }, true), - expectedError: `"arg=value": query parameters are not permitted`, - }, - { - name: "user", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("https://harry.potter@example.com/"), - }, - }, - }, true), - expectedError: `"harry.potter": user information is not permitted`, - }, - { - name: "just totally wrong", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"), - }, - }, - }, true), - expectedError: `host must be specified`, - }, - { - name: "path must start with slash", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("foo/"), - Port: 443, - }, - }, - }, - }, true), - expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`, - }, - { - name: "path accepts slash", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("/"), - Port: 443, - }, - }, - SideEffects: &noSideEffect, - }, - }, true), - expectedError: ``, - }, - { - name: "path accepts no trailing slash", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("/foo"), - Port: 443, - }, - }, - SideEffects: &noSideEffect, - }, - }, true), - expectedError: ``, - }, - { - name: "path fails //", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("//"), - Port: 443, - }, - }, - SideEffects: &noSideEffect, - }, - }, true), - expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`, - }, - { - name: "path no empty step", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("/foo//bar/"), - Port: 443, - }, - }, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`, + Name: "k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, }, { - name: "path no empty step 2", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("/foo/bar//"), - Port: 443, - }, - }, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`, + Name: "", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, }, - { - name: "path no non-subdomain", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("/apis/foo.bar/v1alpha1/--bad"), - Port: 443, - }, - }, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a lowercase RFC 1123 subdomain`, + }, true), + expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`, + }, { + name: "Webhooks must have unique names when created", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, { + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, }, - { - name: "invalid port 0", - config: newMutatingWebhookConfiguration( - []admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("https://apis/foo.bar"), - Port: 0, - }, - }, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `Invalid value: 0: port is not valid: must be between 1 and 65535, inclusive`, - }, - { - name: "invalid port >65535", - config: newMutatingWebhookConfiguration( - []admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: admissionregistration.WebhookClientConfig{ - Service: &admissionregistration.ServiceReference{ - Namespace: "ns", - Name: "n", - Path: strPtr("https://apis/foo.bar"), - Port: 65536, - }, - }, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: `Invalid value: 65536: port is not valid: must be between 1 and 65535, inclusive`, - }, - { - name: "timeout seconds cannot be greater than 30", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - TimeoutSeconds: int32Ptr(31), + }, true), + expectedError: `webhooks[1].name: Duplicate value: "webhook.k8s.io"`, + }, { + name: "Operations must not be empty or nil", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, - }, true), - expectedError: `webhooks[0].timeoutSeconds: Invalid value: 31: the timeout value must be between 1 and 30 seconds`, + }, { + Operations: nil, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }}, }, - { - name: "timeout seconds cannot be smaller than 1", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - TimeoutSeconds: int32Ptr(0), + }, true), + expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`, + }, { + name: "\"\" is NOT a valid operation", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE", ""}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, - }, true), - expectedError: `webhooks[0].timeoutSeconds: Invalid value: 0: the timeout value must be between 1 and 30 seconds`, + }}, }, - { - name: "timeout seconds must be positive", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - TimeoutSeconds: int32Ptr(-1), + }, true), + expectedError: `Unsupported value: ""`, + }, { + name: "operation must be either create/update/delete/connect", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"PATCH"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, - }, true), - expectedError: `webhooks[0].timeoutSeconds: Invalid value: -1: the timeout value must be between 1 and 30 seconds`, + }}, }, - { - name: "valid timeout seconds", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - TimeoutSeconds: int32Ptr(1), + }, true), + expectedError: `Unsupported value: "PATCH"`, + }, { + name: "wildcard operation cannot be mixed with other strings", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE", "*"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, - { - Name: "webhook2.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - TimeoutSeconds: int32Ptr(15), - }, - { - Name: "webhook3.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - TimeoutSeconds: int32Ptr(30), - }, - }, true), + }}, }, - { - name: "single match condition must have a name", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Expression: "true", - }, + }, true), + expectedError: `if '*' is present, must not specify other operations`, + }, { + name: `resource "*" can co-exist with resources that have subresources`, + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*", "a/b", "a/*", "*/b"}, + }, + }}, + }, + }, true), + }, { + name: `resource "*" cannot mix with resources that don't have subresources`, + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*", "a"}, + }, + }}, + }, + }, true), + expectedError: `if '*' is present, must not specify other resources without subresources`, + }, { + name: "resource a/* cannot mix with a/x", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a/*", "a/x"}, + }, + }}, + }, + }, true), + expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, + }, { + name: "resource a/* can mix with a", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a/*", "a"}, + }, + }}, + }, + }, true), + }, { + name: "resource */a cannot mix with x/a", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/a", "x/a"}, + }, + }}, + }, + }, true), + expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, + }, { + name: "resource */* cannot mix with other resources", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + Rules: []admissionregistration.RuleWithOperations{{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/*", "a"}, + }, + }}, + }, + }, true), + expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, + }, { + name: "FailurePolicy can only be \"Ignore\" or \"Fail\"", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("other") + return &r + }(), + }, + }, true), + expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, + }, { + name: "AdmissionReviewVersions are required", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, false), + expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`, + }, { + name: "SideEffects are required", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: nil, + }, + }, true), + expectedError: `webhooks[0].sideEffects: Required value: must specify one of None, NoneOnDryRun`, + }, { + name: "SideEffects can only be \"None\" or \"NoneOnDryRun\" when created", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: func() *admissionregistration.SideEffectClass { + r := admissionregistration.SideEffectClass("other") + return &r + }(), + }, + }, true), + expectedError: `webhooks[0].sideEffects: Unsupported value: "other": supported values: "None", "NoneOnDryRun"`, + }, { + name: "both service and URL missing", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{}, + }, + }, true), + expectedError: `exactly one of`, + }, { + name: "both service and URL provided", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Port: 443, + }, + URL: strPtr("example.com/k8s/webhook"), + }, + }, + }, true), + expectedError: `[0].clientConfig: Required value: exactly one of url or service is required`, + }, { + name: "blank URL", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr(""), + }, + }, + }, true), + expectedError: `[0].clientConfig.url: Invalid value: "": host must be specified`, + }, { + name: "wrong scheme", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("http://example.com"), + }, + }, + }, true), + expectedError: `https`, + }, { + name: "missing host", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https:///fancy/webhook"), + }, + }, + }, true), + expectedError: `host must be specified`, + }, { + name: "fragment", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https://example.com/#bookmark"), + }, + }, + }, true), + expectedError: `"bookmark": fragments are not permitted`, + }, { + name: "query", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https://example.com?arg=value"), + }, + }, + }, true), + expectedError: `"arg=value": query parameters are not permitted`, + }, { + name: "user", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https://harry.potter@example.com/"), + }, + }, + }, true), + expectedError: `"harry.potter": user information is not permitted`, + }, { + name: "just totally wrong", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"), + }, + }, + }, true), + expectedError: `host must be specified`, + }, { + name: "path must start with slash", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("foo/"), + Port: 443, + }, + }, + }, + }, true), + expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`, + }, { + name: "path accepts slash", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/"), + Port: 443, + }, + }, + SideEffects: &noSideEffect, + }, + }, true), + expectedError: ``, + }, { + name: "path accepts no trailing slash", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo"), + Port: 443, + }, + }, + SideEffects: &noSideEffect, + }, + }, true), + expectedError: ``, + }, { + name: "path fails //", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("//"), + Port: 443, + }, + }, + SideEffects: &noSideEffect, + }, + }, true), + expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`, + }, { + name: "path no empty step", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo//bar/"), + Port: 443, + }, + }, + SideEffects: &unknownSideEffect, + }, + }, true), + expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`, + }, { + name: "path no empty step 2", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo/bar//"), + Port: 443, + }, + }, + SideEffects: &unknownSideEffect, + }, + }, true), + expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`, + }, { + name: "path no non-subdomain", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/apis/foo.bar/v1alpha1/--bad"), + Port: 443, + }, + }, + SideEffects: &unknownSideEffect, + }, + }, true), + expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a lowercase RFC 1123 subdomain`, + }, { + name: "invalid port 0", + config: newMutatingWebhookConfiguration( + []admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("https://apis/foo.bar"), + Port: 0, }, }, + SideEffects: &unknownSideEffect, + }, }, true), - expectedError: `webhooks[0].matchConditions[0].name: Required value`, - }, - { - name: "all match conditions must have a name", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Expression: "true", - }, - { - Expression: "true", - }, - }, - }, - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "", - Expression: "true", - }, + expectedError: `Invalid value: 0: port is not valid: must be between 1 and 65535, inclusive`, + }, { + name: "invalid port >65535", + config: newMutatingWebhookConfiguration( + []admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("https://apis/foo.bar"), + Port: 65536, }, }, + SideEffects: &unknownSideEffect, + }, }, true), - expectedError: `webhooks[0].matchConditions[0].name: Required value, webhooks[0].matchConditions[1].name: Required value, webhooks[1].matchConditions[0].name: Required value`, + expectedError: `Invalid value: 65536: port is not valid: must be between 1 and 65535, inclusive`, + }, { + name: "timeout seconds cannot be greater than 30", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + TimeoutSeconds: int32Ptr(31), }, - { - name: "single match condition must have a qualified name", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "-hello", - Expression: "true", - }, - }, - }, - }, true), - expectedError: `webhooks[0].matchConditions[0].name: Invalid value: "-hello": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`, + }, true), + expectedError: `webhooks[0].timeoutSeconds: Invalid value: 31: the timeout value must be between 1 and 30 seconds`, + }, { + name: "timeout seconds cannot be smaller than 1", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + TimeoutSeconds: int32Ptr(0), }, - { - name: "all match conditions must have qualified names", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: ".io", - Expression: "true", - }, - { - Name: "thing.test.com", - Expression: "true", - }, - }, - }, - { - Name: "webhook2.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "some name", - Expression: "true", - }, - }, - }, - }, true), - expectedError: `[webhooks[0].matchConditions[0].name: Invalid value: ".io": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]'), webhooks[1].matchConditions[0].name: Invalid value: "some name": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')]`, + }, true), + expectedError: `webhooks[0].timeoutSeconds: Invalid value: 0: the timeout value must be between 1 and 30 seconds`, + }, { + name: "timeout seconds must be positive", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + TimeoutSeconds: int32Ptr(-1), }, - { - name: "expression is required", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - }, - }, - }, - }, true), - expectedError: `webhooks[0].matchConditions[0].expression: Required value`, + }, true), + expectedError: `webhooks[0].timeoutSeconds: Invalid value: -1: the timeout value must be between 1 and 30 seconds`, + }, { + name: "valid timeout seconds", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + TimeoutSeconds: int32Ptr(1), + }, { + Name: "webhook2.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + TimeoutSeconds: int32Ptr(15), + }, { + Name: "webhook3.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + TimeoutSeconds: int32Ptr(30), }, - { - name: "expression is required to have some value", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "", - }, - }, - }, - }, true), - expectedError: `webhooks[0].matchConditions[0].expression: Required value`, + }, true), + }, { + name: "single match condition must have a name", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Expression: "true", + }}, }, - { - name: "invalid expression", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "object.x in [1, 2, ", - }, - }, - }, - }, true), - expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "object.x in [1, 2,": compilation failed: ERROR: :1:19: Syntax error: missing ']' at ''`, + }, true), + expectedError: `webhooks[0].matchConditions[0].name: Required value`, + }, { + name: "all match conditions must have a name", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Expression: "true", + }, { + Expression: "true", + }}, + }, { + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "", + Expression: "true", + }}, }, - { - name: "unique names same hook", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "true", - }, - { - Name: "webhook.k8s.io", - Expression: "true", - }, - }, - }, - }, true), - expectedError: `matchConditions[1].name: Duplicate value: "webhook.k8s.io"`, + }, true), + expectedError: `webhooks[0].matchConditions[0].name: Required value, webhooks[0].matchConditions[1].name: Required value, webhooks[1].matchConditions[0].name: Required value`, + }, { + name: "single match condition must have a qualified name", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "-hello", + Expression: "true", + }}, }, - { - name: "repeat names allowed across different hooks", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "true", - }, - }, - }, - { - Name: "webhook2.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "true", - }, - }, - }, - }, true), - expectedError: ``, + }, true), + expectedError: `webhooks[0].matchConditions[0].name: Invalid value: "-hello": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`, + }, { + name: "all match conditions must have qualified names", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: ".io", + Expression: "true", + }, { + Name: "thing.test.com", + Expression: "true", + }}, + }, { + Name: "webhook2.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "some name", + Expression: "true", + }}, }, - { - name: "must evaluate to bool", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "webhook.k8s.io", - Expression: "6", - }, - }, - }, - }, true), - expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "6": must evaluate to bool`, + }, true), + expectedError: `[webhooks[0].matchConditions[0].name: Invalid value: ".io": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]'), webhooks[1].matchConditions[0].name: Invalid value: "some name": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')]`, + }, { + name: "expression is required", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + }}, }, - { - name: "max of 64 match conditions", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - MatchConditions: get65MatchConditions(), - }, - }, true), - expectedError: `webhooks[0].matchConditions: Too many: 65: must have at most 64 items`, + }, true), + expectedError: `webhooks[0].matchConditions[0].expression: Required value`, + }, { + name: "expression is required to have some value", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "", + }}, }, - } + }, true), + expectedError: `webhooks[0].matchConditions[0].expression: Required value`, + }, { + name: "invalid expression", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "object.x in [1, 2, ", + }}, + }, + }, true), + expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "object.x in [1, 2,": compilation failed: ERROR: :1:19: Syntax error: missing ']' at ''`, + }, { + name: "unique names same hook", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "true", + }, { + Name: "webhook.k8s.io", + Expression: "true", + }}, + }, + }, true), + expectedError: `matchConditions[1].name: Duplicate value: "webhook.k8s.io"`, + }, { + name: "repeat names allowed across different hooks", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "true", + }}, + }, { + Name: "webhook2.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "true", + }}, + }, + }, true), + expectedError: ``, + }, { + name: "must evaluate to bool", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "webhook.k8s.io", + Expression: "6", + }}, + }, + }, true), + expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "6": must evaluate to bool`, + }, { + name: "max of 64 match conditions", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + MatchConditions: get65MatchConditions(), + }, + }, true), + expectedError: `webhooks[0].matchConditions: Too many: 65: must have at most 64 items`, + }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { errs := ValidateMutatingWebhookConfiguration(test.config) @@ -2128,169 +1772,142 @@ func TestValidateMutatingWebhookConfigurationUpdate(t *testing.T) { oldconfig *admissionregistration.MutatingWebhookConfiguration config *admissionregistration.MutatingWebhookConfiguration expectedError string - }{ - { - name: "should pass on valid new AdmissionReviewVersion (v1beta1)", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"v1beta1"}, - }, - }, true), - oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: ``, + }{{ + name: "should pass on valid new AdmissionReviewVersion (v1beta1)", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"v1beta1"}, }, - { - name: "should pass on valid new AdmissionReviewVersion (v1)", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"v1"}, - }, - }, true), - oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: ``, + }, true), + oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, }, - { - name: "should pass on invalid AdmissionReviewVersion with invalid previous versions", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"}, - }, - }, true), - oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"invalid-v0"}, - }, - }, true), - expectedError: ``, + }, true), + expectedError: ``, + }, { + name: "should pass on valid new AdmissionReviewVersion (v1)", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"v1"}, }, - { - name: "should fail on invalid AdmissionReviewVersion with valid previous versions", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"invalid-v1"}, - }, - }, true), - oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"v1beta1", "invalid-v1"}, - }, - }, true), - expectedError: `Invalid value: []string{"invalid-v1"}`, + }, true), + oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, }, - { - name: "should fail on invalid AdmissionReviewVersion with missing previous versions", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - AdmissionReviewVersions: []string{"invalid-v1"}, - }, - }, true), - oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, false), - expectedError: `Invalid value: []string{"invalid-v1"}`, + }, true), + expectedError: ``, + }, { + name: "should pass on invalid AdmissionReviewVersion with invalid previous versions", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"}, }, - { - name: "Webhooks can have duplicate names when old config has duplicate names", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: ``, + }, true), + oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"invalid-v0"}, }, - { - name: "Webhooks can't have side effects when old config has no side effects", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &noSideEffect, - }, - }, true), - expectedError: `Unsupported value: "Unknown": supported values: "None", "NoneOnDryRun"`, + }, true), + expectedError: ``, + }, { + name: "should fail on invalid AdmissionReviewVersion with valid previous versions", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"invalid-v1"}, }, - { - name: "Webhooks can have side effects when old config has side effects", - config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{ - { - Name: "webhook.k8s.io", - ClientConfig: validClientConfig, - SideEffects: &unknownSideEffect, - }, - }, true), - expectedError: ``, + }, true), + oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"v1beta1", "invalid-v1"}, }, - } + }, true), + expectedError: `Invalid value: []string{"invalid-v1"}`, + }, { + name: "should fail on invalid AdmissionReviewVersion with missing previous versions", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + AdmissionReviewVersions: []string{"invalid-v1"}, + }, + }, true), + oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, false), + expectedError: `Invalid value: []string{"invalid-v1"}`, + }, { + name: "Webhooks can have duplicate names when old config has duplicate names", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, { + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, true), + oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, { + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, true), + expectedError: ``, + }, { + name: "Webhooks can't have side effects when old config has no side effects", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, true), + oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &noSideEffect, + }, + }, true), + expectedError: `Unsupported value: "Unknown": supported values: "None", "NoneOnDryRun"`, + }, { + name: "Webhooks can have side effects when old config has side effects", + config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, true), + oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{ + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, + SideEffects: &unknownSideEffect, + }, + }, true), + expectedError: ``, + }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { errs := ValidateMutatingWebhookConfigurationUpdate(test.config, test.oldconfig) @@ -2314,999 +1931,845 @@ func TestValidateValidatingAdmissionPolicy(t *testing.T) { name string config *admissionregistration.ValidatingAdmissionPolicy expectedError string - }{ - { - name: "metadata.name validation", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "!!!!", - }, + }{{ + name: "metadata.name validation", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "!!!!", }, - expectedError: `metadata.name: Invalid value: "!!!!":`, }, - { - name: "failure policy validation", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("other") - return &r - }(), - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - }, + expectedError: `metadata.name: Invalid value: "!!!!":`, + }, { + name: "failure policy validation", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("other") + return &r + }(), + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, }, - expectedError: `spec.failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, }, - { - name: "failure policy validation", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("other") - return &r - }(), - }, + expectedError: `spec.failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, + }, { + name: "failure policy validation", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("other") + return &r + }(), }, - expectedError: `spec.failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, }, - { - name: "API version is required in ParamKind", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - ParamKind: &admissionregistration.ParamKind{ - Kind: "Example", - APIVersion: "test.example.com", - }, + expectedError: `spec.failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, + }, { + name: "API version is required in ParamKind", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + ParamKind: &admissionregistration.ParamKind{ + Kind: "Example", + APIVersion: "test.example.com", }, }, - expectedError: `spec.paramKind.apiVersion: Invalid value: "test.example.com"`, }, - { - name: "API kind is required in ParamKind", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - ParamKind: &admissionregistration.ParamKind{ - APIVersion: "test.example.com/v1", - }, + expectedError: `spec.paramKind.apiVersion: Invalid value: "test.example.com"`, + }, { + name: "API kind is required in ParamKind", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + ParamKind: &admissionregistration.ParamKind{ + APIVersion: "test.example.com/v1", }, }, - expectedError: `spec.paramKind.kind: Required value`, }, - { - name: "API version format in ParamKind", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - ParamKind: &admissionregistration.ParamKind{ - Kind: "Example", - APIVersion: "test.example.com/!!!", - }, + expectedError: `spec.paramKind.kind: Required value`, + }, { + name: "API version format in ParamKind", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + ParamKind: &admissionregistration.ParamKind{ + Kind: "Example", + APIVersion: "test.example.com/!!!", }, }, - expectedError: `pec.paramKind.apiVersion: Invalid value: "!!!":`, }, - { - name: "API group format in ParamKind", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - ParamKind: &admissionregistration.ParamKind{ - APIVersion: "!!!/v1", - Kind: "ReplicaLimit", - }, + expectedError: `pec.paramKind.apiVersion: Invalid value: "!!!":`, + }, { + name: "API group format in ParamKind", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + ParamKind: &admissionregistration.ParamKind{ + APIVersion: "!!!/v1", + Kind: "ReplicaLimit", }, }, - expectedError: `pec.paramKind.apiVersion: Invalid value: "!!!":`, }, - { - name: "Validations is required", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{}, + expectedError: `pec.paramKind.apiVersion: Invalid value: "!!!":`, + }, { + name: "Validations is required", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{}, + }, - expectedError: `spec.validations: Required value: validations or auditAnnotations must contain at least one item`, - }, - { - name: "Invalid Validations Reason", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - Reason: func() *metav1.StatusReason { - r := metav1.StatusReason("other") - return &r - }(), - }, - }, - }, + expectedError: `spec.validations: Required value: validations or auditAnnotations must contain at least one item`, + }, { + name: "Invalid Validations Reason", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + Reason: func() *metav1.StatusReason { + r := metav1.StatusReason("other") + return &r + }(), + }}, + }, + }, - expectedError: `spec.validations[0].reason: Unsupported value: "other"`, - }, - { - name: "MatchConstraints is required", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - }, + expectedError: `spec.validations[0].reason: Unsupported value: "other"`, + }, { + name: "MatchConstraints is required", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + }, + }, - expectedError: `spec.matchConstraints: Required value`, + expectedError: `spec.matchConstraints: Required value`, + }, { + name: "matchConstraints.resourceRules is required", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{}, + }, }, - { - name: "matchConstraints.resourceRules is required", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", + expectedError: `spec.matchConstraints.resourceRules: Required value`, + }, { + name: "matchConstraints.resourceRules has at least one explicit rule", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Rule: admissionregistration.Rule{}, }, - }, - MatchConstraints: &admissionregistration.MatchResources{}, + ResourceNames: []string{"/./."}, + }}, }, }, - expectedError: `spec.matchConstraints.resourceRules: Required value`, }, - { - name: "matchConstraints.resourceRules has at least one explicit rule", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Rule: admissionregistration.Rule{}, - }, - ResourceNames: []string{"/./."}, - }, - }, - }, - }, + expectedError: `spec.matchConstraints.resourceRules[0].apiVersions: Required value`, + }, { + name: "expression is required", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{}}, }, - expectedError: `spec.matchConstraints.resourceRules[0].apiVersions: Required value`, }, - { - name: "expression is required", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{{}}, - }, - }, - expectedError: `spec.validations[0].expression: Required value: expression is not specified`, - }, - { - name: "matchResources resourceNames check", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - ResourceNames: []string{"/./."}, - }, - }, - }, + expectedError: `spec.validations[0].expression: Required value: expression is not specified`, + }, { + name: "matchResources resourceNames check", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + ResourceNames: []string{"/./."}, + }}, }, }, - expectedError: `spec.matchConstraints.resourceRules[0].resourceNames[0]: Invalid value: "/./."`, }, - { - name: "matchResources resourceNames cannot duplicate", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - ResourceNames: []string{"test", "test"}, - }, - }, - }, + expectedError: `spec.matchConstraints.resourceRules[0].resourceNames[0]: Invalid value: "/./."`, + }, { + name: "matchResources resourceNames cannot duplicate", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + ResourceNames: []string{"test", "test"}, + }}, }, }, - expectedError: `spec.matchConstraints.resourceRules[0].resourceNames[1]: Duplicate value: "test"`, }, - { - name: "matchResources validation: matchPolicy", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("other") - return &r - }(), - }, - }, + expectedError: `spec.matchConstraints.resourceRules[0].resourceNames[1]: Duplicate value: "test"`, + }, { + name: "matchResources validation: matchPolicy", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", }, - expectedError: `spec.matchConstraints.matchPolicy: Unsupported value: "other": supported values: "Equivalent", "Exact"`, - }, - { - name: "Operations must not be empty or nil", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Fail") + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("other") return &r }(), - MatchConstraints: &admissionregistration.MatchResources{ - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: nil, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - ExcludeResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: nil, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, }, }, - expectedError: `spec.matchConstraints.resourceRules[0].operations: Required value, spec.matchConstraints.resourceRules[1].operations: Required value, spec.matchConstraints.excludeResourceRules[0].operations: Required value, spec.matchConstraints.excludeResourceRules[1].operations: Required value`, }, - { - name: "\"\" is NOT a valid operation", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE", ""}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, - }, + expectedError: `spec.matchConstraints.matchPolicy: Unsupported value: "other": supported values: "Equivalent", "Exact"`, + }, { + name: "Operations must not be empty or nil", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", }, - expectedError: `Unsupported value: ""`, - }, - { - name: "operation must be either create/update/delete/connect", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Fail") + return &r + }(), + MatchConstraints: &admissionregistration.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"PATCH"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, - }, - }, - expectedError: `Unsupported value: "PATCH"`, - }, - { - name: "wildcard operation cannot be mixed with other strings", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE", "*"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, - }, - }, - expectedError: `if '*' is present, must not specify other operations`, - }, - { - name: `resource "*" can co-exist with resources that have subresources`, - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Fail") + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") return &r }(), - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*", "a/b", "a/*", "*/b"}, - }, - }, + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, + }, { + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: nil, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, + }}, + ExcludeResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, }, - }, + }, { + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: nil, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }, + }}, }, }, }, - { - name: `resource "*" cannot mix with resources that don't have subresources`, - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*", "a"}, - }, - }, + expectedError: `spec.matchConstraints.resourceRules[0].operations: Required value, spec.matchConstraints.resourceRules[1].operations: Required value, spec.matchConstraints.excludeResourceRules[0].operations: Required value, spec.matchConstraints.excludeResourceRules[1].operations: Required value`, + }, { + name: "\"\" is NOT a valid operation", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE", ""}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, + }}, }, }, - expectedError: `if '*' is present, must not specify other resources without subresources`, }, - { - name: "resource a/* cannot mix with a/x", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a/*", "a/x"}, - }, - }, + expectedError: `Unsupported value: ""`, + }, { + name: "operation must be either create/update/delete/connect", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"PATCH"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, + }}, }, }, - expectedError: `spec.matchConstraints.resourceRules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, }, - { - name: "resource a/* can mix with a", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + expectedError: `Unsupported value: "PATCH"`, + }, { + name: "wildcard operation cannot be mixed with other strings", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE", "*"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }, + }}, }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Fail") + }, + }, + expectedError: `if '*' is present, must not specify other operations`, + }, { + name: `resource "*" can co-exist with resources that have subresources`, + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Fail") + return &r + }(), + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") return &r }(), - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a/*", "a"}, - }, - }, + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*", "a/b", "a/*", "*/b"}, }, }, + }}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, }, }, }, - { - name: "resource */a cannot mix with x/a", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/a", "x/a"}, - }, - }, + }, { + name: `resource "*" cannot mix with resources that don't have subresources`, + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*", "a"}, }, }, - }, + }}, }, }, - expectedError: `spec.matchConstraints.resourceRules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, }, - { - name: "resource */* cannot mix with other resources", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/*", "a"}, - }, - }, + expectedError: `if '*' is present, must not specify other resources without subresources`, + }, { + name: "resource a/* cannot mix with a/x", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a/*", "a/x"}, }, }, - }, + }}, }, }, - expectedError: `spec.matchConstraints.resourceRules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, }, - { - name: "invalid expression", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x in [1, 2, ", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/*"}, - }, - }, - }, - }, - }, - }, + expectedError: `spec.matchConstraints.resourceRules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, + }, { + name: "resource a/* can mix with a", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", }, - expectedError: `spec.validations[0].expression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: :1:20: Syntax error: missing ']' at '`, - }, - { - name: "invalid messageExpression", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "true", - MessageExpression: "object.x in [1, 2, ", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/*"}, - }, - }, - }, - }, - }, - }, - }, - expectedError: `spec.validations[0].messageExpression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: :1:20: Syntax error: missing ']' at '`, - }, - { - name: "messageExpression of wrong type", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "true", - MessageExpression: "0 == 0", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/*"}, - }, - }, - }, - }, - }, - }, - }, - expectedError: `spec.validations[0].messageExpression: Invalid value: "0 == 0": must evaluate to string`, - }, - { - name: "invalid auditAnnotations key due to key name", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - AuditAnnotations: []admissionregistration.AuditAnnotation{ - { - Key: "@", - ValueExpression: "value", - }, - }, - }, - }, - expectedError: `spec.auditAnnotations[0].key: Invalid value: "config/@": name part must consist of alphanumeric characters`, - }, - { - name: "auditAnnotations keys must be unique", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - AuditAnnotations: []admissionregistration.AuditAnnotation{ - { - Key: "a", - ValueExpression: "'1'", - }, - { - Key: "a", - ValueExpression: "'2'", - }, - }, - }, - }, - expectedError: `spec.auditAnnotations[1].key: Duplicate value: "a"`, - }, - { - name: "invalid auditAnnotations key due to metadata.name", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nope!", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - AuditAnnotations: []admissionregistration.AuditAnnotation{ - { - Key: "key", - ValueExpression: "'value'", - }, - }, - }, - }, - expectedError: `spec.auditAnnotations[0].key: Invalid value: "nope!/key": prefix part a lowercase RFC 1123 subdomain`, - }, - { - name: "invalid auditAnnotations key due to length", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "this-is-a-long-name-for-a-admission-policy-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - AuditAnnotations: []admissionregistration.AuditAnnotation{ - { - Key: "this-is-a-long-name-for-an-audit-annotation-key-xxxxxxxxxxxxxxxxxxxxxxxxxx", - ValueExpression: "'value'", - }, - }, - }, - }, - expectedError: `spec.auditAnnotations[0].key: Invalid value`, - }, - { - name: "invalid auditAnnotations valueExpression type", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - AuditAnnotations: []admissionregistration.AuditAnnotation{ - { - Key: "something", - ValueExpression: "true", - }, - }, - }, - }, - expectedError: `spec.auditAnnotations[0].valueExpression: Invalid value: "true": must evaluate to one of [string null_type]`, - }, - { - name: "invalid auditAnnotations valueExpression", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - AuditAnnotations: []admissionregistration.AuditAnnotation{ - { - Key: "something", - ValueExpression: "object.x in [1, 2, ", - }, - }, - }, - }, - expectedError: `spec.auditAnnotations[0].valueExpression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: :1:19: Syntax error: missing ']' at '`, - }, - { - name: "single match condition must have a name", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - MatchConditions: []admissionregistration.MatchCondition{ - { - Expression: "true", - }, - }, - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - }, - }, - expectedError: `spec.matchConditions[0].name: Required value`, - }, - { - name: "match condition with parameters allowed", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - ParamKind: &admissionregistration.ParamKind{ - Kind: "Foo", - APIVersion: "foobar/v1alpha1", - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"*"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - }, - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Fail") + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Fail") + return &r + }(), + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") return &r }(), - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "hasParams", - Expression: `params.foo == "okay"`, - }, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, - }, - }, - expectedError: "", - }, - { - name: "match condition with parameters not allowed if no param kind", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"*"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a/*", "a"}, }, }, - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - }, - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Fail") - return &r - }(), - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "hasParams", - Expression: `params.foo == "okay"`, - }, - }, - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, + }}, }, }, - expectedError: `undeclared reference to 'params'`, }, - } + }, { + name: "resource */a cannot mix with x/a", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/a", "x/a"}, + }, + }, + }}, + }, + }, + }, + expectedError: `spec.matchConstraints.resourceRules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, + }, { + name: "resource */* cannot mix with other resources", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/*", "a"}, + }, + }, + }}, + }, + }, + }, + expectedError: `spec.matchConstraints.resourceRules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, + }, { + name: "invalid expression", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x in [1, 2, ", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/*"}, + }, + }, + }}, + }, + }, + }, + expectedError: `spec.validations[0].expression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: :1:20: Syntax error: missing ']' at '`, + }, { + name: "invalid messageExpression", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "true", + MessageExpression: "object.x in [1, 2, ", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/*"}, + }, + }, + }}, + }, + }, + }, + expectedError: `spec.validations[0].messageExpression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: :1:20: Syntax error: missing ']' at '`, + }, { + name: "messageExpression of wrong type", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "true", + MessageExpression: "0 == 0", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/*"}, + }, + }, + }}, + }, + }, + }, + expectedError: `spec.validations[0].messageExpression: Invalid value: "0 == 0": must evaluate to string`, + }, { + name: "invalid auditAnnotations key due to key name", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + AuditAnnotations: []admissionregistration.AuditAnnotation{{ + Key: "@", + ValueExpression: "value", + }}, + }, + }, + expectedError: `spec.auditAnnotations[0].key: Invalid value: "config/@": name part must consist of alphanumeric characters`, + }, { + name: "auditAnnotations keys must be unique", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + AuditAnnotations: []admissionregistration.AuditAnnotation{{ + Key: "a", + ValueExpression: "'1'", + }, { + Key: "a", + ValueExpression: "'2'", + }}, + }, + }, + expectedError: `spec.auditAnnotations[1].key: Duplicate value: "a"`, + }, { + name: "invalid auditAnnotations key due to metadata.name", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nope!", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + AuditAnnotations: []admissionregistration.AuditAnnotation{{ + Key: "key", + ValueExpression: "'value'", + }}, + }, + }, + expectedError: `spec.auditAnnotations[0].key: Invalid value: "nope!/key": prefix part a lowercase RFC 1123 subdomain`, + }, { + name: "invalid auditAnnotations key due to length", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "this-is-a-long-name-for-a-admission-policy-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + AuditAnnotations: []admissionregistration.AuditAnnotation{{ + Key: "this-is-a-long-name-for-an-audit-annotation-key-xxxxxxxxxxxxxxxxxxxxxxxxxx", + ValueExpression: "'value'", + }}, + }, + }, + expectedError: `spec.auditAnnotations[0].key: Invalid value`, + }, { + name: "invalid auditAnnotations valueExpression type", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + AuditAnnotations: []admissionregistration.AuditAnnotation{{ + Key: "something", + ValueExpression: "true", + }}, + }, + }, + expectedError: `spec.auditAnnotations[0].valueExpression: Invalid value: "true": must evaluate to one of [string null_type]`, + }, { + name: "invalid auditAnnotations valueExpression", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + AuditAnnotations: []admissionregistration.AuditAnnotation{{ + Key: "something", + ValueExpression: "object.x in [1, 2, ", + }}, + }, + }, + expectedError: `spec.auditAnnotations[0].valueExpression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: :1:19: Syntax error: missing ']' at '`, + }, { + name: "single match condition must have a name", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + MatchConditions: []admissionregistration.MatchCondition{{ + Expression: "true", + }}, + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + }, + }, + expectedError: `spec.matchConditions[0].name: Required value`, + }, { + name: "match condition with parameters allowed", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + ParamKind: &admissionregistration.ParamKind{ + Kind: "Foo", + APIVersion: "foobar/v1alpha1", + }, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"*"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }, + }}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + }, + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Fail") + return &r + }(), + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "hasParams", + Expression: `params.foo == "okay"`, + }}, + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + }, + }, + expectedError: "", + }, { + name: "match condition with parameters not allowed if no param kind", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"*"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }, + }}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + }, + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Fail") + return &r + }(), + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "hasParams", + Expression: `params.foo == "okay"`, + }}, + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + }, + }, + expectedError: `undeclared reference to 'params'`, + }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { errs := ValidateValidatingAdmissionPolicy(test.config) @@ -3330,322 +2793,282 @@ func TestValidateValidatingAdmissionPolicyUpdate(t *testing.T) { oldconfig *admissionregistration.ValidatingAdmissionPolicy config *admissionregistration.ValidatingAdmissionPolicy expectedError string - }{ - { - name: "should pass on valid new ValidatingAdmissionPolicy", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Fail") + }{{ + name: "should pass on valid new ValidatingAdmissionPolicy", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Fail") + return &r + }(), + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") return &r }(), - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, - }, - }, - oldconfig: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, + }}, }, }, }, - { - name: "should pass on valid new ValidatingAdmissionPolicy with invalid old ValidatingAdmissionPolicy", - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Fail") - return &r - }(), - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - MatchConstraints: &admissionregistration.MatchResources{ - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + oldconfig: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, + }}, }, }, - oldconfig: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "!!!", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{}, - }, }, - { - name: "match conditions re-checked if paramKind changes", - oldconfig: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - ParamKind: &admissionregistration.ParamKind{ - Kind: "Foo", - APIVersion: "foobar/v1alpha1", + }, { + name: "should pass on valid new ValidatingAdmissionPolicy with invalid old ValidatingAdmissionPolicy", + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Fail") + return &r + }(), + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + MatchConstraints: &admissionregistration.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"*"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Fail") + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") return &r }(), - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "hasParams", - Expression: `params.foo == "okay"`, - }, - }, - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - }, - }, - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"*"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - }, - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Fail") - return &r - }(), - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "hasParams", - Expression: `params.foo == "okay"`, - }, - }, - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, + }}, }, }, - expectedError: `undeclared reference to 'params'`, }, - { - name: "match conditions not re-checked if no change to paramKind or matchConditions", - oldconfig: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"*"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - }, - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Fail") - return &r - }(), - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "hasParams", - Expression: `params.foo == "okay"`, - }, - }, - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 100", - }, - }, - }, + oldconfig: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "!!!", }, - config: &admissionregistration.ValidatingAdmissionPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicySpec{ - MatchConstraints: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"*"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - }, - FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.FailurePolicyType("Ignore") - return &r - }(), - MatchConditions: []admissionregistration.MatchCondition{ - { - Name: "hasParams", - Expression: `params.foo == "okay"`, - }, - }, - Validations: []admissionregistration.Validation{ - { - Expression: "object.x < 50", - }, - }, - }, - }, - expectedError: "", + Spec: admissionregistration.ValidatingAdmissionPolicySpec{}, }, - // TODO: CustomAuditAnnotations: string valueExpression with {oldObject} is allowed + }, { + name: "match conditions re-checked if paramKind changes", + oldconfig: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + ParamKind: &admissionregistration.ParamKind{ + Kind: "Foo", + APIVersion: "foobar/v1alpha1", + }, + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"*"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }, + }}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + }, + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Fail") + return &r + }(), + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "hasParams", + Expression: `params.foo == "okay"`, + }}, + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + }, + }, + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"*"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }, + }}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + }, + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Fail") + return &r + }(), + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "hasParams", + Expression: `params.foo == "okay"`, + }}, + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + }, + }, + expectedError: `undeclared reference to 'params'`, + }, { + name: "match conditions not re-checked if no change to paramKind or matchConditions", + oldconfig: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"*"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }, + }}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + }, + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Fail") + return &r + }(), + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "hasParams", + Expression: `params.foo == "okay"`, + }}, + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 100", + }}, + }, + }, + config: &admissionregistration.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicySpec{ + MatchConstraints: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"*"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }, + }}, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + }, + FailurePolicy: func() *admissionregistration.FailurePolicyType { + r := admissionregistration.FailurePolicyType("Ignore") + return &r + }(), + MatchConditions: []admissionregistration.MatchCondition{{ + Name: "hasParams", + Expression: `params.foo == "okay"`, + }}, + Validations: []admissionregistration.Validation{{ + Expression: "object.x < 50", + }}, + }, + }, + expectedError: "", + }, + // TODO: CustomAuditAnnotations: string valueExpression with {oldObject} is allowed } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -3670,425 +3093,385 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) { name string config *admissionregistration.ValidatingAdmissionPolicyBinding expectedError string - }{ - { - name: "metadata.name validation", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "!!!!", + }{{ + name: "metadata.name validation", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "!!!!", + }, + }, + expectedError: `metadata.name: Invalid value: "!!!!":`, + }, { + name: "PolicyName is required", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{}, + }, + expectedError: `spec.policyName: Required value`, + }, { + name: "matchResources validation: matchPolicy", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", + }, + MatchResources: &admissionregistration.MatchResources{ + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("other") + return &r + }(), }, }, - expectedError: `metadata.name: Invalid value: "!!!!":`, }, - { - name: "PolicyName is required", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{}, + expectedError: `spec.matchResouces.matchPolicy: Unsupported value: "other": supported values: "Equivalent", "Exact"`, + }, { + name: "Operations must not be empty or nil", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", }, - expectedError: `spec.policyName: Required value`, - }, - { - name: "matchResources validation: matchPolicy", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, - MatchResources: &admissionregistration.MatchResources{ - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("other") - return &r - }(), - }, - }, - }, - expectedError: `spec.matchResouces.matchPolicy: Unsupported value: "other": supported values: "Equivalent", "Exact"`, - }, - { - name: "Operations must not be empty or nil", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, - MatchResources: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: nil, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + MatchResources: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - ExcludeResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: nil, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + }, { + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: nil, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, - }, - }, - expectedError: `spec.matchResouces.resourceRules[0].operations: Required value, spec.matchResouces.resourceRules[1].operations: Required value, spec.matchResouces.excludeResourceRules[0].operations: Required value, spec.matchResouces.excludeResourceRules[1].operations: Required value`, - }, - { - name: "\"\" is NOT a valid operation", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, MatchResources: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE", ""}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + }}, + ExcludeResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, - }, - }, - expectedError: `Unsupported value: ""`, - }, - { - name: "operation must be either create/update/delete/connect", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, MatchResources: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"PATCH"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + }, { + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: nil, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, + }}, }, }, - expectedError: `Unsupported value: "PATCH"`, }, - { - name: "wildcard operation cannot be mixed with other strings", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, - MatchResources: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE", "*"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + expectedError: `spec.matchResouces.resourceRules[0].operations: Required value, spec.matchResouces.resourceRules[1].operations: Required value, spec.matchResouces.excludeResourceRules[0].operations: Required value, spec.matchResouces.excludeResourceRules[1].operations: Required value`, + }, { + name: "\"\" is NOT a valid operation", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", + }, MatchResources: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE", ""}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, + }}, }, }, - expectedError: `if '*' is present, must not specify other operations`, }, - { - name: `resource "*" can co-exist with resources that have subresources`, - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, - MatchResources: &admissionregistration.MatchResources{ - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*", "a/b", "a/*", "*/b"}, - }, - }, + expectedError: `Unsupported value: ""`, + }, { + name: "operation must be either create/update/delete/connect", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", + }, MatchResources: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"PATCH"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, + }}, }, }, }, - { - name: `resource "*" cannot mix with resources that don't have subresources`, - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + expectedError: `Unsupported value: "PATCH"`, + }, { + name: "wildcard operation cannot be mixed with other strings", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, - MatchResources: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*", "a"}, - }, - }, + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + MatchResources: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE", "*"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, + }}, }, }, - expectedError: `if '*' is present, must not specify other resources without subresources`, }, - { - name: "resource a/* cannot mix with a/x", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + expectedError: `if '*' is present, must not specify other operations`, + }, { + name: `resource "*" can co-exist with resources that have subresources`, + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + MatchResources: &admissionregistration.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, - MatchResources: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a/*", "a/x"}, - }, - }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*", "a/b", "a/*", "*/b"}, }, }, - }, + }}, }, }, - expectedError: `spec.matchResouces.resourceRules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, }, - { - name: "resource a/* can mix with a", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + }, { + name: `resource "*" cannot mix with resources that don't have subresources`, + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, - MatchResources: &admissionregistration.MatchResources{ - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a/*", "a"}, - }, - }, + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + MatchResources: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*", "a"}, }, }, - }, + }}, }, }, }, - { - name: "resource */a cannot mix with x/a", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + expectedError: `if '*' is present, must not specify other resources without subresources`, + }, { + name: "resource a/* cannot mix with a/x", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, - MatchResources: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/a", "x/a"}, - }, - }, + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + MatchResources: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a/*", "a/x"}, }, }, - }, + }}, }, }, - expectedError: `spec.matchResouces.resourceRules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, }, - { - name: "resource */* cannot mix with other resources", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + expectedError: `spec.matchResouces.resourceRules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, + }, { + name: "resource a/* can mix with a", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + MatchResources: &admissionregistration.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, - MatchResources: &admissionregistration.MatchResources{ - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"*/*", "a"}, - }, - }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a/*", "a"}, }, }, - }, + }}, }, }, - expectedError: `spec.matchResouces.resourceRules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, }, - { - name: "validationActions must be unique", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + }, { + name: "resource */a cannot mix with x/a", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny, admissionregistration.Deny}, + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + MatchResources: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/a", "x/a"}, + }, + }, + }}, }, }, - expectedError: `spec.validationActions[1]: Duplicate value: "Deny"`, }, - { - name: "validationActions must contain supported values", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + expectedError: `spec.matchResouces.resourceRules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, + }, { + name: "resource */* cannot mix with other resources", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.ValidationAction("illegal")}, + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + MatchResources: &admissionregistration.MatchResources{ + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"*/*", "a"}, + }, + }, + }}, }, }, - expectedError: `Unsupported value: "illegal": supported values: "Audit", "Deny", "Warn"`, }, - } + expectedError: `spec.matchResouces.resourceRules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, + }, { + name: "validationActions must be unique", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", + }, + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny, admissionregistration.Deny}, + }, + }, + expectedError: `spec.validationActions[1]: Duplicate value: "Deny"`, + }, { + name: "validationActions must contain supported values", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", + }, + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.ValidationAction("illegal")}, + }, + }, + expectedError: `Unsupported value: "illegal": supported values: "Audit", "Deny", "Warn"`, + }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { errs := ValidateValidatingAdmissionPolicyBinding(test.config) @@ -4113,128 +3496,119 @@ func TestValidateValidatingAdmissionPolicyBindingUpdate(t *testing.T) { oldconfig *admissionregistration.ValidatingAdmissionPolicyBinding config *admissionregistration.ValidatingAdmissionPolicyBinding expectedError string - }{ - { - name: "should pass on valid new ValidatingAdmissionPolicyBinding", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", - }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", - }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, - MatchResources: &admissionregistration.MatchResources{ - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, - }, - }, - }, - }, + }{{ + name: "should pass on valid new ValidatingAdmissionPolicyBinding", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", }, - oldconfig: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + MatchResources: &admissionregistration.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, - MatchResources: &admissionregistration.MatchResources{ - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, + }}, }, }, }, - { - name: "should pass on valid new ValidatingAdmissionPolicyBinding with invalid old ValidatingAdmissionPolicyBinding", - config: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "config", + oldconfig: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ - PolicyName: "xyzlimit-scale.example.com", - ParamRef: &admissionregistration.ParamRef{ - Name: "xyzlimit-scale-setting.example.com", + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + MatchResources: &admissionregistration.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, }, - ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, - MatchResources: &admissionregistration.MatchResources{ - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - ObjectSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - }, - MatchPolicy: func() *admissionregistration.MatchPolicyType { - r := admissionregistration.MatchPolicyType("Exact") - return &r - }(), - ResourceRules: []admissionregistration.NamedRuleWithOperations{ - { - RuleWithOperations: admissionregistration.RuleWithOperations{ - Operations: []admissionregistration.OperationType{"CREATE"}, - Rule: admissionregistration.Rule{ - APIGroups: []string{"a"}, - APIVersions: []string{"a"}, - Resources: []string{"a"}, - }, - }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, }, }, - }, + }}, }, }, - oldconfig: &admissionregistration.ValidatingAdmissionPolicyBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "!!!", - }, - Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{}, - }, }, - } + }, { + name: "should pass on valid new ValidatingAdmissionPolicyBinding with invalid old ValidatingAdmissionPolicyBinding", + config: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: "xyzlimit-scale.example.com", + ParamRef: &admissionregistration.ParamRef{ + Name: "xyzlimit-scale-setting.example.com", + }, + ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny}, + MatchResources: &admissionregistration.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + MatchPolicy: func() *admissionregistration.MatchPolicyType { + r := admissionregistration.MatchPolicyType("Exact") + return &r + }(), + ResourceRules: []admissionregistration.NamedRuleWithOperations{{ + RuleWithOperations: admissionregistration.RuleWithOperations{ + Operations: []admissionregistration.OperationType{"CREATE"}, + Rule: admissionregistration.Rule{ + APIGroups: []string{"a"}, + APIVersions: []string{"a"}, + Resources: []string{"a"}, + }, + }, + }}, + }, + }, + }, + oldconfig: &admissionregistration.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "!!!", + }, + Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{}, + }, + }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { errs := ValidateValidatingAdmissionPolicyBindingUpdate(test.config, test.oldconfig) @@ -4258,64 +3632,51 @@ func TestValidateValidatingAdmissionPolicyStatus(t *testing.T) { name string status *admissionregistration.ValidatingAdmissionPolicyStatus expectedError string - }{ - { - name: "empty", - status: &admissionregistration.ValidatingAdmissionPolicyStatus{}, - }, - { - name: "type checking", - status: &admissionregistration.ValidatingAdmissionPolicyStatus{ - TypeChecking: &admissionregistration.TypeChecking{ - ExpressionWarnings: []admissionregistration.ExpressionWarning{ - { - FieldRef: "spec.validations[0].expression", - Warning: "message", - }, - }, - }, + }{{ + name: "empty", + status: &admissionregistration.ValidatingAdmissionPolicyStatus{}, + }, { + name: "type checking", + status: &admissionregistration.ValidatingAdmissionPolicyStatus{ + TypeChecking: &admissionregistration.TypeChecking{ + ExpressionWarnings: []admissionregistration.ExpressionWarning{{ + FieldRef: "spec.validations[0].expression", + Warning: "message", + }}, }, }, - { - name: "type checking bad json path", - status: &admissionregistration.ValidatingAdmissionPolicyStatus{ - TypeChecking: &admissionregistration.TypeChecking{ - ExpressionWarnings: []admissionregistration.ExpressionWarning{ - { - FieldRef: "spec[foo]", - Warning: "message", - }, - }, - }, + }, { + name: "type checking bad json path", + status: &admissionregistration.ValidatingAdmissionPolicyStatus{ + TypeChecking: &admissionregistration.TypeChecking{ + ExpressionWarnings: []admissionregistration.ExpressionWarning{{ + FieldRef: "spec[foo]", + Warning: "message", + }}, }, - expectedError: "invalid JSONPath: invalid array index foo", }, - { - name: "type checking missing warning", - status: &admissionregistration.ValidatingAdmissionPolicyStatus{ - TypeChecking: &admissionregistration.TypeChecking{ - ExpressionWarnings: []admissionregistration.ExpressionWarning{ - { - FieldRef: "spec.validations[0].expression", - }, - }, - }, + expectedError: "invalid JSONPath: invalid array index foo", + }, { + name: "type checking missing warning", + status: &admissionregistration.ValidatingAdmissionPolicyStatus{ + TypeChecking: &admissionregistration.TypeChecking{ + ExpressionWarnings: []admissionregistration.ExpressionWarning{{ + FieldRef: "spec.validations[0].expression", + }}, }, - expectedError: "Required value", }, - { - name: "type checking missing fieldRef", - status: &admissionregistration.ValidatingAdmissionPolicyStatus{ - TypeChecking: &admissionregistration.TypeChecking{ - ExpressionWarnings: []admissionregistration.ExpressionWarning{ - { - Warning: "message", - }, - }, - }, + expectedError: "Required value", + }, { + name: "type checking missing fieldRef", + status: &admissionregistration.ValidatingAdmissionPolicyStatus{ + TypeChecking: &admissionregistration.TypeChecking{ + ExpressionWarnings: []admissionregistration.ExpressionWarning{{ + Warning: "message", + }}, }, - expectedError: "Required value", }, + expectedError: "Required value", + }, } { t.Run(tc.name, func(t *testing.T) { errs := validateValidatingAdmissionPolicyStatus(tc.status, field.NewPath("status")) diff --git a/pkg/apis/apiserverinternal/validation/validation_test.go b/pkg/apis/apiserverinternal/validation/validation_test.go index 0d0b3bae893..a2bbae659d2 100644 --- a/pkg/apis/apiserverinternal/validation/validation_test.go +++ b/pkg/apis/apiserverinternal/validation/validation_test.go @@ -28,96 +28,84 @@ func TestValidateServerStorageVersion(t *testing.T) { cases := []struct { ssv apiserverinternal.ServerStorageVersion expectedErr string - }{ - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "-fea", - EncodingVersion: "v1alpha1", - DecodableVersions: []string{"v1alpha1", "v1"}, - }, - expectedErr: "apiServerID: Invalid value", + }{{ + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "-fea", + EncodingVersion: "v1alpha1", + DecodableVersions: []string{"v1alpha1", "v1"}, }, - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "fea", - EncodingVersion: "v1alpha1", - DecodableVersions: []string{"v1beta1", "v1"}, - }, - expectedErr: "decodableVersions must include encodingVersion", + expectedErr: "apiServerID: Invalid value", + }, { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "v1alpha1", + DecodableVersions: []string{"v1beta1", "v1"}, }, - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "fea", - EncodingVersion: "v1alpha1", - DecodableVersions: []string{"v1alpha1", "v1", "-fea"}, - }, - expectedErr: "decodableVersions[2]: Invalid value", + expectedErr: "decodableVersions must include encodingVersion", + }, { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "v1alpha1", + DecodableVersions: []string{"v1alpha1", "v1", "-fea"}, }, - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "fea", - EncodingVersion: "v1alpha1", - DecodableVersions: []string{"v1alpha1", "v1"}, - }, - expectedErr: "", + expectedErr: "decodableVersions[2]: Invalid value", + }, { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "v1alpha1", + DecodableVersions: []string{"v1alpha1", "v1"}, }, - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "fea", - EncodingVersion: "mygroup.com/v2", - DecodableVersions: []string{"v1alpha1", "v1", "mygroup.com/v2"}, - }, - expectedErr: "", + expectedErr: "", + }, { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "mygroup.com/v2", + DecodableVersions: []string{"v1alpha1", "v1", "mygroup.com/v2"}, }, - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "fea", - EncodingVersion: "mygroup.com/v2", - DecodableVersions: []string{"mygroup.com/v2", "/v3"}, - }, - expectedErr: `[].decodableVersions[1]: Invalid value: "/v3": group part: must be non-empty`, + expectedErr: "", + }, { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "mygroup.com/v2", + DecodableVersions: []string{"mygroup.com/v2", "/v3"}, }, - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "fea", - EncodingVersion: "mygroup.com/v2", - DecodableVersions: []string{"mygroup.com/v2", "mygroup.com/"}, - }, - expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/": version part: must be non-empty`, + expectedErr: `[].decodableVersions[1]: Invalid value: "/v3": group part: must be non-empty`, + }, { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "mygroup.com/v2", + DecodableVersions: []string{"mygroup.com/v2", "mygroup.com/"}, }, - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "fea", - EncodingVersion: "/v3", - DecodableVersions: []string{"mygroup.com/v2", "/v3"}, - }, - expectedErr: `[].encodingVersion: Invalid value: "/v3": group part: must be non-empty`, + expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/": version part: must be non-empty`, + }, { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "/v3", + DecodableVersions: []string{"mygroup.com/v2", "/v3"}, }, - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "fea", - EncodingVersion: "v1", - DecodableVersions: []string{"v1", "mygroup_com/v2"}, - }, - expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup_com/v2": group part: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`, + expectedErr: `[].encodingVersion: Invalid value: "/v3": group part: must be non-empty`, + }, { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "v1", + DecodableVersions: []string{"v1", "mygroup_com/v2"}, }, - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "fea", - EncodingVersion: "v1", - DecodableVersions: []string{"v1", "mygroup.com/v2_"}, - }, - expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/v2_": version part: a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')`, + expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup_com/v2": group part: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`, + }, { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "v1", + DecodableVersions: []string{"v1", "mygroup.com/v2_"}, }, - { - ssv: apiserverinternal.ServerStorageVersion{ - APIServerID: "fea", - EncodingVersion: "v1", - DecodableVersions: []string{"v1", "mygroup.com/v2/myresource"}, - }, - expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/v2/myresource": an apiVersion is a DNS-1035 label, which must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyVersion')`, + expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/v2_": version part: a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')`, + }, { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "v1", + DecodableVersions: []string{"v1", "mygroup.com/v2/myresource"}, }, - } + expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/v2/myresource": an apiVersion is a DNS-1035 label, which must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyVersion')`, + }} for _, tc := range cases { err := validateServerStorageVersion(tc.ssv, field.NewPath("")).ToAggregate() @@ -142,91 +130,70 @@ func TestValidateCommonVersion(t *testing.T) { cases := []struct { status apiserverinternal.StorageVersionStatus expectedErr string - }{ - { - status: apiserverinternal.StorageVersionStatus{ - StorageVersions: []apiserverinternal.ServerStorageVersion{}, - CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), - }, - expectedErr: "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet", + }{{ + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{}, + CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), }, - { - status: apiserverinternal.StorageVersionStatus{ - StorageVersions: []apiserverinternal.ServerStorageVersion{ - { - APIServerID: "1", - EncodingVersion: "v1alpha1", - }, - { - APIServerID: "2", - EncodingVersion: "v1", - }, - }, - CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), - }, - expectedErr: "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet", + expectedErr: "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet", + }, { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{{ + APIServerID: "1", + EncodingVersion: "v1alpha1", + }, { + APIServerID: "2", + EncodingVersion: "v1", + }}, + CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), }, - { - status: apiserverinternal.StorageVersionStatus{ - StorageVersions: []apiserverinternal.ServerStorageVersion{ - { - APIServerID: "1", - EncodingVersion: "v1alpha1", - }, - { - APIServerID: "2", - EncodingVersion: "v1alpha1", - }, - }, - CommonEncodingVersion: nil, - }, - expectedErr: "Invalid value: \"null\": the common encoding version is v1alpha1", + expectedErr: "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet", + }, { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{{ + APIServerID: "1", + EncodingVersion: "v1alpha1", + }, { + APIServerID: "2", + EncodingVersion: "v1alpha1", + }}, + CommonEncodingVersion: nil, }, - { - status: apiserverinternal.StorageVersionStatus{ - StorageVersions: []apiserverinternal.ServerStorageVersion{ - { - APIServerID: "1", - EncodingVersion: "v1alpha1", - }, - { - APIServerID: "2", - EncodingVersion: "v1alpha1", - }, - }, - CommonEncodingVersion: func() *string { a := "v1"; return &a }(), - }, - expectedErr: "Invalid value: \"v1\": the actual common encoding version is v1alpha1", + expectedErr: "Invalid value: \"null\": the common encoding version is v1alpha1", + }, { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{{ + APIServerID: "1", + EncodingVersion: "v1alpha1", + }, { + APIServerID: "2", + EncodingVersion: "v1alpha1", + }}, + CommonEncodingVersion: func() *string { a := "v1"; return &a }(), }, - { - status: apiserverinternal.StorageVersionStatus{ - StorageVersions: []apiserverinternal.ServerStorageVersion{ - { - APIServerID: "1", - EncodingVersion: "v1alpha1", - }, - { - APIServerID: "2", - EncodingVersion: "v1alpha1", - }, - }, - CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), - }, - expectedErr: "", + expectedErr: "Invalid value: \"v1\": the actual common encoding version is v1alpha1", + }, { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{{ + APIServerID: "1", + EncodingVersion: "v1alpha1", + }, { + APIServerID: "2", + EncodingVersion: "v1alpha1", + }}, + CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), }, - { - status: apiserverinternal.StorageVersionStatus{ - StorageVersions: []apiserverinternal.ServerStorageVersion{ - { - APIServerID: "1", - EncodingVersion: "v1alpha1", - }, - }, - CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), - }, - expectedErr: "", + expectedErr: "", + }, { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{{ + APIServerID: "1", + EncodingVersion: "v1alpha1", + }}, + CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), }, - } + expectedErr: "", + }} for _, tc := range cases { err := validateCommonVersion(tc.status, field.NewPath("")) if err == nil && len(tc.expectedErr) == 0 { @@ -250,78 +217,58 @@ func TestValidateStorageVersionCondition(t *testing.T) { cases := []struct { conditions []apiserverinternal.StorageVersionCondition expectedErr string - }{ - { - conditions: []apiserverinternal.StorageVersionCondition{ - { - Type: "-fea", - Status: "True", - Reason: "unknown", - Message: "unknown", - }, - }, - expectedErr: "type: Invalid value", - }, - { - conditions: []apiserverinternal.StorageVersionCondition{ - { - Type: "fea", - Status: "-True", - Reason: "unknown", - Message: "unknown", - }, - }, - expectedErr: "status: Invalid value", - }, - { - conditions: []apiserverinternal.StorageVersionCondition{ - { - Type: "fea", - Status: "True", - Message: "unknown", - }, - }, - expectedErr: "Required value: reason cannot be empty", - }, - { - conditions: []apiserverinternal.StorageVersionCondition{ - { - Type: "fea", - Status: "True", - Reason: "unknown", - }, - }, - expectedErr: "Required value: message cannot be empty", - }, - { - conditions: []apiserverinternal.StorageVersionCondition{ - { - Type: "fea", - Status: "True", - Reason: "unknown", - Message: "unknown", - }, - { - Type: "fea", - Status: "True", - Reason: "unknown", - Message: "unknown", - }, - }, - expectedErr: `"fea": the type of the condition is not unique, it also appears in conditions[0]`, - }, - { - conditions: []apiserverinternal.StorageVersionCondition{ - { - Type: "fea", - Status: "True", - Reason: "unknown", - Message: "unknown", - }, - }, - expectedErr: "", - }, - } + }{{ + conditions: []apiserverinternal.StorageVersionCondition{{ + Type: "-fea", + Status: "True", + Reason: "unknown", + Message: "unknown", + }}, + expectedErr: "type: Invalid value", + }, { + conditions: []apiserverinternal.StorageVersionCondition{{ + Type: "fea", + Status: "-True", + Reason: "unknown", + Message: "unknown", + }}, + expectedErr: "status: Invalid value", + }, { + conditions: []apiserverinternal.StorageVersionCondition{{ + Type: "fea", + Status: "True", + Message: "unknown", + }}, + expectedErr: "Required value: reason cannot be empty", + }, { + conditions: []apiserverinternal.StorageVersionCondition{{ + Type: "fea", + Status: "True", + Reason: "unknown", + }}, + expectedErr: "Required value: message cannot be empty", + }, { + conditions: []apiserverinternal.StorageVersionCondition{{ + Type: "fea", + Status: "True", + Reason: "unknown", + Message: "unknown", + }, { + Type: "fea", + Status: "True", + Reason: "unknown", + Message: "unknown", + }}, + expectedErr: `"fea": the type of the condition is not unique, it also appears in conditions[0]`, + }, { + conditions: []apiserverinternal.StorageVersionCondition{{ + Type: "fea", + Status: "True", + Reason: "unknown", + Message: "unknown", + }}, + expectedErr: "", + }} for _, tc := range cases { err := validateStorageVersionCondition(tc.conditions, field.NewPath("")).ToAggregate() if err == nil && len(tc.expectedErr) == 0 { @@ -345,40 +292,31 @@ func TestValidateStorageVersionName(t *testing.T) { cases := []struct { name string expectedErr string - }{ - { - name: "", - expectedErr: `name must be in the form of .`, - }, - { - name: "pods", - expectedErr: `name must be in the form of .`, - }, - { - name: "core.pods", - expectedErr: "", - }, - { - name: "authentication.k8s.io.tokenreviews", - expectedErr: "", - }, - { - name: strings.Repeat("x", 253) + ".tokenreviews", - expectedErr: "", - }, - { - name: strings.Repeat("x", 254) + ".tokenreviews", - expectedErr: `the group segment must be no more than 253 characters`, - }, - { - name: "authentication.k8s.io." + strings.Repeat("x", 63), - expectedErr: "", - }, - { - name: "authentication.k8s.io." + strings.Repeat("x", 64), - expectedErr: `the resource segment must be no more than 63 characters`, - }, - } + }{{ + name: "", + expectedErr: `name must be in the form of .`, + }, { + name: "pods", + expectedErr: `name must be in the form of .`, + }, { + name: "core.pods", + expectedErr: "", + }, { + name: "authentication.k8s.io.tokenreviews", + expectedErr: "", + }, { + name: strings.Repeat("x", 253) + ".tokenreviews", + expectedErr: "", + }, { + name: strings.Repeat("x", 254) + ".tokenreviews", + expectedErr: `the group segment must be no more than 253 characters`, + }, { + name: "authentication.k8s.io." + strings.Repeat("x", 63), + expectedErr: "", + }, { + name: "authentication.k8s.io." + strings.Repeat("x", 64), + expectedErr: `the resource segment must be no more than 63 characters`, + }} for _, tc := range cases { errs := ValidateStorageVersionName(tc.name, false) if errs == nil && len(tc.expectedErr) == 0 { diff --git a/pkg/apis/apps/validation/validation_test.go b/pkg/apis/apps/validation/validation_test.go index 7b0ad6aefc2..c79ab04c827 100644 --- a/pkg/apis/apps/validation/validation_test.go +++ b/pkg/apis/apps/validation/validation_test.go @@ -248,291 +248,257 @@ func TestValidateStatefulSet(t *testing.T) { errs field.ErrorList } - successCases := []testCase{ - { - name: "alpha name", - set: mkStatefulSet(&validPodTemplate, tweakName("abc")), - }, - { - name: "alphanumeric name", - set: mkStatefulSet(&validPodTemplate, tweakName("abc-123")), - }, - { - name: "parallel pod management", - set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.ParallelPodManagement)), - }, - { - name: "ordered ready pod management", - set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)), - }, - { - name: "update strategy", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType), - tweakRollingUpdatePartition(2), - ), - }, - { - name: "PVC policy " + enableStatefulSetAutoDeletePVC, - set: mkStatefulSet(&validPodTemplate, - tweakPVCPolicy(mkPVCPolicy( - tweakPVCDeletedPolicy(apps.DeletePersistentVolumeClaimRetentionPolicyType), - tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType), - )), - ), - }, - { - name: "maxUnavailable with parallel pod management", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType), - tweakRollingUpdatePartition(2), - tweakMaxUnavailable(intstr.FromInt32(2)), - ), - }, - { - name: "ordinals.start positive value", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakOrdinalsStart(2), - ), - }, + successCases := []testCase{{ + name: "alpha name", + set: mkStatefulSet(&validPodTemplate, tweakName("abc")), + }, { + name: "alphanumeric name", + set: mkStatefulSet(&validPodTemplate, tweakName("abc-123")), + }, { + name: "parallel pod management", + set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.ParallelPodManagement)), + }, { + name: "ordered ready pod management", + set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)), + }, { + name: "update strategy", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType), + tweakRollingUpdatePartition(2), + ), + }, { + name: "PVC policy " + enableStatefulSetAutoDeletePVC, + set: mkStatefulSet(&validPodTemplate, + tweakPVCPolicy(mkPVCPolicy( + tweakPVCDeletedPolicy(apps.DeletePersistentVolumeClaimRetentionPolicyType), + tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType), + )), + ), + }, { + name: "maxUnavailable with parallel pod management", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType), + tweakRollingUpdatePartition(2), + tweakMaxUnavailable(intstr.FromInt32(2)), + ), + }, { + name: "ordinals.start positive value", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakOrdinalsStart(2), + ), + }, } - errorCases := []testCase{ - { - name: "zero-length name", - set: mkStatefulSet(&validPodTemplate, tweakName("")), - errs: field.ErrorList{ - field.Required(field.NewPath("metadata", "name"), ""), - }, + errorCases := []testCase{{ + name: "zero-length name", + set: mkStatefulSet(&validPodTemplate, tweakName("")), + errs: field.ErrorList{ + field.Required(field.NewPath("metadata", "name"), ""), }, - { - name: "name-with-dots", - set: mkStatefulSet(&validPodTemplate, tweakName("abc.123")), - errs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), "abc.123", ""), - }, + }, { + name: "name-with-dots", + set: mkStatefulSet(&validPodTemplate, tweakName("abc.123")), + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), "abc.123", ""), }, - { - name: "long name", - set: mkStatefulSet(&validPodTemplate, tweakName(strings.Repeat("a", 64))), - errs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), strings.Repeat("a", 64), ""), - }, + }, { + name: "long name", + set: mkStatefulSet(&validPodTemplate, tweakName(strings.Repeat("a", 64))), + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), strings.Repeat("a", 64), ""), }, - { - name: "missing-namespace", - set: mkStatefulSet(&validPodTemplate, tweakNamespace("")), - errs: field.ErrorList{ - field.Required(field.NewPath("metadata", "namespace"), ""), - }, + }, { + name: "missing-namespace", + set: mkStatefulSet(&validPodTemplate, tweakNamespace("")), + errs: field.ErrorList{ + field.Required(field.NewPath("metadata", "namespace"), ""), }, - { - name: "empty selector", - set: mkStatefulSet(&validPodTemplate, tweakSelectorLabels(nil)), - errs: field.ErrorList{ - field.Required(field.NewPath("spec", "selector"), ""), - field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), nil, ""), // selector is empty, labels are not, so select doesn't match labels - }, + }, { + name: "empty selector", + set: mkStatefulSet(&validPodTemplate, tweakSelectorLabels(nil)), + errs: field.ErrorList{ + field.Required(field.NewPath("spec", "selector"), ""), + field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), nil, ""), // selector is empty, labels are not, so select doesn't match labels }, - { - name: "selector_doesnt_match", - set: mkStatefulSet(&validPodTemplate, tweakSelectorLabels(map[string]string{"foo": "bar"})), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), nil, ""), - }, + }, { + name: "selector_doesnt_match", + set: mkStatefulSet(&validPodTemplate, tweakSelectorLabels(map[string]string{"foo": "bar"})), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), nil, ""), }, - { - name: "negative_replicas", - set: mkStatefulSet(&validPodTemplate, tweakReplicas(-1)), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "replicas"), nil, ""), - }, + }, { + name: "negative_replicas", + set: mkStatefulSet(&validPodTemplate, tweakReplicas(-1)), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "replicas"), nil, ""), }, - { - name: "invalid_label", - set: mkStatefulSet(&validPodTemplate, - tweakLabels("NoUppercaseOrSpecialCharsLike=Equals", "bar"), - ), - errs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "labels"), nil, ""), - }, + }, { + name: "invalid_label", + set: mkStatefulSet(&validPodTemplate, + tweakLabels("NoUppercaseOrSpecialCharsLike=Equals", "bar"), + ), + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "labels"), nil, ""), }, - { - name: "invalid_label 2", - set: mkStatefulSet(&invalidPodTemplate, - tweakLabels("NoUppercaseOrSpecialCharsLike=Equals", "bar"), - tweakSelectorLabels(invalidLabels), - ), - errs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "labels"), nil, ""), - field.Invalid(field.NewPath("spec", "selector"), nil, ""), - field.Invalid(field.NewPath("spec", "selector", "matchLabels"), nil, ""), - }, + }, { + name: "invalid_label 2", + set: mkStatefulSet(&invalidPodTemplate, + tweakLabels("NoUppercaseOrSpecialCharsLike=Equals", "bar"), + tweakSelectorLabels(invalidLabels), + ), + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "labels"), nil, ""), + field.Invalid(field.NewPath("spec", "selector"), nil, ""), + field.Invalid(field.NewPath("spec", "selector", "matchLabels"), nil, ""), }, - { - name: "invalid_annotation", - set: mkStatefulSet(&validPodTemplate, - tweakAnnotations("NoUppercaseOrSpecialCharsLike=Equals", "bar"), - ), - errs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "annotations"), nil, ""), - }, + }, { + name: "invalid_annotation", + set: mkStatefulSet(&validPodTemplate, + tweakAnnotations("NoUppercaseOrSpecialCharsLike=Equals", "bar"), + ), + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "annotations"), nil, ""), }, - { - name: "invalid restart policy 1", - set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy(api.RestartPolicyOnFailure)), - errs: field.ErrorList{ - field.NotSupported(field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil), - }, + }, { + name: "invalid restart policy 1", + set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy(api.RestartPolicyOnFailure)), + errs: field.ErrorList{ + field.NotSupported(field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil), }, - { - name: "invalid restart policy 2", - set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy(api.RestartPolicyNever)), - errs: field.ErrorList{ - field.NotSupported(field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil), - }, + }, { + name: "invalid restart policy 2", + set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy(api.RestartPolicyNever)), + errs: field.ErrorList{ + field.NotSupported(field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil), }, - { - name: "empty restart policy", - set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy("")), - errs: field.ErrorList{ - field.NotSupported(field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil), - }, + }, { + name: "empty restart policy", + set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy("")), + errs: field.ErrorList{ + field.NotSupported(field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil), }, - { - name: "invalid update strategy", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakUpdateStrategyType("foo"), - ), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "updateStrategy"), nil, ""), - }, + }, { + name: "invalid update strategy", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakUpdateStrategyType("foo"), + ), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy"), nil, ""), }, - { - name: "empty update strategy", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakUpdateStrategyType(""), - ), - errs: field.ErrorList{ - field.Required(field.NewPath("spec", "updateStrategy"), ""), - }, + }, { + name: "empty update strategy", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakUpdateStrategyType(""), + ), + errs: field.ErrorList{ + field.Required(field.NewPath("spec", "updateStrategy"), ""), }, - { - name: "invalid rolling update", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakUpdateStrategyType(apps.OnDeleteStatefulSetStrategyType), - tweakRollingUpdatePartition(1), - ), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate"), nil, ""), - }, + }, { + name: "invalid rolling update", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakUpdateStrategyType(apps.OnDeleteStatefulSetStrategyType), + tweakRollingUpdatePartition(1), + ), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate"), nil, ""), }, - { - name: "negative parition", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakRollingUpdatePartition(-1), - ), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "partition"), nil, ""), - }, + }, { + name: "negative parition", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakRollingUpdatePartition(-1), + ), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "partition"), nil, ""), }, - { - name: "empty pod management policy", - set: mkStatefulSet(&validPodTemplate, - tweakPodManagementPolicy(""), - tweakReplicas(3), - ), - errs: field.ErrorList{ - field.Required(field.NewPath("spec", "podManagementPolicy"), ""), - }, + }, { + name: "empty pod management policy", + set: mkStatefulSet(&validPodTemplate, + tweakPodManagementPolicy(""), + tweakReplicas(3), + ), + errs: field.ErrorList{ + field.Required(field.NewPath("spec", "podManagementPolicy"), ""), }, - { - name: "invalid pod management policy", - set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy("foo")), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "podManagementPolicy"), nil, ""), - }, + }, { + name: "invalid pod management policy", + set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy("foo")), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "podManagementPolicy"), nil, ""), }, - { - name: "set active deadline seconds", - set: mkStatefulSet(&invalidPodTemplate2, tweakReplicas(3)), - errs: field.ErrorList{ - field.Forbidden(field.NewPath("spec", "template", "spec", "activeDeadlineSeconds"), ""), - }, + }, { + name: "set active deadline seconds", + set: mkStatefulSet(&invalidPodTemplate2, tweakReplicas(3)), + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec", "template", "spec", "activeDeadlineSeconds"), ""), }, - { - name: "empty PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC, - set: mkStatefulSet(&validPodTemplate, - tweakPVCPolicy(mkPVCPolicy()), - ), - errs: field.ErrorList{ - field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenDeleted"), nil, nil), - field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenScaled"), nil, nil), - }, + }, { + name: "empty PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC, + set: mkStatefulSet(&validPodTemplate, + tweakPVCPolicy(mkPVCPolicy()), + ), + errs: field.ErrorList{ + field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenDeleted"), nil, nil), + field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenScaled"), nil, nil), }, - { - name: "invalid PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC, - set: mkStatefulSet(&validPodTemplate, - tweakPVCPolicy(mkPVCPolicy( - tweakPVCDeletedPolicy("invalid-retention-policy"), - tweakPVCScalePolicy("invalid-retention-policy"), - )), - ), - errs: field.ErrorList{ - field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenDeleted"), nil, nil), - field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenScaled"), nil, nil), - }, + }, { + name: "invalid PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC, + set: mkStatefulSet(&validPodTemplate, + tweakPVCPolicy(mkPVCPolicy( + tweakPVCDeletedPolicy("invalid-retention-policy"), + tweakPVCScalePolicy("invalid-retention-policy"), + )), + ), + errs: field.ErrorList{ + field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenDeleted"), nil, nil), + field.NotSupported(field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenScaled"), nil, nil), }, - { - name: "zero maxUnavailable", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType), - tweakMaxUnavailable(intstr.FromInt32(0)), - ), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""), - }, + }, { + name: "zero maxUnavailable", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType), + tweakMaxUnavailable(intstr.FromInt32(0)), + ), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""), }, - { - name: "zero percent maxUnavailable", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType), - tweakMaxUnavailable(intstr.FromString("0%")), - ), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""), - }, + }, { + name: "zero percent maxUnavailable", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType), + tweakMaxUnavailable(intstr.FromString("0%")), + ), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""), }, - { - name: "greater than 100 percent maxUnavailable", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType), - tweakMaxUnavailable(intstr.FromString("101%")), - ), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""), - }, + }, { + name: "greater than 100 percent maxUnavailable", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType), + tweakMaxUnavailable(intstr.FromString("101%")), + ), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""), }, - { - name: "invalid ordinals.start", - set: mkStatefulSet(&validPodTemplate, - tweakReplicas(3), - tweakOrdinalsStart(-2), - ), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "ordinals.start"), nil, ""), - }, + }, { + name: "invalid ordinals.start", + set: mkStatefulSet(&validPodTemplate, + tweakReplicas(3), + tweakOrdinalsStart(-2), + ), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "ordinals.start"), nil, ""), }, + }, } cmpOpts := []cmp.Option{cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail"), cmpopts.SortSlices(func(a, b *field.Error) bool { return a.Error() < b.Error() })} @@ -635,113 +601,100 @@ func TestValidateStatefulSetStatus(t *testing.T) { observedGeneration *int64 collisionCount *int32 expectedErr bool - }{ - { - name: "valid status", - replicas: 3, - readyReplicas: 3, - currentReplicas: 2, - updatedReplicas: 1, - expectedErr: false, - }, - { - name: "invalid replicas", - replicas: -1, - readyReplicas: 3, - currentReplicas: 2, - updatedReplicas: 1, - expectedErr: true, - }, - { - name: "invalid readyReplicas", - replicas: 3, - readyReplicas: -1, - currentReplicas: 2, - updatedReplicas: 1, - expectedErr: true, - }, - { - name: "invalid currentReplicas", - replicas: 3, - readyReplicas: 3, - currentReplicas: -1, - updatedReplicas: 1, - expectedErr: true, - }, - { - name: "invalid updatedReplicas", - replicas: 3, - readyReplicas: 3, - currentReplicas: 2, - updatedReplicas: -1, - expectedErr: true, - }, - { - name: "invalid observedGeneration", - replicas: 3, - readyReplicas: 3, - currentReplicas: 2, - updatedReplicas: 1, - observedGeneration: &observedGenerationMinusOne, - expectedErr: true, - }, - { - name: "invalid collisionCount", - replicas: 3, - readyReplicas: 3, - currentReplicas: 2, - updatedReplicas: 1, - collisionCount: &collisionCountMinusOne, - expectedErr: true, - }, - { - name: "readyReplicas greater than replicas", - replicas: 3, - readyReplicas: 4, - currentReplicas: 2, - updatedReplicas: 1, - expectedErr: true, - }, - { - name: "currentReplicas greater than replicas", - replicas: 3, - readyReplicas: 3, - currentReplicas: 4, - updatedReplicas: 1, - expectedErr: true, - }, - { - name: "updatedReplicas greater than replicas", - replicas: 3, - readyReplicas: 3, - currentReplicas: 2, - updatedReplicas: 4, - expectedErr: true, - }, - { - name: "invalid: number of available replicas", - replicas: 3, - readyReplicas: 3, - currentReplicas: 2, - availableReplicas: int32(-1), - expectedErr: true, - }, - { - name: "invalid: available replicas greater than replicas", - replicas: 3, - readyReplicas: 3, - currentReplicas: 2, - availableReplicas: int32(4), - expectedErr: true, - }, - { - name: "invalid: available replicas greater than ready replicas", - replicas: 3, - readyReplicas: 2, - currentReplicas: 2, - availableReplicas: int32(3), - expectedErr: true, - }, + }{{ + name: "valid status", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: 1, + expectedErr: false, + }, { + name: "invalid replicas", + replicas: -1, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: 1, + expectedErr: true, + }, { + name: "invalid readyReplicas", + replicas: 3, + readyReplicas: -1, + currentReplicas: 2, + updatedReplicas: 1, + expectedErr: true, + }, { + name: "invalid currentReplicas", + replicas: 3, + readyReplicas: 3, + currentReplicas: -1, + updatedReplicas: 1, + expectedErr: true, + }, { + name: "invalid updatedReplicas", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: -1, + expectedErr: true, + }, { + name: "invalid observedGeneration", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: 1, + observedGeneration: &observedGenerationMinusOne, + expectedErr: true, + }, { + name: "invalid collisionCount", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: 1, + collisionCount: &collisionCountMinusOne, + expectedErr: true, + }, { + name: "readyReplicas greater than replicas", + replicas: 3, + readyReplicas: 4, + currentReplicas: 2, + updatedReplicas: 1, + expectedErr: true, + }, { + name: "currentReplicas greater than replicas", + replicas: 3, + readyReplicas: 3, + currentReplicas: 4, + updatedReplicas: 1, + expectedErr: true, + }, { + name: "updatedReplicas greater than replicas", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: 4, + expectedErr: true, + }, { + name: "invalid: number of available replicas", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + availableReplicas: int32(-1), + expectedErr: true, + }, { + name: "invalid: available replicas greater than replicas", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + availableReplicas: int32(4), + expectedErr: true, + }, { + name: "invalid: available replicas greater than ready replicas", + replicas: 3, + readyReplicas: 2, + currentReplicas: 2, + availableReplicas: int32(3), + expectedErr: true, + }, } for _, test := range tests { @@ -842,145 +795,126 @@ func TestValidateStatefulSetUpdate(t *testing.T) { errs field.ErrorList } - successCases := []testCase{ - { - name: "update replica count", - old: mkStatefulSet(&validPodTemplate), - update: mkStatefulSet(&validPodTemplate, tweakReplicas(3)), - }, - { - name: "update containers 1", - old: mkStatefulSet(&validPodTemplate), - update: mkStatefulSet(addContainersValidTemplate), - }, - { - name: "update containers 2", - old: mkStatefulSet(addContainersValidTemplate), - update: mkStatefulSet(&validPodTemplate), - }, - { - name: "update containers and pvc retention policy 1", - old: mkStatefulSet(addContainersValidTemplate), - update: mkStatefulSet(&validPodTemplate, - tweakPVCPolicy(mkPVCPolicy( - tweakPVCDeletedPolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType), - tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType), - )), - ), - }, - { - name: "update containers and pvc retention policy 2", - old: mkStatefulSet(&validPodTemplate, - tweakPVCPolicy(mkPVCPolicy( - tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType), - )), - ), - update: mkStatefulSet(&validPodTemplate), - }, - { - name: "update update strategy", - old: mkStatefulSet(&validPodTemplate), - update: mkStatefulSet(&validPodTemplate, - tweakUpdateStrategyType(apps.OnDeleteStatefulSetStrategyType), - ), - }, - { - name: "update min ready seconds 1", - old: mkStatefulSet(&validPodTemplate), - update: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(10)), - }, - { - name: "update min ready seconds 2", - old: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(5)), - update: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(10)), - }, - { - name: "update existing instance with now-invalid name", - old: mkStatefulSet(&validPodTemplate, tweakFinalizers("final")), - update: mkStatefulSet(&validPodTemplate, tweakFinalizers()), - }, - { - name: "update existing instance with .spec.ordinals.start", - old: mkStatefulSet(&validPodTemplate), - update: mkStatefulSet(&validPodTemplate, tweakOrdinalsStart(3)), - }, + successCases := []testCase{{ + name: "update replica count", + old: mkStatefulSet(&validPodTemplate), + update: mkStatefulSet(&validPodTemplate, tweakReplicas(3)), + }, { + name: "update containers 1", + old: mkStatefulSet(&validPodTemplate), + update: mkStatefulSet(addContainersValidTemplate), + }, { + name: "update containers 2", + old: mkStatefulSet(addContainersValidTemplate), + update: mkStatefulSet(&validPodTemplate), + }, { + name: "update containers and pvc retention policy 1", + old: mkStatefulSet(addContainersValidTemplate), + update: mkStatefulSet(&validPodTemplate, + tweakPVCPolicy(mkPVCPolicy( + tweakPVCDeletedPolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType), + tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType), + )), + ), + }, { + name: "update containers and pvc retention policy 2", + old: mkStatefulSet(&validPodTemplate, + tweakPVCPolicy(mkPVCPolicy( + tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType), + )), + ), + update: mkStatefulSet(&validPodTemplate), + }, { + name: "update update strategy", + old: mkStatefulSet(&validPodTemplate), + update: mkStatefulSet(&validPodTemplate, + tweakUpdateStrategyType(apps.OnDeleteStatefulSetStrategyType), + ), + }, { + name: "update min ready seconds 1", + old: mkStatefulSet(&validPodTemplate), + update: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(10)), + }, { + name: "update min ready seconds 2", + old: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(5)), + update: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(10)), + }, { + name: "update existing instance with now-invalid name", + old: mkStatefulSet(&validPodTemplate, tweakFinalizers("final")), + update: mkStatefulSet(&validPodTemplate, tweakFinalizers()), + }, { + name: "update existing instance with .spec.ordinals.start", + old: mkStatefulSet(&validPodTemplate), + update: mkStatefulSet(&validPodTemplate, tweakOrdinalsStart(3)), + }, } - errorCases := []testCase{ - { - name: "update name", - old: mkStatefulSet(&validPodTemplate, tweakName("abc")), - update: mkStatefulSet(&validPodTemplate, tweakName("abc2")), - errs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), nil, ""), - }, + errorCases := []testCase{{ + name: "update name", + old: mkStatefulSet(&validPodTemplate, tweakName("abc")), + update: mkStatefulSet(&validPodTemplate, tweakName("abc2")), + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), nil, ""), }, - { - name: "update namespace", - old: mkStatefulSet(&validPodTemplate, tweakNamespace(metav1.NamespaceDefault)), - update: mkStatefulSet(&validPodTemplate, tweakNamespace(metav1.NamespaceDefault+"1")), - errs: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "namespace"), nil, ""), - }, + }, { + name: "update namespace", + old: mkStatefulSet(&validPodTemplate, tweakNamespace(metav1.NamespaceDefault)), + update: mkStatefulSet(&validPodTemplate, tweakNamespace(metav1.NamespaceDefault+"1")), + errs: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "namespace"), nil, ""), }, - { - name: "update selector", - old: mkStatefulSet(&validPodTemplate, tweakSelectorLabels(validLabels)), - update: mkStatefulSet(&validPodTemplate2, - tweakSelectorLabels(validLabels2), - ), - errs: field.ErrorList{ - field.Forbidden(field.NewPath("spec"), ""), - }, + }, { + name: "update selector", + old: mkStatefulSet(&validPodTemplate, tweakSelectorLabels(validLabels)), + update: mkStatefulSet(&validPodTemplate2, + tweakSelectorLabels(validLabels2), + ), + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), }, - { - name: "update pod management policy 1", - old: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy("")), - update: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)), - errs: field.ErrorList{ - field.Forbidden(field.NewPath("spec"), ""), - }, + }, { + name: "update pod management policy 1", + old: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy("")), + update: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)), + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), }, - { - name: "update pod management policy 2", - old: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.ParallelPodManagement)), - update: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)), - errs: field.ErrorList{ - field.Forbidden(field.NewPath("spec"), ""), - }, + }, { + name: "update pod management policy 2", + old: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.ParallelPodManagement)), + update: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)), + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), }, - { - name: "update to negative replicas", - old: mkStatefulSet(&validPodTemplate), - update: mkStatefulSet(&validPodTemplate, tweakReplicas(-1)), - errs: field.ErrorList{ - field.Invalid(field.NewPath("spec", "replicas"), nil, ""), - }, + }, { + name: "update to negative replicas", + old: mkStatefulSet(&validPodTemplate), + update: mkStatefulSet(&validPodTemplate, tweakReplicas(-1)), + errs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "replicas"), nil, ""), }, - { - name: "update pvc template size", - old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)), - update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplateChangedSize)), - errs: field.ErrorList{ - field.Forbidden(field.NewPath("spec"), ""), - }, + }, { + name: "update pvc template size", + old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)), + update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplateChangedSize)), + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), }, - { - name: "update pvc template storage class", - old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)), - update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplateChangedClass)), - errs: field.ErrorList{ - field.Forbidden(field.NewPath("spec"), ""), - }, + }, { + name: "update pvc template storage class", + old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)), + update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplateChangedClass)), + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), }, - { - name: "add new pvc template", - old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)), - update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate, validPVCTemplate2)), - errs: field.ErrorList{ - field.Forbidden(field.NewPath("spec"), ""), - }, + }, { + name: "add new pvc template", + old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)), + update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate, validPVCTemplate2)), + errs: field.ErrorList{ + field.Forbidden(field.NewPath("spec"), ""), }, + }, } cmpOpts := []cmp.Option{ @@ -1112,31 +1046,27 @@ func TestValidateControllerRevisionUpdate(t *testing.T) { newHistory apps.ControllerRevision oldHistory apps.ControllerRevision isValid bool - }{ - { - name: "valid", - newHistory: valid, - oldHistory: valid, - isValid: true, - }, - { - name: "invalid", - newHistory: noVersion, - oldHistory: valid, - isValid: false, - }, - { - name: "changed data", - newHistory: changedData, - oldHistory: valid, - isValid: false, - }, - { - name: "changed revision", - newHistory: changedRevision, - oldHistory: valid, - isValid: true, - }, + }{{ + name: "valid", + newHistory: valid, + oldHistory: valid, + isValid: true, + }, { + name: "invalid", + newHistory: noVersion, + oldHistory: valid, + isValid: false, + }, { + name: "changed data", + newHistory: changedData, + oldHistory: valid, + isValid: false, + }, { + name: "changed revision", + newHistory: changedRevision, + oldHistory: valid, + isValid: true, + }, } for _, tc := range cases { @@ -1158,33 +1088,32 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) { update apps.DaemonSet } - successCases := []dsUpdateTest{ - { - old: apps.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Status: apps.DaemonSetStatus{ - CurrentNumberScheduled: 1, - NumberMisscheduled: 2, - DesiredNumberScheduled: 3, - NumberReady: 1, - UpdatedNumberScheduled: 1, - NumberAvailable: 1, - NumberUnavailable: 2, - }, - }, - update: apps.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Status: apps.DaemonSetStatus{ - CurrentNumberScheduled: 1, - NumberMisscheduled: 1, - DesiredNumberScheduled: 3, - NumberReady: 1, - UpdatedNumberScheduled: 1, - NumberAvailable: 1, - NumberUnavailable: 2, - }, + successCases := []dsUpdateTest{{ + old: apps.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Status: apps.DaemonSetStatus{ + CurrentNumberScheduled: 1, + NumberMisscheduled: 2, + DesiredNumberScheduled: 3, + NumberReady: 1, + UpdatedNumberScheduled: 1, + NumberAvailable: 1, + NumberUnavailable: 2, }, }, + update: apps.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Status: apps.DaemonSetStatus{ + CurrentNumberScheduled: 1, + NumberMisscheduled: 1, + DesiredNumberScheduled: 3, + NumberReady: 1, + UpdatedNumberScheduled: 1, + NumberAvailable: 1, + NumberUnavailable: 2, + }, + }, + }, } for _, successCase := range successCases { @@ -2071,27 +2000,25 @@ func TestValidateDaemonSet(t *testing.T) { }, }, } - successCases := []apps.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.DaemonSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: validSelector}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.DaemonSetUpdateStrategy{ - Type: apps.OnDeleteDaemonSetStrategyType, - }, + successCases := []apps.DaemonSet{{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.DaemonSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: validSelector}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.DaemonSetUpdateStrategy{ + Type: apps.OnDeleteDaemonSetStrategyType, }, }, - { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.DaemonSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: validSelector}, - Template: validPodTemplate.Template, - UpdateStrategy: apps.DaemonSetUpdateStrategy{ - Type: apps.OnDeleteDaemonSetStrategyType, - }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.DaemonSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: validSelector}, + Template: validPodTemplate.Template, + UpdateStrategy: apps.DaemonSetUpdateStrategy{ + Type: apps.OnDeleteDaemonSetStrategyType, }, }, + }, } for _, successCase := range successCases { if errs := ValidateDaemonSet(&successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 { @@ -2290,14 +2217,12 @@ func validDeployment() *apps.Deployment { Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSDefault, - Containers: []api.Container{ - { - Name: "nginx", - Image: "image", - ImagePullPolicy: api.PullNever, - TerminationMessagePolicy: api.TerminationMessageReadFile, - }, - }, + Containers: []api.Container{{ + Name: "nginx", + Image: "image", + ImagePullPolicy: api.PullNever, + TerminationMessagePolicy: api.TerminationMessageReadFile, + }}, }, }, RollbackTo: &apps.RollbackConfig{ @@ -2427,98 +2352,87 @@ func TestValidateDeploymentStatus(t *testing.T) { collisionCount *int32 expectedErr bool - }{ - { - name: "valid status", - replicas: 3, - updatedReplicas: 3, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: false, - }, - { - name: "invalid replicas", - replicas: -1, - updatedReplicas: 2, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid updatedReplicas", - replicas: 2, - updatedReplicas: -1, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid readyReplicas", - replicas: 3, - readyReplicas: -1, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid availableReplicas", - replicas: 3, - readyReplicas: 3, - availableReplicas: -1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid observedGeneration", - replicas: 3, - readyReplicas: 3, - availableReplicas: 3, - observedGeneration: -1, - expectedErr: true, - }, - { - name: "updatedReplicas greater than replicas", - replicas: 3, - updatedReplicas: 4, - readyReplicas: 3, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "readyReplicas greater than replicas", - replicas: 3, - readyReplicas: 4, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "availableReplicas greater than replicas", - replicas: 3, - readyReplicas: 3, - availableReplicas: 4, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "availableReplicas greater than readyReplicas", - replicas: 3, - readyReplicas: 2, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "invalid collisionCount", - replicas: 3, - observedGeneration: 1, - collisionCount: &collisionCount, - expectedErr: true, - }, + }{{ + name: "valid status", + replicas: 3, + updatedReplicas: 3, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: false, + }, { + name: "invalid replicas", + replicas: -1, + updatedReplicas: 2, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid updatedReplicas", + replicas: 2, + updatedReplicas: -1, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid readyReplicas", + replicas: 3, + readyReplicas: -1, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid availableReplicas", + replicas: 3, + readyReplicas: 3, + availableReplicas: -1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid observedGeneration", + replicas: 3, + readyReplicas: 3, + availableReplicas: 3, + observedGeneration: -1, + expectedErr: true, + }, { + name: "updatedReplicas greater than replicas", + replicas: 3, + updatedReplicas: 4, + readyReplicas: 3, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, { + name: "readyReplicas greater than replicas", + replicas: 3, + readyReplicas: 4, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, { + name: "availableReplicas greater than replicas", + replicas: 3, + readyReplicas: 3, + availableReplicas: 4, + observedGeneration: 1, + expectedErr: true, + }, { + name: "availableReplicas greater than readyReplicas", + replicas: 3, + readyReplicas: 2, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, { + name: "invalid collisionCount", + replicas: 3, + observedGeneration: 1, + collisionCount: &collisionCount, + expectedErr: true, + }, } for _, test := range tests { @@ -2548,47 +2462,43 @@ func TestValidateDeploymentStatusUpdate(t *testing.T) { from, to apps.DeploymentStatus expectedErr bool - }{ - { - name: "increase: valid update", - from: apps.DeploymentStatus{ - CollisionCount: nil, - }, - to: apps.DeploymentStatus{ - CollisionCount: &collisionCount, - }, - expectedErr: false, + }{{ + name: "increase: valid update", + from: apps.DeploymentStatus{ + CollisionCount: nil, }, - { - name: "stable: valid update", - from: apps.DeploymentStatus{ - CollisionCount: &collisionCount, - }, - to: apps.DeploymentStatus{ - CollisionCount: &collisionCount, - }, - expectedErr: false, + to: apps.DeploymentStatus{ + CollisionCount: &collisionCount, }, - { - name: "unset: invalid update", - from: apps.DeploymentStatus{ - CollisionCount: &collisionCount, - }, - to: apps.DeploymentStatus{ - CollisionCount: nil, - }, - expectedErr: true, + expectedErr: false, + }, { + name: "stable: valid update", + from: apps.DeploymentStatus{ + CollisionCount: &collisionCount, }, - { - name: "decrease: invalid update", - from: apps.DeploymentStatus{ - CollisionCount: &otherCollisionCount, - }, - to: apps.DeploymentStatus{ - CollisionCount: &collisionCount, - }, - expectedErr: true, + to: apps.DeploymentStatus{ + CollisionCount: &collisionCount, }, + expectedErr: false, + }, { + name: "unset: invalid update", + from: apps.DeploymentStatus{ + CollisionCount: &collisionCount, + }, + to: apps.DeploymentStatus{ + CollisionCount: nil, + }, + expectedErr: true, + }, { + name: "decrease: invalid update", + from: apps.DeploymentStatus{ + CollisionCount: &otherCollisionCount, + }, + to: apps.DeploymentStatus{ + CollisionCount: &collisionCount, + }, + expectedErr: true, + }, } for _, test := range tests { @@ -2872,97 +2782,87 @@ func TestValidateReplicaSetStatus(t *testing.T) { observedGeneration int64 expectedErr bool - }{ - { - name: "valid status", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: false, - }, - { - name: "invalid replicas", - replicas: -1, - fullyLabeledReplicas: 3, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid fullyLabeledReplicas", - replicas: 3, - fullyLabeledReplicas: -1, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid readyReplicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: -1, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid availableReplicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 3, - availableReplicas: -1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid observedGeneration", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 3, - availableReplicas: 3, - observedGeneration: -1, - expectedErr: true, - }, - { - name: "fullyLabeledReplicas greater than replicas", - replicas: 3, - fullyLabeledReplicas: 4, - readyReplicas: 3, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "readyReplicas greater than replicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 4, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "availableReplicas greater than replicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 3, - availableReplicas: 4, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "availableReplicas greater than readyReplicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 2, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, + }{{ + name: "valid status", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: false, + }, { + name: "invalid replicas", + replicas: -1, + fullyLabeledReplicas: 3, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid fullyLabeledReplicas", + replicas: 3, + fullyLabeledReplicas: -1, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid readyReplicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: -1, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid availableReplicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 3, + availableReplicas: -1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid observedGeneration", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 3, + availableReplicas: 3, + observedGeneration: -1, + expectedErr: true, + }, { + name: "fullyLabeledReplicas greater than replicas", + replicas: 3, + fullyLabeledReplicas: 4, + readyReplicas: 3, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, { + name: "readyReplicas greater than replicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 4, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, { + name: "availableReplicas greater than replicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 3, + availableReplicas: 4, + observedGeneration: 1, + expectedErr: true, + }, { + name: "availableReplicas greater than readyReplicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 2, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, } for _, test := range tests { @@ -2998,30 +2898,29 @@ func TestValidateReplicaSetStatusUpdate(t *testing.T) { old apps.ReplicaSet update apps.ReplicaSet } - successCases := []rcUpdateTest{ - { - old: apps.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.ReplicaSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - }, - Status: apps.ReplicaSetStatus{ - Replicas: 2, - }, + successCases := []rcUpdateTest{{ + old: apps.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.ReplicaSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, }, - update: apps.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.ReplicaSetSpec{ - Replicas: 3, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - }, - Status: apps.ReplicaSetStatus{ - Replicas: 4, - }, + Status: apps.ReplicaSetStatus{ + Replicas: 2, }, }, + update: apps.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.ReplicaSetSpec{ + Replicas: 3, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + }, + Status: apps.ReplicaSetStatus{ + Replicas: 4, + }, + }, + }, } for _, successCase := range successCases { successCase.old.ObjectMeta.ResourceVersion = "1" @@ -3300,29 +3199,26 @@ func TestValidateReplicaSet(t *testing.T) { }, }, } - successCases := []apps.ReplicaSet{ - { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: apps.ReplicaSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - }, + successCases := []apps.ReplicaSet{{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: apps.ReplicaSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, }, - { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.ReplicaSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: validPodTemplate.Template, - }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.ReplicaSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, }, - { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: apps.ReplicaSetSpec{ - Replicas: 1, - Selector: &metav1.LabelSelector{MatchLabels: validLabels}, - Template: readWriteVolumePodTemplate.Template, - }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.ReplicaSetSpec{ + Replicas: 1, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: readWriteVolumePodTemplate.Template, }, + }, } for _, successCase := range successCases { if errs := ValidateReplicaSet(&successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 { diff --git a/pkg/apis/authorization/validation/validation_test.go b/pkg/apis/authorization/validation/validation_test.go index 44168859f0b..36e6d46dbe7 100644 --- a/pkg/apis/authorization/validation/validation_test.go +++ b/pkg/apis/authorization/validation/validation_test.go @@ -40,29 +40,25 @@ func TestValidateSARSpec(t *testing.T) { name string obj authorizationapi.SubjectAccessReviewSpec msg string - }{ - { - name: "neither request", - obj: authorizationapi.SubjectAccessReviewSpec{User: "me"}, - msg: "exactly one of nonResourceAttributes or resourceAttributes must be specified", + }{{ + name: "neither request", + obj: authorizationapi.SubjectAccessReviewSpec{User: "me"}, + msg: "exactly one of nonResourceAttributes or resourceAttributes must be specified", + }, { + name: "both requests", + obj: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{}, + NonResourceAttributes: &authorizationapi.NonResourceAttributes{}, + User: "me", }, - { - name: "both requests", - obj: authorizationapi.SubjectAccessReviewSpec{ - ResourceAttributes: &authorizationapi.ResourceAttributes{}, - NonResourceAttributes: &authorizationapi.NonResourceAttributes{}, - User: "me", - }, - msg: "cannot be specified in combination with resourceAttributes", + msg: "cannot be specified in combination with resourceAttributes", + }, { + name: "no subject", + obj: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{}, }, - { - name: "no subject", - obj: authorizationapi.SubjectAccessReviewSpec{ - ResourceAttributes: &authorizationapi.ResourceAttributes{}, - }, - msg: `spec.user: Invalid value: "": at least one of user or group must be specified`, - }, - } + msg: `spec.user: Invalid value: "": at least one of user or group must be specified`, + }} for _, c := range errorCases { errs := ValidateSubjectAccessReviewSpec(c.obj, field.NewPath("spec")) @@ -102,21 +98,18 @@ func TestValidateSelfSAR(t *testing.T) { name string obj authorizationapi.SelfSubjectAccessReviewSpec msg string - }{ - { - name: "neither request", - obj: authorizationapi.SelfSubjectAccessReviewSpec{}, - msg: "exactly one of nonResourceAttributes or resourceAttributes must be specified", + }{{ + name: "neither request", + obj: authorizationapi.SelfSubjectAccessReviewSpec{}, + msg: "exactly one of nonResourceAttributes or resourceAttributes must be specified", + }, { + name: "both requests", + obj: authorizationapi.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{}, + NonResourceAttributes: &authorizationapi.NonResourceAttributes{}, }, - { - name: "both requests", - obj: authorizationapi.SelfSubjectAccessReviewSpec{ - ResourceAttributes: &authorizationapi.ResourceAttributes{}, - NonResourceAttributes: &authorizationapi.NonResourceAttributes{}, - }, - msg: "cannot be specified in combination with resourceAttributes", - }, - } + msg: "cannot be specified in combination with resourceAttributes", + }} for _, c := range errorCases { errs := ValidateSelfSubjectAccessReviewSpec(c.obj, field.NewPath("spec")) @@ -136,14 +129,12 @@ func TestValidateSelfSAR(t *testing.T) { } func TestValidateLocalSAR(t *testing.T) { - successCases := []authorizationapi.LocalSubjectAccessReview{ - { - Spec: authorizationapi.SubjectAccessReviewSpec{ - ResourceAttributes: &authorizationapi.ResourceAttributes{}, - User: "user", - }, + successCases := []authorizationapi.LocalSubjectAccessReview{{ + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{}, + User: "user", }, - } + }} for _, successCase := range successCases { if errs := ValidateLocalSubjectAccessReview(&successCase); len(errs) != 0 { t.Errorf("expected success: %v", errs) @@ -154,41 +145,37 @@ func TestValidateLocalSAR(t *testing.T) { name string obj *authorizationapi.LocalSubjectAccessReview msg string - }{ - { - name: "name", - obj: &authorizationapi.LocalSubjectAccessReview{ - ObjectMeta: metav1.ObjectMeta{Name: "a"}, - Spec: authorizationapi.SubjectAccessReviewSpec{ - ResourceAttributes: &authorizationapi.ResourceAttributes{}, - User: "user", - }, + }{{ + name: "name", + obj: &authorizationapi.LocalSubjectAccessReview{ + ObjectMeta: metav1.ObjectMeta{Name: "a"}, + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{}, + User: "user", }, - msg: "must be empty except for namespace", }, - { - name: "namespace conflict", - obj: &authorizationapi.LocalSubjectAccessReview{ - ObjectMeta: metav1.ObjectMeta{Namespace: "a"}, - Spec: authorizationapi.SubjectAccessReviewSpec{ - ResourceAttributes: &authorizationapi.ResourceAttributes{}, - User: "user", - }, + msg: "must be empty except for namespace", + }, { + name: "namespace conflict", + obj: &authorizationapi.LocalSubjectAccessReview{ + ObjectMeta: metav1.ObjectMeta{Namespace: "a"}, + Spec: authorizationapi.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapi.ResourceAttributes{}, + User: "user", }, - msg: "must match metadata.namespace", }, - { - name: "nonresource", - obj: &authorizationapi.LocalSubjectAccessReview{ - ObjectMeta: metav1.ObjectMeta{Namespace: "a"}, - Spec: authorizationapi.SubjectAccessReviewSpec{ - NonResourceAttributes: &authorizationapi.NonResourceAttributes{}, - User: "user", - }, + msg: "must match metadata.namespace", + }, { + name: "nonresource", + obj: &authorizationapi.LocalSubjectAccessReview{ + ObjectMeta: metav1.ObjectMeta{Namespace: "a"}, + Spec: authorizationapi.SubjectAccessReviewSpec{ + NonResourceAttributes: &authorizationapi.NonResourceAttributes{}, + User: "user", }, - msg: "disallowed on this kind of request", }, - } + msg: "disallowed on this kind of request", + }} for _, c := range errorCases { errs := ValidateLocalSubjectAccessReview(c.obj) diff --git a/pkg/apis/autoscaling/validation/validation_test.go b/pkg/apis/autoscaling/validation/validation_test.go index 1eb9d896552..f7199db2720 100644 --- a/pkg/apis/autoscaling/validation/validation_test.go +++ b/pkg/apis/autoscaling/validation/validation_test.go @@ -31,35 +31,31 @@ import ( ) func TestValidateScale(t *testing.T) { - successCases := []autoscaling.Scale{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "frontend", - Namespace: metav1.NamespaceDefault, - }, - Spec: autoscaling.ScaleSpec{ - Replicas: 1, - }, + successCases := []autoscaling.Scale{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "frontend", + Namespace: metav1.NamespaceDefault, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "frontend", - Namespace: metav1.NamespaceDefault, - }, - Spec: autoscaling.ScaleSpec{ - Replicas: 10, - }, + Spec: autoscaling.ScaleSpec{ + Replicas: 1, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "frontend", - Namespace: metav1.NamespaceDefault, - }, - Spec: autoscaling.ScaleSpec{ - Replicas: 0, - }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "frontend", + Namespace: metav1.NamespaceDefault, }, - } + Spec: autoscaling.ScaleSpec{ + Replicas: 10, + }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "frontend", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.ScaleSpec{ + Replicas: 0, + }, + }} for _, successCase := range successCases { if errs := ValidateScale(&successCase); len(errs) != 0 { @@ -70,20 +66,18 @@ func TestValidateScale(t *testing.T) { errorCases := []struct { scale autoscaling.Scale msg string - }{ - { - scale: autoscaling.Scale{ - ObjectMeta: metav1.ObjectMeta{ - Name: "frontend", - Namespace: metav1.NamespaceDefault, - }, - Spec: autoscaling.ScaleSpec{ - Replicas: -1, - }, + }{{ + scale: autoscaling.Scale{ + ObjectMeta: metav1.ObjectMeta{ + Name: "frontend", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.ScaleSpec{ + Replicas: -1, }, - msg: "must be greater than or equal to 0", }, - } + msg: "must be greater than or equal to 0", + }} for _, c := range errorCases { if errs := ValidateScale(&c.scale); len(errs) == 0 { @@ -99,90 +93,73 @@ func TestValidateBehavior(t *testing.T) { minPolicy := autoscaling.MinPolicySelect disabledPolicy := autoscaling.DisabledPolicySelect incorrectPolicy := autoscaling.ScalingPolicySelect("incorrect") - simplePoliciesList := []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PercentScalingPolicy, - Value: 10, - PeriodSeconds: 1, + simplePoliciesList := []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PercentScalingPolicy, + Value: 10, + PeriodSeconds: 1, + }, { + Type: autoscaling.PodsScalingPolicy, + Value: 1, + PeriodSeconds: 1800, + }} + successCases := []autoscaling.HorizontalPodAutoscalerBehavior{{ + ScaleUp: nil, + ScaleDown: nil, + }, { + ScaleUp: &autoscaling.HPAScalingRules{ + StabilizationWindowSeconds: utilpointer.Int32(3600), + SelectPolicy: &minPolicy, + Policies: simplePoliciesList, }, - { - Type: autoscaling.PodsScalingPolicy, - Value: 1, - PeriodSeconds: 1800, + ScaleDown: &autoscaling.HPAScalingRules{ + StabilizationWindowSeconds: utilpointer.Int32(0), + SelectPolicy: &disabledPolicy, + Policies: simplePoliciesList, }, - } - successCases := []autoscaling.HorizontalPodAutoscalerBehavior{ - { - ScaleUp: nil, - ScaleDown: nil, + }, { + ScaleUp: &autoscaling.HPAScalingRules{ + StabilizationWindowSeconds: utilpointer.Int32(120), + SelectPolicy: &maxPolicy, + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + Value: 1, + PeriodSeconds: 2, + }, { + Type: autoscaling.PercentScalingPolicy, + Value: 3, + PeriodSeconds: 4, + }, { + Type: autoscaling.PodsScalingPolicy, + Value: 5, + PeriodSeconds: 6, + }, { + Type: autoscaling.PercentScalingPolicy, + Value: 7, + PeriodSeconds: 8, + }}, }, - { - ScaleUp: &autoscaling.HPAScalingRules{ - StabilizationWindowSeconds: utilpointer.Int32(3600), - SelectPolicy: &minPolicy, - Policies: simplePoliciesList, - }, - ScaleDown: &autoscaling.HPAScalingRules{ - StabilizationWindowSeconds: utilpointer.Int32(0), - SelectPolicy: &disabledPolicy, - Policies: simplePoliciesList, - }, + ScaleDown: &autoscaling.HPAScalingRules{ + StabilizationWindowSeconds: utilpointer.Int32(120), + SelectPolicy: &maxPolicy, + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + Value: 1, + PeriodSeconds: 2, + }, { + Type: autoscaling.PercentScalingPolicy, + Value: 3, + PeriodSeconds: 4, + }, { + Type: autoscaling.PodsScalingPolicy, + Value: 5, + PeriodSeconds: 6, + }, { + Type: autoscaling.PercentScalingPolicy, + Value: 7, + PeriodSeconds: 8, + }}, }, - { - ScaleUp: &autoscaling.HPAScalingRules{ - StabilizationWindowSeconds: utilpointer.Int32(120), - SelectPolicy: &maxPolicy, - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - Value: 1, - PeriodSeconds: 2, - }, - { - Type: autoscaling.PercentScalingPolicy, - Value: 3, - PeriodSeconds: 4, - }, - { - Type: autoscaling.PodsScalingPolicy, - Value: 5, - PeriodSeconds: 6, - }, - { - Type: autoscaling.PercentScalingPolicy, - Value: 7, - PeriodSeconds: 8, - }, - }, - }, - ScaleDown: &autoscaling.HPAScalingRules{ - StabilizationWindowSeconds: utilpointer.Int32(120), - SelectPolicy: &maxPolicy, - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - Value: 1, - PeriodSeconds: 2, - }, - { - Type: autoscaling.PercentScalingPolicy, - Value: 3, - PeriodSeconds: 4, - }, - { - Type: autoscaling.PodsScalingPolicy, - Value: 5, - PeriodSeconds: 6, - }, - { - Type: autoscaling.PercentScalingPolicy, - Value: 7, - PeriodSeconds: 8, - }, - }, - }, - }, - } + }} for _, behavior := range successCases { hpa := prepareHPAWithBehavior(behavior) if errs := ValidateHorizontalPodAutoscaler(&hpa); len(errs) != 0 { @@ -192,238 +169,191 @@ func TestValidateBehavior(t *testing.T) { errorCases := []struct { behavior autoscaling.HorizontalPodAutoscalerBehavior msg string - }{ - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - SelectPolicy: &minPolicy, - }, + }{{ + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleUp: &autoscaling.HPAScalingRules{ + SelectPolicy: &minPolicy, }, - msg: "spec.behavior.scaleUp.policies: Required value: must specify at least one Policy", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - StabilizationWindowSeconds: utilpointer.Int32(3601), - SelectPolicy: &minPolicy, - Policies: simplePoliciesList, - }, + msg: "spec.behavior.scaleUp.policies: Required value: must specify at least one Policy", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleUp: &autoscaling.HPAScalingRules{ + StabilizationWindowSeconds: utilpointer.Int32(3601), + SelectPolicy: &minPolicy, + Policies: simplePoliciesList, }, - msg: "spec.behavior.scaleUp.stabilizationWindowSeconds: Invalid value: 3601: must be less than or equal to 3600", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - Value: 7, - PeriodSeconds: 1801, - }, - }, - }, + msg: "spec.behavior.scaleUp.stabilizationWindowSeconds: Invalid value: 3601: must be less than or equal to 3600", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleUp: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + Value: 7, + PeriodSeconds: 1801, + }}, }, - msg: "spec.behavior.scaleUp.policies[0].periodSeconds: Invalid value: 1801: must be less than or equal to 1800", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - SelectPolicy: &incorrectPolicy, - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - Value: 7, - PeriodSeconds: 8, - }, - }, - }, + msg: "spec.behavior.scaleUp.policies[0].periodSeconds: Invalid value: 1801: must be less than or equal to 1800", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleUp: &autoscaling.HPAScalingRules{ + SelectPolicy: &incorrectPolicy, + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + Value: 7, + PeriodSeconds: 8, + }}, }, - msg: `spec.behavior.scaleUp.selectPolicy: Unsupported value: "incorrect": supported values: "Disabled", "Max", "Min"`, }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.HPAScalingPolicyType("hm"), - Value: 7, - PeriodSeconds: 8, - }, - }, - }, + msg: `spec.behavior.scaleUp.selectPolicy: Unsupported value: "incorrect": supported values: "Disabled", "Max", "Min"`, + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleUp: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.HPAScalingPolicyType("hm"), + Value: 7, + PeriodSeconds: 8, + }}, }, - msg: `spec.behavior.scaleUp.policies[0].type: Unsupported value: "hm": supported values: "Percent", "Pods"`, }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - Value: 8, - }, - }, - }, + msg: `spec.behavior.scaleUp.policies[0].type: Unsupported value: "hm": supported values: "Percent", "Pods"`, + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleUp: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + Value: 8, + }}, }, - msg: "spec.behavior.scaleUp.policies[0].periodSeconds: Invalid value: 0: must be greater than zero", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - PeriodSeconds: 8, - }, - }, - }, + msg: "spec.behavior.scaleUp.policies[0].periodSeconds: Invalid value: 0: must be greater than zero", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleUp: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + PeriodSeconds: 8, + }}, }, - msg: "spec.behavior.scaleUp.policies[0].value: Invalid value: 0: must be greater than zero", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - PeriodSeconds: -1, - Value: 1, - }, - }, - }, + msg: "spec.behavior.scaleUp.policies[0].value: Invalid value: 0: must be greater than zero", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleUp: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + PeriodSeconds: -1, + Value: 1, + }}, }, - msg: "spec.behavior.scaleUp.policies[0].periodSeconds: Invalid value: -1: must be greater than zero", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleUp: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - PeriodSeconds: 1, - Value: -1, - }, - }, - }, + msg: "spec.behavior.scaleUp.policies[0].periodSeconds: Invalid value: -1: must be greater than zero", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleUp: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + PeriodSeconds: 1, + Value: -1, + }}, }, - msg: "spec.behavior.scaleUp.policies[0].value: Invalid value: -1: must be greater than zero", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleDown: &autoscaling.HPAScalingRules{ - SelectPolicy: &minPolicy, - }, + msg: "spec.behavior.scaleUp.policies[0].value: Invalid value: -1: must be greater than zero", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleDown: &autoscaling.HPAScalingRules{ + SelectPolicy: &minPolicy, }, - msg: "spec.behavior.scaleDown.policies: Required value: must specify at least one Policy", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleDown: &autoscaling.HPAScalingRules{ - StabilizationWindowSeconds: utilpointer.Int32(3601), - SelectPolicy: &minPolicy, - Policies: simplePoliciesList, - }, + msg: "spec.behavior.scaleDown.policies: Required value: must specify at least one Policy", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleDown: &autoscaling.HPAScalingRules{ + StabilizationWindowSeconds: utilpointer.Int32(3601), + SelectPolicy: &minPolicy, + Policies: simplePoliciesList, }, - msg: "spec.behavior.scaleDown.stabilizationWindowSeconds: Invalid value: 3601: must be less than or equal to 3600", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleDown: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PercentScalingPolicy, - Value: 7, - PeriodSeconds: 1801, - }, - }, - }, + msg: "spec.behavior.scaleDown.stabilizationWindowSeconds: Invalid value: 3601: must be less than or equal to 3600", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleDown: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PercentScalingPolicy, + Value: 7, + PeriodSeconds: 1801, + }}, }, - msg: "spec.behavior.scaleDown.policies[0].periodSeconds: Invalid value: 1801: must be less than or equal to 1800", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleDown: &autoscaling.HPAScalingRules{ - SelectPolicy: &incorrectPolicy, - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - Value: 7, - PeriodSeconds: 8, - }, - }, - }, + msg: "spec.behavior.scaleDown.policies[0].periodSeconds: Invalid value: 1801: must be less than or equal to 1800", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleDown: &autoscaling.HPAScalingRules{ + SelectPolicy: &incorrectPolicy, + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + Value: 7, + PeriodSeconds: 8, + }}, }, - msg: `spec.behavior.scaleDown.selectPolicy: Unsupported value: "incorrect": supported values: "Disabled", "Max", "Min"`, }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleDown: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.HPAScalingPolicyType("hm"), - Value: 7, - PeriodSeconds: 8, - }, - }, - }, + msg: `spec.behavior.scaleDown.selectPolicy: Unsupported value: "incorrect": supported values: "Disabled", "Max", "Min"`, + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleDown: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.HPAScalingPolicyType("hm"), + Value: 7, + PeriodSeconds: 8, + }}, }, - msg: `spec.behavior.scaleDown.policies[0].type: Unsupported value: "hm": supported values: "Percent", "Pods"`, }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleDown: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - Value: 8, - }, - }, - }, + msg: `spec.behavior.scaleDown.policies[0].type: Unsupported value: "hm": supported values: "Percent", "Pods"`, + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleDown: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + Value: 8, + }}, }, - msg: "spec.behavior.scaleDown.policies[0].periodSeconds: Invalid value: 0: must be greater than zero", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleDown: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - PeriodSeconds: 8, - }, - }, - }, + msg: "spec.behavior.scaleDown.policies[0].periodSeconds: Invalid value: 0: must be greater than zero", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleDown: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + PeriodSeconds: 8, + }}, }, - msg: "spec.behavior.scaleDown.policies[0].value: Invalid value: 0: must be greater than zero", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleDown: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - PeriodSeconds: -1, - Value: 1, - }, - }, - }, + msg: "spec.behavior.scaleDown.policies[0].value: Invalid value: 0: must be greater than zero", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleDown: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + PeriodSeconds: -1, + Value: 1, + }}, }, - msg: "spec.behavior.scaleDown.policies[0].periodSeconds: Invalid value: -1: must be greater than zero", }, - { - behavior: autoscaling.HorizontalPodAutoscalerBehavior{ - ScaleDown: &autoscaling.HPAScalingRules{ - Policies: []autoscaling.HPAScalingPolicy{ - { - Type: autoscaling.PodsScalingPolicy, - PeriodSeconds: 1, - Value: -1, - }, - }, - }, + msg: "spec.behavior.scaleDown.policies[0].periodSeconds: Invalid value: -1: must be greater than zero", + }, { + behavior: autoscaling.HorizontalPodAutoscalerBehavior{ + ScaleDown: &autoscaling.HPAScalingRules{ + Policies: []autoscaling.HPAScalingPolicy{{ + Type: autoscaling.PodsScalingPolicy, + PeriodSeconds: 1, + Value: -1, + }}, }, - msg: "spec.behavior.scaleDown.policies[0].value: Invalid value: -1: must be greater than zero", }, - } + msg: "spec.behavior.scaleDown.policies[0].value: Invalid value: -1: must be greater than zero", + }} for _, c := range errorCases { hpa := prepareHPAWithBehavior(c.behavior) if errs := ValidateHorizontalPodAutoscaler(&hpa); len(errs) == 0 { @@ -447,18 +377,16 @@ func prepareHPAWithBehavior(b autoscaling.HorizontalPodAutoscalerBehavior) autos }, MinReplicas: utilpointer.Int32(1), MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, Behavior: &b, }, } @@ -470,246 +398,220 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { t.Errorf("unable to parse label selector: %v", err) } - successCases := []autoscaling.HorizontalPodAutoscaler{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, + successCases := []autoscaling.HorizontalPodAutoscaler{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, - }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, - AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", + }, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.AverageValueMetricType, + AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.PodsMetricSourceType, - Pods: &autoscaling.PodsMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, - AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.PodsMetricSourceType, + Pods: &autoscaling.PodsMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.AverageValueMetricType, + AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Container: "test-container", - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Container: "test-container", + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Container: "test-container", - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, - AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Container: "test-container", + Target: autoscaling.MetricTarget{ + Type: autoscaling.AverageValueMetricType, + AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ObjectMetricSourceType, - Object: &autoscaling.ObjectMetricSource{ - DescribedObject: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - Value: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ObjectMetricSourceType, + Object: &autoscaling.ObjectMetricSource{ + DescribedObject: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", + }, + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + Value: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - Value: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + Value: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, - AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.AverageValueMetricType, + AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - } + }} for _, successCase := range successCases { if errs := ValidateHorizontalPodAutoscaler(&successCase); len(errs) != 0 { t.Errorf("expected success: %v", errs) @@ -719,666 +621,586 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { errorCases := []struct { horizontalPodAutoscaler autoscaling.HorizontalPodAutoscaler msg string - }{ - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + }{{ + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "scaleTargetRef.kind: Required", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Container: "test-application", - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + msg: "scaleTargetRef.kind: Required", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Container: "test-application", + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "scaleTargetRef.kind: Required", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "..", Name: "myrc"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + msg: "scaleTargetRef.kind: Required", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "..", Name: "myrc"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "scaleTargetRef.kind: Invalid", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "..", Name: "myrc"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Container: "test-application", - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + msg: "scaleTargetRef.kind: Invalid", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "..", Name: "myrc"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Container: "test-application", + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "scaleTargetRef.kind: Invalid", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + msg: "scaleTargetRef.kind: Invalid", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "scaleTargetRef.name: Required", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Container: "test-application", - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + msg: "scaleTargetRef.name: Required", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Container: "test-application", + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "scaleTargetRef.name: Required", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "ReplicationController", Name: ".."}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + msg: "scaleTargetRef.name: Required", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "ReplicationController", Name: ".."}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "scaleTargetRef.name: Invalid", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "ReplicationController", Name: ".."}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Container: "test-application", - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + msg: "scaleTargetRef.name: Invalid", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Kind: "ReplicationController", Name: ".."}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Container: "test-application", + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "scaleTargetRef.name: Invalid", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, - }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{}, - MinReplicas: utilpointer.Int32(-1), - MaxReplicas: 5, - }, + msg: "scaleTargetRef.name: Invalid", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, }, - msg: "must be greater than or equal to 1", - }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, - }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{}, - MinReplicas: utilpointer.Int32(7), - MaxReplicas: 5, - }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{}, + MinReplicas: utilpointer.Int32(-1), + MaxReplicas: 5, }, - msg: "must be greater than or equal to `minReplicas`", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, - }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + msg: "must be greater than or equal to 1", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{}, + MinReplicas: utilpointer.Int32(7), + MaxReplicas: 5, + }, + }, + msg: "must be greater than or equal to `minReplicas`", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), + AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - msg: "may not set both a target raw value and a target utilization", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, - }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Container: "test-application", - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + msg: "may not set both a target raw value and a target utilization", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Container: "test-application", + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), + AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - msg: "may not set both a target raw value and a target utilization", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + msg: "may not set both a target raw value and a target utilization", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "must specify a resource name", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Container: "test-application", - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + msg: "must specify a resource name", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Container: "test-application", + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "must specify a resource name", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: "InvalidResource", - Container: "test-application", - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(70), - }, - }, + msg: "must specify a resource name", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: "InvalidResource", + Container: "test-application", + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(70), }, }, - }, + }}, }, - msg: "Invalid value: \"InvalidResource\": must be a standard resource type or fully qualified", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(-10), - }, - }, + msg: "Invalid value: \"InvalidResource\": must be a standard resource type or fully qualified", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(-10), }, }, - }, + }}, }, - msg: "must be greater than 0", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Container: "test-application", - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(-10), - }, - }, + msg: "must be greater than 0", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Container: "test-application", + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(-10), }, }, - }, + }}, }, - msg: "must be greater than 0", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(-10), - }, - }, + msg: "must be greater than 0", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(-10), }, }, - }, + }}, }, - msg: "must specify a container", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Container: "---***", - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, - AverageUtilization: utilpointer.Int32(-10), - }, - }, + msg: "must specify a container", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Container: "---***", + Target: autoscaling.MetricTarget{ + Type: autoscaling.UtilizationMetricType, + AverageUtilization: utilpointer.Int32(-10), }, }, - }, + }}, }, - msg: "Invalid value: \"---***\"", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - }, - }, + msg: "Invalid value: \"---***\"", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, }, }, - }, + }}, }, - msg: "must set either a target raw value or a target utilization", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ContainerResourceMetricSourceType, - ContainerResource: &autoscaling.ContainerResourceMetricSource{ - Name: api.ResourceCPU, - Container: "test-application", - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - }, - }, + msg: "must set either a target raw value or a target utilization", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: api.ResourceCPU, + Container: "test-application", + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, }, }, - }, + }}, }, - msg: "must set either a target raw value or a target utilization", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.PodsMetricSourceType, - Pods: &autoscaling.PodsMetricSource{ - Metric: autoscaling.MetricIdentifier{}, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), - }, - }, + msg: "must set either a target raw value or a target utilization", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.PodsMetricSourceType, + Pods: &autoscaling.PodsMetricSource{ + Metric: autoscaling.MetricIdentifier{}, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, - }, + }}, }, - msg: "must specify a metric name", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.PodsMetricSourceType, - Pods: &autoscaling.PodsMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - }, - }, + msg: "must specify a metric name", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.PodsMetricSourceType, + Pods: &autoscaling.PodsMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, }, }, - }, + }}, }, - msg: "must specify a positive target averageValue", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ObjectMetricSourceType, - Object: &autoscaling.ObjectMetricSource{ - DescribedObject: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - }, - }, + msg: "must specify a positive target averageValue", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ObjectMetricSourceType, + Object: &autoscaling.ObjectMetricSource{ + DescribedObject: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", + }, + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, }, }, - }, + }}, }, - msg: "must set either a target value or averageValue", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ObjectMetricSourceType, - Object: &autoscaling.ObjectMetricSource{ - DescribedObject: autoscaling.CrossVersionObjectReference{ - Name: "myrc", - }, - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - Value: resource.NewMilliQuantity(100, resource.DecimalSI), - }, - }, + msg: "must set either a target value or averageValue", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ObjectMetricSourceType, + Object: &autoscaling.ObjectMetricSource{ + DescribedObject: autoscaling.CrossVersionObjectReference{ + Name: "myrc", + }, + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + Value: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, - }, + }}, }, - msg: "object.describedObject.kind: Required", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ObjectMetricSourceType, - Object: &autoscaling.ObjectMetricSource{ - DescribedObject: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - Value: resource.NewMilliQuantity(100, resource.DecimalSI), - }, - }, + msg: "object.describedObject.kind: Required", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ObjectMetricSourceType, + Object: &autoscaling.ObjectMetricSource{ + DescribedObject: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + Value: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, - }, + }}, }, - msg: "must specify a metric name", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - Value: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + msg: "must specify a metric name", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + Value: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - msg: "must specify a metric name", }, - { - horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, - MinReplicas: utilpointer.Int32(1), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "foo/../", - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - Value: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + msg: "must specify a metric name", + }, { + horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, + MinReplicas: utilpointer.Int32(1), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "foo/../", + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + Value: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - msg: "'/'", }, + msg: "'/'", + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ @@ -1387,177 +1209,156 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, MinReplicas: utilpointer.Int32(1), MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - }, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, }, }, - }, + }}, }, }, msg: "must set either a target value for metric or a per-pod target", - }, - { + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, MinReplicas: utilpointer.Int32(1), MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - Value: resource.NewMilliQuantity(-300, resource.DecimalSI), - }, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + Value: resource.NewMilliQuantity(-300, resource.DecimalSI), }, }, - }, + }}, }, }, msg: "must be positive", - }, - { + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, MinReplicas: utilpointer.Int32(1), MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - AverageValue: resource.NewMilliQuantity(-300, resource.DecimalSI), - }, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + AverageValue: resource.NewMilliQuantity(-300, resource.DecimalSI), }, }, - }, + }}, }, }, msg: "must be positive", - }, - { + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, MinReplicas: utilpointer.Int32(1), MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - Value: resource.NewMilliQuantity(300, resource.DecimalSI), - AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), - }, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + Value: resource.NewMilliQuantity(300, resource.DecimalSI), + AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, }, msg: "may not set both a target value for metric and a per-pod target", - }, - { + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, MinReplicas: utilpointer.Int32(1), MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Type: "boogity", - Value: resource.NewMilliQuantity(300, resource.DecimalSI), - }, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Type: "boogity", + Value: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, }, msg: "must be either Utilization, Value, or AverageValue", - }, - { + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, MinReplicas: utilpointer.Int32(1), MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Value: resource.NewMilliQuantity(300, resource.DecimalSI), - }, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Value: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, }, msg: "must specify a metric target type", - }, - { + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, MinReplicas: utilpointer.Int32(1), MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - Selector: metricLabelSelector, - }, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + Selector: metricLabelSelector, }, }, - }, + }}, }, }, msg: "must specify a metric target", - }, - { + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ @@ -1570,51 +1371,45 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { }, }, msg: "must specify a metric source type", - }, - { + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, MinReplicas: utilpointer.Int32(1), MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.MetricSourceType("InvalidType"), - }, - }, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.MetricSourceType("InvalidType"), + }}, }, }, msg: "type: Unsupported value", - }, - { + }, { horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{Name: "myrc", Kind: "ReplicationController"}, MinReplicas: utilpointer.Int32(1), MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, - AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), - }, - }, - Pods: &autoscaling.PodsMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, - AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), - }, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ResourceMetricSourceType, + Resource: &autoscaling.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: autoscaling.MetricTarget{ + Type: autoscaling.AverageValueMetricType, + AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, - }, + Pods: &autoscaling.PodsMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.AverageValueMetricType, + AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), + }, + }, + }}, }, }, msg: "must populate the given metric source only", @@ -1711,71 +1506,64 @@ func prepareMinReplicasCases(t *testing.T, minReplicas int32) []autoscaling.Hori if err != nil { t.Errorf("unable to parse label selector: %v", err) } - minReplicasCases := []autoscaling.HorizontalPodAutoscaler{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, - ResourceVersion: "theversion", + minReplicasCases := []autoscaling.HorizontalPodAutoscaler{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "theversion", + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(minReplicas), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ObjectMetricSourceType, - Object: &autoscaling.ObjectMetricSource{ - DescribedObject: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, - Value: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + MinReplicas: utilpointer.Int32(minReplicas), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ObjectMetricSourceType, + Object: &autoscaling.ObjectMetricSource{ + DescribedObject: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", + }, + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.ValueMetricType, + Value: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "myautoscaler", - Namespace: metav1.NamespaceDefault, - ResourceVersion: "theversion", + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "myautoscaler", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "theversion", + }, + Spec: autoscaling.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + Kind: "ReplicationController", + Name: "myrc", }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ - Kind: "ReplicationController", - Name: "myrc", - }, - MinReplicas: utilpointer.Int32(minReplicas), - MaxReplicas: 5, - Metrics: []autoscaling.MetricSpec{ - { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ - Name: "somemetric", - Selector: metricLabelSelector, - }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, - AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), - }, - }, + MinReplicas: utilpointer.Int32(minReplicas), + MaxReplicas: 5, + Metrics: []autoscaling.MetricSpec{{ + Type: autoscaling.ExternalMetricSourceType, + External: &autoscaling.ExternalMetricSource{ + Metric: autoscaling.MetricIdentifier{ + Name: "somemetric", + Selector: metricLabelSelector, + }, + Target: autoscaling.MetricTarget{ + Type: autoscaling.AverageValueMetricType, + AverageValue: resource.NewMilliQuantity(300, resource.DecimalSI), }, }, - }, + }}, }, - } + }} return minReplicasCases } diff --git a/pkg/apis/batch/validation/validation_test.go b/pkg/apis/batch/validation/validation_test.go index af5828d284e..ad82d9cf516 100644 --- a/pkg/apis/batch/validation/validation_test.go +++ b/pkg/apis/batch/validation/validation_test.go @@ -112,49 +112,39 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Type: api.DisruptionTarget, - Status: api.ConditionTrue, - }, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Type: api.DisruptionTarget, + Status: api.ConditionTrue, + }}, + }, { + Action: batch.PodFailurePolicyActionFailJob, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Type: api.PodConditionType("CustomConditionType"), + Status: api.ConditionFalse, + }}, + }, { + Action: batch.PodFailurePolicyActionCount, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + ContainerName: pointer.String("abc"), + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{1, 2, 3}, }, - { - Action: batch.PodFailurePolicyActionFailJob, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Type: api.PodConditionType("CustomConditionType"), - Status: api.ConditionFalse, - }, - }, + }, { + Action: batch.PodFailurePolicyActionIgnore, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + ContainerName: pointer.String("def"), + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{4}, }, - { - Action: batch.PodFailurePolicyActionCount, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - ContainerName: pointer.String("abc"), - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{1, 2, 3}, - }, + }, { + Action: batch.PodFailurePolicyActionFailJob, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + Operator: batch.PodFailurePolicyOnExitCodesOpNotIn, + Values: []int32{5, 6, 7}, }, - { - Action: batch.PodFailurePolicyActionIgnore, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - ContainerName: pointer.String("def"), - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{4}, - }, - }, - { - Action: batch.PodFailurePolicyActionFailJob, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - Operator: batch.PodFailurePolicyOnExitCodesOpNotIn, - Values: []int32{5, 6, 7}, - }, - }, - }, + }}, }, }, }, @@ -314,11 +304,9 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionFailJob, - }, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionFailJob, + }}, }, }, }, @@ -331,15 +319,13 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionFailJob, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{11, 11}, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionFailJob, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{11, 11}, }, - }, + }}, }, }, }, @@ -352,21 +338,19 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionFailJob, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: func() (values []int32) { - tooManyValues := make([]int32, maxPodFailurePolicyOnExitCodesValues+1) - for i := range tooManyValues { - tooManyValues[i] = int32(i) - } - return tooManyValues - }(), - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionFailJob, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: func() (values []int32) { + tooManyValues := make([]int32, maxPodFailurePolicyOnExitCodesValues+1) + for i := range tooManyValues { + tooManyValues[i] = int32(i) + } + return tooManyValues + }(), }, - }, + }}, }, }, }, @@ -404,21 +388,19 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionFailJob, - OnPodConditions: func() []batch.PodFailurePolicyOnPodConditionsPattern { - tooManyPatterns := make([]batch.PodFailurePolicyOnPodConditionsPattern, maxPodFailurePolicyOnPodConditionsPatterns+1) - for i := range tooManyPatterns { - tooManyPatterns[i] = batch.PodFailurePolicyOnPodConditionsPattern{ - Type: api.PodConditionType(fmt.Sprintf("CustomType_%d", i)), - Status: api.ConditionTrue, - } + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionFailJob, + OnPodConditions: func() []batch.PodFailurePolicyOnPodConditionsPattern { + tooManyPatterns := make([]batch.PodFailurePolicyOnPodConditionsPattern, maxPodFailurePolicyOnPodConditionsPatterns+1) + for i := range tooManyPatterns { + tooManyPatterns[i] = batch.PodFailurePolicyOnPodConditionsPattern{ + Type: api.PodConditionType(fmt.Sprintf("CustomType_%d", i)), + Status: api.ConditionTrue, } - return tooManyPatterns - }(), - }, - }, + } + return tooManyPatterns + }(), + }}, }, }, }, @@ -431,15 +413,13 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionFailJob, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{12, 13, 13, 13}, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionFailJob, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{12, 13, 13, 13}, }, - }, + }}, }, }, }, @@ -452,15 +432,13 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionFailJob, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{19, 11}, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionFailJob, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{19, 11}, }, - }, + }}, }, }, }, @@ -473,15 +451,13 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionFailJob, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{}, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionFailJob, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{}, }, - }, + }}, }, }, }, @@ -494,15 +470,13 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: "", - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{1, 2, 3}, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: "", + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{1, 2, 3}, }, - }, + }}, }, }, }, @@ -515,15 +489,13 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionFailJob, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - Operator: "", - Values: []int32{1, 2, 3}, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionFailJob, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + Operator: "", + Values: []int32{1, 2, 3}, }, - }, + }}, }, }, }, @@ -536,22 +508,18 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionFailJob, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - ContainerName: pointer.String("abc"), - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{1, 2, 3}, - }, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Type: api.DisruptionTarget, - Status: api.ConditionTrue, - }, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionFailJob, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + ContainerName: pointer.String("abc"), + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{1, 2, 3}, }, - }, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Type: api.DisruptionTarget, + Status: api.ConditionTrue, + }}, + }}, }, }, }, @@ -564,15 +532,13 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{1, 0, 2}, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{1, 0, 2}, }, - }, + }}, }, }, }, @@ -585,24 +551,21 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - ContainerName: pointer.String("abc"), - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{1, 2, 3}, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + ContainerName: pointer.String("abc"), + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{1, 2, 3}, }, - { - Action: batch.PodFailurePolicyActionFailJob, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - ContainerName: pointer.String("xyz"), - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{5, 6, 7}, - }, + }, { + Action: batch.PodFailurePolicyActionFailJob, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + ContainerName: pointer.String("xyz"), + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{5, 6, 7}, }, - }, + }}, }, }, }, @@ -615,16 +578,14 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: "UnknownAction", - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - ContainerName: pointer.String("abc"), - Operator: batch.PodFailurePolicyOnExitCodesOpIn, - Values: []int32{1, 2, 3}, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: "UnknownAction", + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + ContainerName: pointer.String("abc"), + Operator: batch.PodFailurePolicyOnExitCodesOpIn, + Values: []int32{1, 2, 3}, }, - }, + }}, }, }, }, @@ -637,15 +598,13 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ - Operator: "UnknownOperator", - Values: []int32{1, 2, 3}, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{ + Operator: "UnknownOperator", + Values: []int32{1, 2, 3}, }, - }, + }}, }, }, }, @@ -658,16 +617,12 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Type: api.DisruptionTarget, - }, - }, - }, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Type: api.DisruptionTarget, + }}, + }}, }, }, }, @@ -680,17 +635,13 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Type: api.DisruptionTarget, - Status: "UnknownStatus", - }, - }, - }, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Type: api.DisruptionTarget, + Status: "UnknownStatus", + }}, + }}, }, }, }, @@ -703,16 +654,12 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Status: api.ConditionTrue, - }, - }, - }, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Status: api.ConditionTrue, + }}, + }}, }, }, }, @@ -725,17 +672,13 @@ func TestValidateJob(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Type: api.PodConditionType("Invalid Condition Type"), - Status: api.ConditionTrue, - }, - }, - }, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Type: api.PodConditionType("Invalid Condition Type"), + Status: api.ConditionTrue, + }}, + }}, }, }, }, @@ -1116,17 +1059,13 @@ func TestValidateJobUpdate(t *testing.T) { validNodeAffinity := &api.Affinity{ NodeAffinity: &api.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "foo", - Operator: api.NodeSelectorOpIn, - Values: []string{"bar", "value2"}, - }, - }, - }, - }, + NodeSelectorTerms: []api.NodeSelectorTerm{{ + MatchExpressions: []api.NodeSelectorRequirement{{ + Key: "foo", + Operator: api.NodeSelectorOpIn, + Values: []string{"bar", "value2"}, + }}, + }}, }, }, } @@ -1134,17 +1073,13 @@ func TestValidateJobUpdate(t *testing.T) { validPodTemplateWithAffinity.Spec.Affinity = &api.Affinity{ NodeAffinity: &api.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "foo", - Operator: api.NodeSelectorOpIn, - Values: []string{"bar", "value"}, - }, - }, - }, - }, + NodeSelectorTerms: []api.NodeSelectorTerm{{ + MatchExpressions: []api.NodeSelectorRequirement{{ + Key: "foo", + Operator: api.NodeSelectorOpIn, + Values: []string{"bar", "value"}, + }}, + }}, }, }, } @@ -1242,17 +1177,13 @@ func TestValidateJobUpdate(t *testing.T) { }, update: func(job *batch.Job) { job.Spec.PodFailurePolicy = &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Type: api.DisruptionTarget, - Status: api.ConditionTrue, - }, - }, - }, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Type: api.DisruptionTarget, + Status: api.ConditionTrue, + }}, + }}, } }, err: &field.Error{ @@ -1267,29 +1198,23 @@ func TestValidateJobUpdate(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Type: api.DisruptionTarget, - Status: api.ConditionTrue, - }, - }, - }, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Type: api.DisruptionTarget, + Status: api.ConditionTrue, + }}, + }}, }, }, }, update: func(job *batch.Job) { job.Spec.PodFailurePolicy.Rules = append(job.Spec.PodFailurePolicy.Rules, batch.PodFailurePolicyRule{ Action: batch.PodFailurePolicyActionCount, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Type: api.DisruptionTarget, - Status: api.ConditionTrue, - }, - }, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Type: api.DisruptionTarget, + Status: api.ConditionTrue, + }}, }) }, err: &field.Error{ @@ -1304,17 +1229,13 @@ func TestValidateJobUpdate(t *testing.T) { Selector: validGeneratedSelector, Template: validPodTemplateSpecForGeneratedRestartPolicyNever, PodFailurePolicy: &batch.PodFailurePolicy{ - Rules: []batch.PodFailurePolicyRule{ - { - Action: batch.PodFailurePolicyActionIgnore, - OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{ - { - Type: api.DisruptionTarget, - Status: api.ConditionTrue, - }, - }, - }, - }, + Rules: []batch.PodFailurePolicyRule{{ + Action: batch.PodFailurePolicyActionIgnore, + OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{ + Type: api.DisruptionTarget, + Status: api.ConditionTrue, + }}, + }}, }, }, }, diff --git a/pkg/apis/certificates/validation/validation_test.go b/pkg/apis/certificates/validation/validation_test.go index 988b4c5be6f..bd4fc0cd93f 100644 --- a/pkg/apis/certificates/validation/validation_test.go +++ b/pkg/apis/certificates/validation/validation_test.go @@ -413,90 +413,79 @@ func Test_getValidationOptions(t *testing.T) { newCSR *capi.CertificateSigningRequest oldCSR *capi.CertificateSigningRequest want certificateValidationOptions - }{ - { - name: "strict create", - oldCSR: nil, - want: certificateValidationOptions{}, + }{{ + name: "strict create", + oldCSR: nil, + want: certificateValidationOptions{}, + }, { + name: "strict update", + oldCSR: &capi.CertificateSigningRequest{}, + want: certificateValidationOptions{}, + }, { + name: "compatible update, approved+denied", + oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateDenied}}, + }}, + want: certificateValidationOptions{ + allowBothApprovedAndDenied: true, }, - { - name: "strict update", - oldCSR: &capi.CertificateSigningRequest{}, - want: certificateValidationOptions{}, + }, { + name: "compatible update, legacy signerName", + oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{SignerName: capi.LegacyUnknownSignerName}}, + want: certificateValidationOptions{ + allowLegacySignerName: true, }, - { - name: "compatible update, approved+denied", - oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateDenied}}, - }}, - want: certificateValidationOptions{ - allowBothApprovedAndDenied: true, - }, + }, { + name: "compatible update, duplicate condition types", + oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateApproved}}, + }}, + want: certificateValidationOptions{ + allowDuplicateConditionTypes: true, }, - { - name: "compatible update, legacy signerName", - oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{SignerName: capi.LegacyUnknownSignerName}}, - want: certificateValidationOptions{ - allowLegacySignerName: true, - }, + }, { + name: "compatible update, empty condition types", + oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{}}, + }}, + want: certificateValidationOptions{ + allowEmptyConditionType: true, }, - { - name: "compatible update, duplicate condition types", - oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateApproved}}, - }}, - want: certificateValidationOptions{ - allowDuplicateConditionTypes: true, - }, + }, { + name: "compatible update, no diff to certificate", + newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ + Certificate: validCertificate, + }}, + oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ + Certificate: validCertificate, + }}, + want: certificateValidationOptions{ + allowArbitraryCertificate: true, }, - { - name: "compatible update, empty condition types", - oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{}}, - }}, - want: certificateValidationOptions{ - allowEmptyConditionType: true, - }, + }, { + name: "compatible update, existing invalid certificate", + newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ + Certificate: []byte(`new - no PEM blocks`), + }}, + oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ + Certificate: []byte(`old - no PEM blocks`), + }}, + want: certificateValidationOptions{ + allowArbitraryCertificate: true, }, - { - name: "compatible update, no diff to certificate", - newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ - Certificate: validCertificate, - }}, - oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ - Certificate: validCertificate, - }}, - want: certificateValidationOptions{ - allowArbitraryCertificate: true, - }, + }, { + name: "compatible update, existing unknown usages", + oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"unknown"}}}, + want: certificateValidationOptions{ + allowUnknownUsages: true, }, - { - name: "compatible update, existing invalid certificate", - newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ - Certificate: []byte(`new - no PEM blocks`), - }}, - oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ - Certificate: []byte(`old - no PEM blocks`), - }}, - want: certificateValidationOptions{ - allowArbitraryCertificate: true, - }, + }, { + name: "compatible update, existing duplicate usages", + oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"any", "any"}}}, + want: certificateValidationOptions{ + allowDuplicateUsages: true, }, - { - name: "compatible update, existing unknown usages", - oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"unknown"}}}, - want: certificateValidationOptions{ - allowUnknownUsages: true, - }, - }, - { - name: "compatible update, existing duplicate usages", - oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"any", "any"}}}, - want: certificateValidationOptions{ - allowDuplicateUsages: true, - }, - }, - } + }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getValidationOptions(tt.newCSR, tt.oldCSR); !reflect.DeepEqual(got, tt.want) { @@ -524,86 +513,76 @@ func TestValidateCertificateSigningRequestUpdate(t *testing.T) { newCSR *capi.CertificateSigningRequest oldCSR *capi.CertificateSigningRequest errs []string - }{ - { - name: "no-op", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + }{{ + name: "no-op", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + }, { + name: "finalizer change with invalid status", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, + }, { + name: "add Approved condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{ + `status.conditions: Forbidden: updates may not add a condition of type "Approved"`, }, - { - name: "finalizer change with invalid status", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, + }, { + name: "remove Approved condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, + }}, + errs: []string{ + `status.conditions: Forbidden: updates may not remove a condition of type "Approved"`, }, - { - name: "add Approved condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{ - `status.conditions: Forbidden: updates may not add a condition of type "Approved"`, - }, + }, { + name: "add Denied condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{ + `status.conditions: Forbidden: updates may not add a condition of type "Denied"`, }, - { - name: "remove Approved condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, - }}, - errs: []string{ - `status.conditions: Forbidden: updates may not remove a condition of type "Approved"`, - }, + }, { + name: "remove Denied condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, + }}, + errs: []string{ + `status.conditions: Forbidden: updates may not remove a condition of type "Denied"`, }, - { - name: "add Denied condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{ - `status.conditions: Forbidden: updates may not add a condition of type "Denied"`, - }, + }, { + name: "add Failed condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{}, + }, { + name: "remove Failed condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, + }}, + errs: []string{ + `status.conditions: Forbidden: updates may not remove a condition of type "Failed"`, }, - { - name: "remove Denied condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, - }}, - errs: []string{ - `status.conditions: Forbidden: updates may not remove a condition of type "Denied"`, - }, + }, { + name: "set certificate", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Certificate: validCertificate, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{ + `status.certificate: Forbidden: updates may not set certificate content`, }, - { - name: "add Failed condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{}, - }, - { - name: "remove Failed condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, - }}, - errs: []string{ - `status.conditions: Forbidden: updates may not remove a condition of type "Failed"`, - }, - }, - { - name: "set certificate", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Certificate: validCertificate, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{ - `status.certificate: Forbidden: updates may not set certificate content`, - }, - }, - } + }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -640,119 +619,106 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) { newCSR *capi.CertificateSigningRequest oldCSR *capi.CertificateSigningRequest errs []string - }{ - { - name: "no-op", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + }{{ + name: "no-op", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + }, { + name: "finalizer change with invalid status", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, + }, { + name: "finalizer change with duplicate and unknown usages", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: capi.CertificateSigningRequestSpec{ + Usages: []capi.KeyUsage{"unknown", "unknown"}, + Request: newCSRPEM(t), + SignerName: validSignerName, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: capi.CertificateSigningRequestSpec{ + Usages: []capi.KeyUsage{"unknown", "unknown"}, + Request: newCSRPEM(t), + SignerName: validSignerName, + }}, + }, { + name: "add Approved condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{ + `status.conditions: Forbidden: updates may not add a condition of type "Approved"`, }, - { - name: "finalizer change with invalid status", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, + }, { + name: "remove Approved condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, + }}, + errs: []string{ + `status.conditions: Forbidden: updates may not remove a condition of type "Approved"`, }, - { - name: "finalizer change with duplicate and unknown usages", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: capi.CertificateSigningRequestSpec{ - Usages: []capi.KeyUsage{"unknown", "unknown"}, - Request: newCSRPEM(t), - SignerName: validSignerName, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: capi.CertificateSigningRequestSpec{ - Usages: []capi.KeyUsage{"unknown", "unknown"}, - Request: newCSRPEM(t), - SignerName: validSignerName, - }}, + }, { + name: "add Denied condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{ + `status.conditions: Forbidden: updates may not add a condition of type "Denied"`, }, - { - name: "add Approved condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{ - `status.conditions: Forbidden: updates may not add a condition of type "Approved"`, - }, + }, { + name: "remove Denied condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, + }}, + errs: []string{ + `status.conditions: Forbidden: updates may not remove a condition of type "Denied"`, }, - { - name: "remove Approved condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, - }}, - errs: []string{ - `status.conditions: Forbidden: updates may not remove a condition of type "Approved"`, - }, + }, { + name: "add Failed condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{}, + }, { + name: "remove Failed condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, + }}, + errs: []string{ + `status.conditions: Forbidden: updates may not remove a condition of type "Failed"`, }, - { - name: "add Denied condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{ - `status.conditions: Forbidden: updates may not add a condition of type "Denied"`, - }, + }, { + name: "set valid certificate", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Certificate: validCertificate, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{}, + }, { + name: "set invalid certificate", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Certificate: invalidCertificateNoPEM, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{ + `status.certificate: Invalid value: "": must contain at least one CERTIFICATE PEM block`, }, - { - name: "remove Denied condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, - }}, - errs: []string{ - `status.conditions: Forbidden: updates may not remove a condition of type "Denied"`, - }, + }, { + name: "reset certificate", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Certificate: invalidCertificateNonCertificatePEM, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Certificate: invalidCertificateNoPEM, + }}, + errs: []string{ + `status.certificate: Forbidden: updates may not modify existing certificate content`, }, - { - name: "add Failed condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{}, - }, - { - name: "remove Failed condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, - }}, - errs: []string{ - `status.conditions: Forbidden: updates may not remove a condition of type "Failed"`, - }, - }, - { - name: "set valid certificate", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Certificate: validCertificate, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{}, - }, - { - name: "set invalid certificate", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Certificate: invalidCertificateNoPEM, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{ - `status.certificate: Invalid value: "": must contain at least one CERTIFICATE PEM block`, - }, - }, - { - name: "reset certificate", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Certificate: invalidCertificateNonCertificatePEM, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Certificate: invalidCertificateNoPEM, - }}, - errs: []string{ - `status.certificate: Forbidden: updates may not modify existing certificate content`, - }, - }, - } + }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -789,80 +755,70 @@ func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) { newCSR *capi.CertificateSigningRequest oldCSR *capi.CertificateSigningRequest errs []string - }{ - { - name: "no-op", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + }{{ + name: "no-op", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + }, { + name: "finalizer change with invalid certificate", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, + }, { + name: "add Approved condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + }, { + name: "remove Approved condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, + }}, + errs: []string{ + `status.conditions: Forbidden: updates may not remove a condition of type "Approved"`, }, - { - name: "finalizer change with invalid certificate", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, + }, { + name: "add Denied condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + }, { + name: "remove Denied condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, + }}, + errs: []string{ + `status.conditions: Forbidden: updates may not remove a condition of type "Denied"`, }, - { - name: "add Approved condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + }, { + name: "add Failed condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{}, + }, { + name: "remove Failed condition", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, + }}, + errs: []string{ + `status.conditions: Forbidden: updates may not remove a condition of type "Failed"`, }, - { - name: "remove Approved condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, - }}, - errs: []string{ - `status.conditions: Forbidden: updates may not remove a condition of type "Approved"`, - }, + }, { + name: "set certificate", + newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ + Certificate: validCertificate, + }}, + oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, + errs: []string{ + `status.certificate: Forbidden: updates may not set certificate content`, }, - { - name: "add Denied condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - }, - { - name: "remove Denied condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, - }}, - errs: []string{ - `status.conditions: Forbidden: updates may not remove a condition of type "Denied"`, - }, - }, - { - name: "add Failed condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{}, - }, - { - name: "remove Failed condition", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, - }}, - errs: []string{ - `status.conditions: Forbidden: updates may not remove a condition of type "Failed"`, - }, - }, - { - name: "set certificate", - newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ - Certificate: validCertificate, - }}, - oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, - errs: []string{ - `status.certificate: Forbidden: updates may not set certificate content`, - }, - }, - } + }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -909,32 +865,28 @@ func Test_validateCertificateSigningRequestOptions(t *testing.T) { { name: "no status", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec}, - }, - { + }, { name: "approved condition", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, }, }, - }, - { + }, { name: "denied condition", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, }, }, - }, - { + }, { name: "failed condition", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, }, }, - }, - { + }, { name: "approved+issued", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ @@ -969,8 +921,7 @@ func Test_validateCertificateSigningRequestOptions(t *testing.T) { }, lenientOpts: certificateValidationOptions{allowEmptyConditionType: true}, strictErrs: []string{`status.conditions[0].type: Required value`}, - }, - { + }, { name: "approved and denied", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ @@ -979,8 +930,7 @@ func Test_validateCertificateSigningRequestOptions(t *testing.T) { }, lenientOpts: certificateValidationOptions{allowBothApprovedAndDenied: true}, strictErrs: []string{`status.conditions[1].type: Invalid value: "Denied": Approved and Denied conditions are mutually exclusive`}, - }, - { + }, { name: "duplicate condition", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ @@ -1002,8 +952,7 @@ func Test_validateCertificateSigningRequestOptions(t *testing.T) { }, lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, strictErrs: []string{`status.certificate: Invalid value: "": must contain at least one CERTIFICATE PEM block`}, - }, - { + }, { name: "status.certificate, non-CERTIFICATE PEM", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ @@ -1013,8 +962,7 @@ func Test_validateCertificateSigningRequestOptions(t *testing.T) { }, lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, strictErrs: []string{`status.certificate: Invalid value: "": only CERTIFICATE PEM blocks are allowed, found "CERTIFICATE1"`}, - }, - { + }, { name: "status.certificate, PEM headers", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ @@ -1024,8 +972,7 @@ func Test_validateCertificateSigningRequestOptions(t *testing.T) { }, lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, strictErrs: []string{`status.certificate: Invalid value: "": no PEM block headers are permitted`}, - }, - { + }, { name: "status.certificate, non-base64 PEM", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ @@ -1035,8 +982,7 @@ func Test_validateCertificateSigningRequestOptions(t *testing.T) { }, lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, strictErrs: []string{`status.certificate: Invalid value: "": must contain at least one CERTIFICATE PEM block`}, - }, - { + }, { name: "status.certificate, empty PEM block", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ @@ -1046,8 +992,7 @@ func Test_validateCertificateSigningRequestOptions(t *testing.T) { }, lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, strictErrs: []string{`status.certificate: Invalid value: "": found CERTIFICATE PEM block containing 0 certificates`}, - }, - { + }, { name: "status.certificate, non-ASN1 data", csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ @@ -1164,241 +1109,223 @@ func TestValidateClusterTrustBundle(t *testing.T) { bundle *capi.ClusterTrustBundle opts ValidateClusterTrustBundleOptions wantErrors field.ErrorList - }{ - { - description: "valid, no signer name", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: capi.ClusterTrustBundleSpec{ - TrustBundle: goodCert1Block, - }, + }{{ + description: "valid, no signer name", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: capi.ClusterTrustBundleSpec{ + TrustBundle: goodCert1Block, }, }, - { - description: "invalid, no signer name, invalid name", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:bar:foo", - }, - Spec: capi.ClusterTrustBundleSpec{ - TrustBundle: goodCert1Block, - }, + }, { + description: "invalid, no signer name, invalid name", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:bar:foo", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), "k8s.io:bar:foo", "ClusterTrustBundle without signer name must not have \":\" in its name"), + Spec: capi.ClusterTrustBundleSpec{ + TrustBundle: goodCert1Block, }, }, - { - description: "valid, with signer name", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:bar", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: goodCert1Block, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), "k8s.io:bar:foo", "ClusterTrustBundle without signer name must not have \":\" in its name"), + }, + }, { + description: "valid, with signer name", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:bar", + }, + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: goodCert1Block, }, }, - { - description: "invalid, with signer name, missing name prefix", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "look-ma-no-prefix", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: goodCert1Block, - }, + }, { + description: "invalid, with signer name, missing name prefix", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "look-ma-no-prefix", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), "look-ma-no-prefix", "ClusterTrustBundle for signerName k8s.io/foo must be named with prefix k8s.io:foo:"), + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: goodCert1Block, }, }, - { - description: "invalid, with signer name, empty name suffix", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: goodCert1Block, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), "look-ma-no-prefix", "ClusterTrustBundle for signerName k8s.io/foo must be named with prefix k8s.io:foo:"), + }, + }, { + description: "invalid, with signer name, empty name suffix", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: goodCert1Block, }, }, - { - description: "invalid, with signer name, bad name suffix", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:123notvalidDNSSubdomain", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: goodCert1Block, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), + }, + }, { + description: "invalid, with signer name, bad name suffix", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:123notvalidDNSSubdomain", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:123notvalidDNSSubdomain", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: goodCert1Block, }, }, - { - description: "valid, with signer name, with inter-block garbage", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:abc", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: "garbage\n" + goodCert1Block + "\ngarbage\n" + goodCert2Block, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:123notvalidDNSSubdomain", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), + }, + }, { + description: "valid, with signer name, with inter-block garbage", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:abc", + }, + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: "garbage\n" + goodCert1Block + "\ngarbage\n" + goodCert2Block, }, }, - { - description: "invalid, no signer name, no trust anchors", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: capi.ClusterTrustBundleSpec{}, + }, { + description: "invalid, no signer name, no trust anchors", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "trustBundle"), "", "at least one trust anchor must be provided"), + Spec: capi.ClusterTrustBundleSpec{}, + }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "trustBundle"), "", "at least one trust anchor must be provided"), + }, + }, { + description: "invalid, no trust anchors", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:abc", + }, + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", }, }, - { - description: "invalid, no trust anchors", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:abc", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "trustBundle"), "", "at least one trust anchor must be provided"), + }, + }, { + description: "invalid, bad signer name", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid:foo", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "trustBundle"), "", "at least one trust anchor must be provided"), + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "invalid", + TrustBundle: goodCert1Block, }, }, - { - description: "invalid, bad signer name", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "invalid:foo", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "invalid", - TrustBundle: goodCert1Block, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "signerName"), "invalid", "must be a fully qualified domain and path of the form 'example.com/signer-name'"), + }, + }, { + description: "invalid, no blocks", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "signerName"), "invalid", "must be a fully qualified domain and path of the form 'example.com/signer-name'"), + Spec: capi.ClusterTrustBundleSpec{ + TrustBundle: "non block garbage", }, }, - { - description: "invalid, no blocks", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: capi.ClusterTrustBundleSpec{ - TrustBundle: "non block garbage", - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "trustBundle"), "", "at least one trust anchor must be provided"), + }, + }, { + description: "invalid, bad block type", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "trustBundle"), "", "at least one trust anchor must be provided"), + Spec: capi.ClusterTrustBundleSpec{ + TrustBundle: goodCert1Block + "\n" + badBlockTypeBlock, }, }, - { - description: "invalid, bad block type", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: capi.ClusterTrustBundleSpec{ - TrustBundle: goodCert1Block + "\n" + badBlockTypeBlock, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "trustBundle"), "", "entry 1 has bad block type: NOTACERTIFICATE"), + }, + }, { + description: "invalid, block with headers", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "trustBundle"), "", "entry 1 has bad block type: NOTACERTIFICATE"), + Spec: capi.ClusterTrustBundleSpec{ + TrustBundle: goodCert1Block + "\n" + badBlockHeadersBlock, }, }, - { - description: "invalid, block with headers", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: capi.ClusterTrustBundleSpec{ - TrustBundle: goodCert1Block + "\n" + badBlockHeadersBlock, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "trustBundle"), "", "entry 1 has PEM block headers"), + }, + }, { + description: "invalid, cert is not a CA cert", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "trustBundle"), "", "entry 1 has PEM block headers"), + Spec: capi.ClusterTrustBundleSpec{ + TrustBundle: badNotCACertBlock, }, }, - { - description: "invalid, cert is not a CA cert", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: capi.ClusterTrustBundleSpec{ - TrustBundle: badNotCACertBlock, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "trustBundle"), "", "entry 0 does not have the CA bit set"), + }, + }, { + description: "invalid, duplicated blocks", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "trustBundle"), "", "entry 0 does not have the CA bit set"), + Spec: capi.ClusterTrustBundleSpec{ + TrustBundle: goodCert1Block + "\n" + goodCert1AlternateBlock, }, }, - { - description: "invalid, duplicated blocks", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: capi.ClusterTrustBundleSpec{ - TrustBundle: goodCert1Block + "\n" + goodCert1AlternateBlock, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "trustBundle"), "", "duplicate trust anchor (indices [0 1])"), + }, + }, { + description: "invalid, non-certificate entry", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "trustBundle"), "", "duplicate trust anchor (indices [0 1])"), + Spec: capi.ClusterTrustBundleSpec{ + TrustBundle: goodCert1Block + "\n" + badNonParseableBlock, }, }, - { - description: "invalid, non-certificate entry", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: capi.ClusterTrustBundleSpec{ - TrustBundle: goodCert1Block + "\n" + badNonParseableBlock, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "trustBundle"), "", "entry 1 does not parse as X.509"), + }, + }, { + description: "allow any old garbage in the PEM field if we suppress parsing", + bundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "trustBundle"), "", "entry 1 does not parse as X.509"), + Spec: capi.ClusterTrustBundleSpec{ + TrustBundle: "garbage", }, }, - { - description: "allow any old garbage in the PEM field if we suppress parsing", - bundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: capi.ClusterTrustBundleSpec{ - TrustBundle: "garbage", - }, - }, - opts: ValidateClusterTrustBundleOptions{ - SuppressBundleParsing: true, - }, + opts: ValidateClusterTrustBundleOptions{ + SuppressBundleParsing: true, }, - } + }} for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { gotErrors := ValidateClusterTrustBundle(tc.bundle, tc.opts) @@ -1454,102 +1381,97 @@ func TestValidateClusterTrustBundleUpdate(t *testing.T) { description string oldBundle, newBundle *capi.ClusterTrustBundle wantErrors field.ErrorList - }{ - { - description: "changing signer name disallowed", - oldBundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:bar", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: goodCert1Block, - }, + }{{ + description: "changing signer name disallowed", + oldBundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:bar", }, - newBundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:bar", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/bar", - TrustBundle: goodCert1Block, - }, - }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:bar", "ClusterTrustBundle for signerName k8s.io/bar must be named with prefix k8s.io:bar:"), - field.Invalid(field.NewPath("spec", "signerName"), "k8s.io/bar", "field is immutable"), + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: goodCert1Block, }, }, - { - description: "adding certificate allowed", - oldBundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:bar", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: goodCert1Block, - }, + newBundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:bar", }, - newBundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:bar", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: goodCert1Block + "\n" + goodCert2Block, - }, + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/bar", + TrustBundle: goodCert1Block, }, }, - { - description: "emptying trustBundle disallowed", - oldBundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:bar", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: goodCert1Block, - }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:bar", "ClusterTrustBundle for signerName k8s.io/bar must be named with prefix k8s.io:bar:"), + field.Invalid(field.NewPath("spec", "signerName"), "k8s.io/bar", "field is immutable"), + }, + }, { + description: "adding certificate allowed", + oldBundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:bar", }, - newBundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:bar", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: "", - }, - }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "trustBundle"), "", "at least one trust anchor must be provided"), + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: goodCert1Block, }, }, - { - description: "emptying trustBundle (replace with non-block garbage) disallowed", - oldBundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:bar", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: goodCert1Block, - }, + newBundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:bar", }, - newBundle: &capi.ClusterTrustBundle{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s.io:foo:bar", - }, - Spec: capi.ClusterTrustBundleSpec{ - SignerName: "k8s.io/foo", - TrustBundle: "non block garbage", - }, - }, - wantErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec", "trustBundle"), "", "at least one trust anchor must be provided"), + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: goodCert1Block + "\n" + goodCert2Block, }, }, - } + }, { + description: "emptying trustBundle disallowed", + oldBundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:bar", + }, + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: goodCert1Block, + }, + }, + newBundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:bar", + }, + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: "", + }, + }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "trustBundle"), "", "at least one trust anchor must be provided"), + }, + }, { + description: "emptying trustBundle (replace with non-block garbage) disallowed", + oldBundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:bar", + }, + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: goodCert1Block, + }, + }, + newBundle: &capi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s.io:foo:bar", + }, + Spec: capi.ClusterTrustBundleSpec{ + SignerName: "k8s.io/foo", + TrustBundle: "non block garbage", + }, + }, + wantErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec", "trustBundle"), "", "at least one trust anchor must be provided"), + }, + }} for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { diff --git a/pkg/apis/core/v1/validation/validation_test.go b/pkg/apis/core/v1/validation/validation_test.go index 97fd7735861..19738026bfa 100644 --- a/pkg/apis/core/v1/validation/validation_test.go +++ b/pkg/apis/core/v1/validation/validation_test.go @@ -31,56 +31,51 @@ func TestValidateResourceRequirements(t *testing.T) { successCase := []struct { name string requirements v1.ResourceRequirements - }{ - { - name: "Resources with Requests equal to Limits", - requirements: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - }, + }{{ + name: "Resources with Requests equal to Limits", + requirements: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), }, }, - { - name: "Resources with only Limits", - requirements: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - v1.ResourceName("my.org/resource"): resource.MustParse("10"), - }, + }, { + name: "Resources with only Limits", + requirements: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + v1.ResourceName("my.org/resource"): resource.MustParse("10"), }, }, - { - name: "Resources with only Requests", - requirements: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - v1.ResourceName("my.org/resource"): resource.MustParse("10"), - }, + }, { + name: "Resources with only Requests", + requirements: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + v1.ResourceName("my.org/resource"): resource.MustParse("10"), }, }, - { - name: "Resources with Requests Less Than Limits", - requirements: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("9G"), - v1.ResourceName("my.org/resource"): resource.MustParse("9"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - v1.ResourceName("my.org/resource"): resource.MustParse("9"), - }, + }, { + name: "Resources with Requests Less Than Limits", + requirements: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("9G"), + v1.ResourceName("my.org/resource"): resource.MustParse("9"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + v1.ResourceName("my.org/resource"): resource.MustParse("9"), }, }, - } + }} for _, tc := range successCase { t.Run(tc.name, func(t *testing.T) { if errs := ValidateResourceRequirements(&tc.requirements, field.NewPath("resources")); len(errs) != 0 { @@ -94,41 +89,37 @@ func TestValidateResourceRequirements(t *testing.T) { requirements v1.ResourceRequirements skipLimitValueCheck bool skipRequestValueCheck bool - }{ - { - name: "Resources with Requests Larger Than Limits", - requirements: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - v1.ResourceName("my.org/resource"): resource.MustParse("10m"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("9G"), - v1.ResourceName("my.org/resource"): resource.MustParse("9m"), - }, + }{{ + name: "Resources with Requests Larger Than Limits", + requirements: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + v1.ResourceName("my.org/resource"): resource.MustParse("10m"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("9G"), + v1.ResourceName("my.org/resource"): resource.MustParse("9m"), }, }, - { - name: "Invalid Resources with Requests", - requirements: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName("my.org"): resource.MustParse("10m"), - }, + }, { + name: "Invalid Resources with Requests", + requirements: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName("my.org"): resource.MustParse("10m"), }, - skipRequestValueCheck: true, }, - { - name: "Invalid Resources with Limits", - requirements: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceName("my.org"): resource.MustParse("9m"), - }, + skipRequestValueCheck: true, + }, { + name: "Invalid Resources with Limits", + requirements: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceName("my.org"): resource.MustParse("9m"), }, - skipLimitValueCheck: true, }, - } + skipLimitValueCheck: true, + }} for _, tc := range errorCase { t.Run(tc.name, func(t *testing.T) { errs := ValidateResourceRequirements(&tc.requirements, field.NewPath("resources")) @@ -168,28 +159,22 @@ func TestValidateContainerResourceName(t *testing.T) { successCase := []struct { name string ResourceName string - }{ - { - name: "CPU resource", - ResourceName: "cpu", - }, - { - name: "Memory resource", - ResourceName: "memory", - }, - { - name: "Hugepages resource", - ResourceName: "hugepages-2Mi", - }, - { - name: "Namespaced resource", - ResourceName: "kubernetes.io/resource-foo", - }, - { - name: "Extended Resource", - ResourceName: "my.org/resource-bar", - }, - } + }{{ + name: "CPU resource", + ResourceName: "cpu", + }, { + name: "Memory resource", + ResourceName: "memory", + }, { + name: "Hugepages resource", + ResourceName: "hugepages-2Mi", + }, { + name: "Namespaced resource", + ResourceName: "kubernetes.io/resource-foo", + }, { + name: "Extended Resource", + ResourceName: "my.org/resource-bar", + }} for _, tc := range successCase { t.Run(tc.name, func(t *testing.T) { if errs := ValidateContainerResourceName(tc.ResourceName, field.NewPath(tc.ResourceName)); len(errs) != 0 { @@ -201,20 +186,16 @@ func TestValidateContainerResourceName(t *testing.T) { errorCase := []struct { name string ResourceName string - }{ - { - name: "Invalid standard resource", - ResourceName: "cpu-core", - }, - { - name: "Invalid namespaced resource", - ResourceName: "kubernetes.io/", - }, - { - name: "Invalid extended resource", - ResourceName: "my.org-foo-resource", - }, - } + }{{ + name: "Invalid standard resource", + ResourceName: "cpu-core", + }, { + name: "Invalid namespaced resource", + ResourceName: "kubernetes.io/", + }, { + name: "Invalid extended resource", + ResourceName: "my.org-foo-resource", + }} for _, tc := range errorCase { t.Run(tc.name, func(t *testing.T) { if errs := ValidateContainerResourceName(tc.ResourceName, field.NewPath(tc.ResourceName)); len(errs) == 0 { @@ -239,45 +220,38 @@ func TestValidatePodLogOptions(t *testing.T) { successCase := []struct { name string podLogOptions v1.PodLogOptions - }{ - { - name: "Empty PodLogOptions", - podLogOptions: v1.PodLogOptions{}, + }{{ + name: "Empty PodLogOptions", + podLogOptions: v1.PodLogOptions{}, + }, { + name: "PodLogOptions with TailLines", + podLogOptions: v1.PodLogOptions{ + TailLines: &positiveLine, }, - { - name: "PodLogOptions with TailLines", - podLogOptions: v1.PodLogOptions{ - TailLines: &positiveLine, - }, + }, { + name: "PodLogOptions with LimitBytes", + podLogOptions: v1.PodLogOptions{ + LimitBytes: &limitBytesGreaterThan1, }, - { - name: "PodLogOptions with LimitBytes", - podLogOptions: v1.PodLogOptions{ - LimitBytes: &limitBytesGreaterThan1, - }, + }, { + name: "PodLogOptions with only sinceSeconds", + podLogOptions: v1.PodLogOptions{ + SinceSeconds: &sinceSecondsGreaterThan1, }, - { - name: "PodLogOptions with only sinceSeconds", - podLogOptions: v1.PodLogOptions{ - SinceSeconds: &sinceSecondsGreaterThan1, - }, + }, { + name: "PodLogOptions with LimitBytes with TailLines", + podLogOptions: v1.PodLogOptions{ + LimitBytes: &limitBytesGreaterThan1, + TailLines: &positiveLine, }, - { - name: "PodLogOptions with LimitBytes with TailLines", - podLogOptions: v1.PodLogOptions{ - LimitBytes: &limitBytesGreaterThan1, - TailLines: &positiveLine, - }, + }, { + name: "PodLogOptions with LimitBytes with TailLines with SinceSeconds", + podLogOptions: v1.PodLogOptions{ + LimitBytes: &limitBytesGreaterThan1, + TailLines: &positiveLine, + SinceSeconds: &sinceSecondsGreaterThan1, }, - { - name: "PodLogOptions with LimitBytes with TailLines with SinceSeconds", - podLogOptions: v1.PodLogOptions{ - LimitBytes: &limitBytesGreaterThan1, - TailLines: &positiveLine, - SinceSeconds: &sinceSecondsGreaterThan1, - }, - }, - } + }} for _, tc := range successCase { t.Run(tc.name, func(t *testing.T) { if errs := ValidatePodLogOptions(&tc.podLogOptions); len(errs) != 0 { @@ -289,40 +263,36 @@ func TestValidatePodLogOptions(t *testing.T) { errorCase := []struct { name string podLogOptions v1.PodLogOptions - }{ - { - name: "Invalid podLogOptions with Negative TailLines", - podLogOptions: v1.PodLogOptions{ - TailLines: &negativeLine, - LimitBytes: &limitBytesGreaterThan1, - SinceSeconds: &sinceSecondsGreaterThan1, - }, + }{{ + name: "Invalid podLogOptions with Negative TailLines", + podLogOptions: v1.PodLogOptions{ + TailLines: &negativeLine, + LimitBytes: &limitBytesGreaterThan1, + SinceSeconds: &sinceSecondsGreaterThan1, }, - { - name: "Invalid podLogOptions with zero or negative LimitBytes", - podLogOptions: v1.PodLogOptions{ - TailLines: &positiveLine, - LimitBytes: &limitBytesLessThan1, - SinceSeconds: &sinceSecondsGreaterThan1, - }, + }, { + name: "Invalid podLogOptions with zero or negative LimitBytes", + podLogOptions: v1.PodLogOptions{ + TailLines: &positiveLine, + LimitBytes: &limitBytesLessThan1, + SinceSeconds: &sinceSecondsGreaterThan1, }, - { - name: "Invalid podLogOptions with zero or negative SinceSeconds", - podLogOptions: v1.PodLogOptions{ - TailLines: &negativeLine, - LimitBytes: &limitBytesGreaterThan1, - SinceSeconds: &sinceSecondsLessThan1, - }, - }, { - name: "Invalid podLogOptions with both SinceSeconds and SinceTime set", - podLogOptions: v1.PodLogOptions{ - TailLines: &negativeLine, - LimitBytes: &limitBytesGreaterThan1, - SinceSeconds: &sinceSecondsGreaterThan1, - SinceTime: ×tamp, - }, + }, { + name: "Invalid podLogOptions with zero or negative SinceSeconds", + podLogOptions: v1.PodLogOptions{ + TailLines: &negativeLine, + LimitBytes: &limitBytesGreaterThan1, + SinceSeconds: &sinceSecondsLessThan1, }, - } + }, { + name: "Invalid podLogOptions with both SinceSeconds and SinceTime set", + podLogOptions: v1.PodLogOptions{ + TailLines: &negativeLine, + LimitBytes: &limitBytesGreaterThan1, + SinceSeconds: &sinceSecondsGreaterThan1, + SinceTime: ×tamp, + }, + }} for _, tc := range errorCase { t.Run(tc.name, func(t *testing.T) { if errs := ValidatePodLogOptions(&tc.podLogOptions); len(errs) == 0 { @@ -338,54 +308,37 @@ func TestAccumulateUniqueHostPorts(t *testing.T) { containers []v1.Container accumulator *sets.String fldPath *field.Path - }{ - { - name: "HostPort is not allocated while containers use the same port with different protocol", - containers: []v1.Container{ - { - Ports: []v1.ContainerPort{ - { - HostPort: 8080, - Protocol: v1.ProtocolUDP, - }, - }, - }, - { - Ports: []v1.ContainerPort{ - { - HostPort: 8080, - Protocol: v1.ProtocolTCP, - }, - }, - }, - }, - accumulator: &sets.String{}, - fldPath: field.NewPath("spec", "containers"), - }, - { - name: "HostPort is not allocated while containers use different ports", - containers: []v1.Container{ - { - Ports: []v1.ContainerPort{ - { - HostPort: 8080, - Protocol: v1.ProtocolUDP, - }, - }, - }, - { - Ports: []v1.ContainerPort{ - { - HostPort: 8081, - Protocol: v1.ProtocolUDP, - }, - }, - }, - }, - accumulator: &sets.String{}, - fldPath: field.NewPath("spec", "containers"), - }, - } + }{{ + name: "HostPort is not allocated while containers use the same port with different protocol", + containers: []v1.Container{{ + Ports: []v1.ContainerPort{{ + HostPort: 8080, + Protocol: v1.ProtocolUDP, + }}, + }, { + Ports: []v1.ContainerPort{{ + HostPort: 8080, + Protocol: v1.ProtocolTCP, + }}, + }}, + accumulator: &sets.String{}, + fldPath: field.NewPath("spec", "containers"), + }, { + name: "HostPort is not allocated while containers use different ports", + containers: []v1.Container{{ + Ports: []v1.ContainerPort{{ + HostPort: 8080, + Protocol: v1.ProtocolUDP, + }}, + }, { + Ports: []v1.ContainerPort{{ + HostPort: 8081, + Protocol: v1.ProtocolUDP, + }}, + }}, + accumulator: &sets.String{}, + fldPath: field.NewPath("spec", "containers"), + }} for _, tc := range successCase { t.Run(tc.name, func(t *testing.T) { if errs := AccumulateUniqueHostPorts(tc.containers, tc.accumulator, tc.fldPath); len(errs) != 0 { @@ -398,54 +351,37 @@ func TestAccumulateUniqueHostPorts(t *testing.T) { containers []v1.Container accumulator *sets.String fldPath *field.Path - }{ - { - name: "HostPort is already allocated while containers use the same port with UDP", - containers: []v1.Container{ - { - Ports: []v1.ContainerPort{ - { - HostPort: 8080, - Protocol: v1.ProtocolUDP, - }, - }, - }, - { - Ports: []v1.ContainerPort{ - { - HostPort: 8080, - Protocol: v1.ProtocolUDP, - }, - }, - }, - }, - accumulator: &sets.String{}, - fldPath: field.NewPath("spec", "containers"), - }, - { - name: "HostPort is already allocated", - containers: []v1.Container{ - { - Ports: []v1.ContainerPort{ - { - HostPort: 8080, - Protocol: v1.ProtocolUDP, - }, - }, - }, - { - Ports: []v1.ContainerPort{ - { - HostPort: 8081, - Protocol: v1.ProtocolUDP, - }, - }, - }, - }, - accumulator: &sets.String{"8080/UDP": sets.Empty{}}, - fldPath: field.NewPath("spec", "containers"), - }, - } + }{{ + name: "HostPort is already allocated while containers use the same port with UDP", + containers: []v1.Container{{ + Ports: []v1.ContainerPort{{ + HostPort: 8080, + Protocol: v1.ProtocolUDP, + }}, + }, { + Ports: []v1.ContainerPort{{ + HostPort: 8080, + Protocol: v1.ProtocolUDP, + }}, + }}, + accumulator: &sets.String{}, + fldPath: field.NewPath("spec", "containers"), + }, { + name: "HostPort is already allocated", + containers: []v1.Container{{ + Ports: []v1.ContainerPort{{ + HostPort: 8080, + Protocol: v1.ProtocolUDP, + }}, + }, { + Ports: []v1.ContainerPort{{ + HostPort: 8081, + Protocol: v1.ProtocolUDP, + }}, + }}, + accumulator: &sets.String{"8080/UDP": sets.Empty{}}, + fldPath: field.NewPath("spec", "containers"), + }} for _, tc := range errorCase { t.Run(tc.name, func(t *testing.T) { if errs := AccumulateUniqueHostPorts(tc.containers, tc.accumulator, tc.fldPath); len(errs) == 0 { diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index ce9269e8830..5caae4e1354 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -479,16 +479,12 @@ func TestValidatePersistentVolumes(t *testing.T) { volume: testVolumeWithNodeAffinity( &core.VolumeNodeAffinity{ Required: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Operator: core.NodeSelectorOpIn, - Values: []string{"test-label-value"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Operator: core.NodeSelectorOpIn, + Values: []string{"test-label-value"}, + }}, + }}, }, }), }, @@ -1079,17 +1075,13 @@ func testVolumeWithNodeAffinity(affinity *core.VolumeNodeAffinity) *core.Persist func simpleVolumeNodeAffinity(key, value string) *core.VolumeNodeAffinity { return &core.VolumeNodeAffinity{ Required: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: key, - Operator: core.NodeSelectorOpIn, - Values: []string{value}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: key, + Operator: core.NodeSelectorOpIn, + Values: []string{value}, + }}, + }}, }, } } @@ -1245,159 +1237,130 @@ func TestValidateVolumeNodeAffinityUpdate(t *testing.T) { }, "affinity-same-terms-expressions-length-beta-to-GA-partially-changed": { isExpectedFailure: false, - oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{"foo", "bar"}, - }, - { - topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, - topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, - }, - { - topologyPair{kubeletapis.LabelOS, "bar"}, - topologyPair{kubeletapis.LabelArch, "bar"}, - topologyPair{v1.LabelInstanceType, "bar"}, - }, + oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{"foo", "bar"}, + }, { + topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, + topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, + }, { + topologyPair{kubeletapis.LabelOS, "bar"}, + topologyPair{kubeletapis.LabelArch, "bar"}, + topologyPair{v1.LabelInstanceType, "bar"}, + }, })), - newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{"foo", "bar"}, - }, - { - topologyPair{v1.LabelTopologyZone, "bar"}, - topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, - }, - { - topologyPair{kubeletapis.LabelOS, "bar"}, - topologyPair{v1.LabelArchStable, "bar"}, - topologyPair{v1.LabelInstanceTypeStable, "bar"}, - }, + newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{"foo", "bar"}, + }, { + topologyPair{v1.LabelTopologyZone, "bar"}, + topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, + }, { + topologyPair{kubeletapis.LabelOS, "bar"}, + topologyPair{v1.LabelArchStable, "bar"}, + topologyPair{v1.LabelInstanceTypeStable, "bar"}, + }, })), }, "affinity-same-terms-expressions-length-beta-to-non-GA-partially-changed": { isExpectedFailure: true, - oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{"foo", "bar"}, - }, - { - topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, - topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, - }, + oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{"foo", "bar"}, + }, { + topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, + topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, + }, })), - newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{"foo", "bar"}, - }, - { - topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, - topologyPair{"foo", "bar"}, - }, + newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{"foo", "bar"}, + }, { + topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, + topologyPair{"foo", "bar"}, + }, })), }, "affinity-same-terms-expressions-length-GA-partially-changed": { isExpectedFailure: true, - oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{"foo", "bar"}, - }, - { - topologyPair{v1.LabelTopologyZone, "bar"}, - topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, - topologyPair{v1.LabelOSStable, "bar"}, - }, + oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{"foo", "bar"}, + }, { + topologyPair{v1.LabelTopologyZone, "bar"}, + topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, + topologyPair{v1.LabelOSStable, "bar"}, + }, })), - newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{"foo", "bar"}, - }, - { - topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, - topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, - topologyPair{v1.LabelOSStable, "bar"}, - }, + newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{"foo", "bar"}, + }, { + topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, + topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, + topologyPair{v1.LabelOSStable, "bar"}, + }, })), }, "affinity-same-terms-expressions-length-beta-fully-changed": { isExpectedFailure: false, - oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{"foo", "bar"}, - }, - { - topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, - topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, - }, - { - topologyPair{kubeletapis.LabelOS, "bar"}, - topologyPair{kubeletapis.LabelArch, "bar"}, - topologyPair{v1.LabelInstanceType, "bar"}, - }, + oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{"foo", "bar"}, + }, { + topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, + topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, + }, { + topologyPair{kubeletapis.LabelOS, "bar"}, + topologyPair{kubeletapis.LabelArch, "bar"}, + topologyPair{v1.LabelInstanceType, "bar"}, + }, })), - newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{"foo", "bar"}, - }, - { - topologyPair{v1.LabelTopologyZone, "bar"}, - topologyPair{v1.LabelTopologyRegion, "bar"}, - }, - { - topologyPair{v1.LabelOSStable, "bar"}, - topologyPair{v1.LabelArchStable, "bar"}, - topologyPair{v1.LabelInstanceTypeStable, "bar"}, - }, + newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{"foo", "bar"}, + }, { + topologyPair{v1.LabelTopologyZone, "bar"}, + topologyPair{v1.LabelTopologyRegion, "bar"}, + }, { + topologyPair{v1.LabelOSStable, "bar"}, + topologyPair{v1.LabelArchStable, "bar"}, + topologyPair{v1.LabelInstanceTypeStable, "bar"}, + }, })), }, "affinity-same-terms-expressions-length-beta-GA-mixed-fully-changed": { isExpectedFailure: true, - oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{"foo", "bar"}, - }, - { - topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, - topologyPair{v1.LabelTopologyZone, "bar"}, - }, + oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{"foo", "bar"}, + }, { + topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, + topologyPair{v1.LabelTopologyZone, "bar"}, + }, })), - newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{"foo", "bar"}, - }, - { - topologyPair{v1.LabelTopologyZone, "bar"}, - topologyPair{v1.LabelFailureDomainBetaZone, "bar2"}, - }, + newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{"foo", "bar"}, + }, { + topologyPair{v1.LabelTopologyZone, "bar"}, + topologyPair{v1.LabelFailureDomainBetaZone, "bar2"}, + }, })), }, "affinity-same-terms-length-different-expressions-length-beta-changed": { isExpectedFailure: true, - oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, - }, + oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, + }, })), - newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{v1.LabelTopologyZone, "bar"}, - topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, - }, + newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{v1.LabelTopologyZone, "bar"}, + topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, + }, })), }, "affinity-different-terms-expressions-length-beta-changed": { isExpectedFailure: true, - oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, - }, + oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, + }, })), - newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{ - { - topologyPair{v1.LabelTopologyZone, "bar"}, - }, - { - topologyPair{v1.LabelArchStable, "bar"}, - }, + newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ + topologyPair{v1.LabelTopologyZone, "bar"}, + }, { + topologyPair{v1.LabelArchStable, "bar"}, + }, })), }, "nil-to-obj": { @@ -1585,12 +1548,10 @@ func testValidatePVC(t *testing.T, ephemeral bool) { } goodClaimSpec := core.PersistentVolumeClaimSpec{ Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: "Exists", + }}, }, AccessModes: []core.PersistentVolumeAccessMode{ core.ReadWriteOnce, @@ -1676,13 +1637,12 @@ func testValidatePVC(t *testing.T, ephemeral bool) { isExpectedFailure: ephemeral, claim: func() *core.PersistentVolumeClaim { claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) - claim.OwnerReferences = []metav1.OwnerReference{ - { - APIVersion: "v1", - Kind: "pod", - Name: "foo", - UID: "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d", - }, + claim.OwnerReferences = []metav1.OwnerReference{{ + APIVersion: "v1", + Kind: "pod", + Name: "foo", + UID: "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d", + }, } return claim }(), @@ -1701,13 +1661,12 @@ func testValidatePVC(t *testing.T, ephemeral bool) { isExpectedFailure: ephemeral, claim: func() *core.PersistentVolumeClaim { claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) - claim.ManagedFields = []metav1.ManagedFieldsEntry{ - { - FieldsType: "FieldsV1", - Operation: "Apply", - APIVersion: "apps/v1", - Manager: "foo", - }, + claim.ManagedFields = []metav1.ManagedFieldsEntry{{ + FieldsType: "FieldsV1", + Operation: "Apply", + APIVersion: "apps/v1", + Manager: "foo", + }, } return claim }(), @@ -1790,12 +1749,10 @@ func testValidatePVC(t *testing.T, ephemeral bool) { isExpectedFailure: true, claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: "Exists", + }}, }, AccessModes: []core.PersistentVolumeAccessMode{ core.ReadWriteOnce, @@ -1813,13 +1770,11 @@ func testValidatePVC(t *testing.T, ephemeral bool) { isExpectedFailure: true, claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "InvalidOp", - Values: []string{"value1", "value2"}, - }, - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: "InvalidOp", + Values: []string{"value1", "value2"}, + }}, }, AccessModes: []core.PersistentVolumeAccessMode{ core.ReadWriteOnce, @@ -1878,12 +1833,10 @@ func testValidatePVC(t *testing.T, ephemeral bool) { isExpectedFailure: true, claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: "Exists", + }}, }, AccessModes: []core.PersistentVolumeAccessMode{ core.ReadWriteOnce, @@ -1900,12 +1853,10 @@ func testValidatePVC(t *testing.T, ephemeral bool) { isExpectedFailure: true, claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: "Exists", + }}, }, AccessModes: []core.PersistentVolumeAccessMode{ core.ReadWriteOnce, @@ -1922,12 +1873,10 @@ func testValidatePVC(t *testing.T, ephemeral bool) { isExpectedFailure: true, claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: "Exists", + }}, }, AccessModes: []core.PersistentVolumeAccessMode{ core.ReadWriteOnce, @@ -1985,18 +1934,17 @@ func testValidatePVC(t *testing.T, ephemeral bool) { var errs field.ErrorList if ephemeral { - volumes := []core.Volume{ - { - Name: "foo", - VolumeSource: core.VolumeSource{ - Ephemeral: &core.EphemeralVolumeSource{ - VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{ - ObjectMeta: scenario.claim.ObjectMeta, - Spec: scenario.claim.Spec, - }, + volumes := []core.Volume{{ + Name: "foo", + VolumeSource: core.VolumeSource{ + Ephemeral: &core.EphemeralVolumeSource{ + VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{ + ObjectMeta: scenario.claim.ObjectMeta, + Spec: scenario.claim.Spec, }, }, }, + }, } opts := PodValidationOptions{} _, errs = ValidateVolumes(volumes, nil, field.NewPath(""), opts) @@ -2854,63 +2802,51 @@ func TestValidateKeyToPath(t *testing.T) { kp core.KeyToPath ok bool errtype field.ErrorType - }{ - { - kp: core.KeyToPath{Key: "k", Path: "p"}, - ok: true, - }, - { - kp: core.KeyToPath{Key: "k", Path: "p/p/p/p"}, - ok: true, - }, - { - kp: core.KeyToPath{Key: "k", Path: "p/..p/p../p..p"}, - ok: true, - }, - { - kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(0644)}, - ok: true, - }, - { - kp: core.KeyToPath{Key: "", Path: "p"}, - ok: false, - errtype: field.ErrorTypeRequired, - }, - { - kp: core.KeyToPath{Key: "k", Path: ""}, - ok: false, - errtype: field.ErrorTypeRequired, - }, - { - kp: core.KeyToPath{Key: "k", Path: "..p"}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - { - kp: core.KeyToPath{Key: "k", Path: "../p"}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - { - kp: core.KeyToPath{Key: "k", Path: "p/../p"}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - { - kp: core.KeyToPath{Key: "k", Path: "p/.."}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - { - kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(01000)}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - { - kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(-1)}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, + }{{ + kp: core.KeyToPath{Key: "k", Path: "p"}, + ok: true, + }, { + kp: core.KeyToPath{Key: "k", Path: "p/p/p/p"}, + ok: true, + }, { + kp: core.KeyToPath{Key: "k", Path: "p/..p/p../p..p"}, + ok: true, + }, { + kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(0644)}, + ok: true, + }, { + kp: core.KeyToPath{Key: "", Path: "p"}, + ok: false, + errtype: field.ErrorTypeRequired, + }, { + kp: core.KeyToPath{Key: "k", Path: ""}, + ok: false, + errtype: field.ErrorTypeRequired, + }, { + kp: core.KeyToPath{Key: "k", Path: "..p"}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, { + kp: core.KeyToPath{Key: "k", Path: "../p"}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, { + kp: core.KeyToPath{Key: "k", Path: "p/../p"}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, { + kp: core.KeyToPath{Key: "k", Path: "p/.."}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, { + kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(01000)}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, { + kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(-1)}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, } for i, tc := range testCases { @@ -2936,26 +2872,23 @@ func TestValidateNFSVolumeSource(t *testing.T) { errtype field.ErrorType errfield string errdetail string - }{ - { - name: "missing server", - nfs: &core.NFSVolumeSource{Server: "", Path: "/tmp"}, - errtype: field.ErrorTypeRequired, - errfield: "server", - }, - { - name: "missing path", - nfs: &core.NFSVolumeSource{Server: "my-server", Path: ""}, - errtype: field.ErrorTypeRequired, - errfield: "path", - }, - { - name: "abs path", - nfs: &core.NFSVolumeSource{Server: "my-server", Path: "tmp"}, - errtype: field.ErrorTypeInvalid, - errfield: "path", - errdetail: "must be an absolute path", - }, + }{{ + name: "missing server", + nfs: &core.NFSVolumeSource{Server: "", Path: "/tmp"}, + errtype: field.ErrorTypeRequired, + errfield: "server", + }, { + name: "missing path", + nfs: &core.NFSVolumeSource{Server: "my-server", Path: ""}, + errtype: field.ErrorTypeRequired, + errfield: "path", + }, { + name: "abs path", + nfs: &core.NFSVolumeSource{Server: "my-server", Path: "tmp"}, + errtype: field.ErrorTypeInvalid, + errfield: "path", + errdetail: "must be an absolute path", + }, } for i, tc := range testCases { @@ -2983,25 +2916,22 @@ func TestValidateGlusterfs(t *testing.T) { gfs *core.GlusterfsVolumeSource errtype field.ErrorType errfield string - }{ - { - name: "missing endpointname", - gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: "/tmp"}, - errtype: field.ErrorTypeRequired, - errfield: "endpoints", - }, - { - name: "missing path", - gfs: &core.GlusterfsVolumeSource{EndpointsName: "my-endpoint", Path: ""}, - errtype: field.ErrorTypeRequired, - errfield: "path", - }, - { - name: "missing endpointname and path", - gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: ""}, - errtype: field.ErrorTypeRequired, - errfield: "endpoints", - }, + }{{ + name: "missing endpointname", + gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: "/tmp"}, + errtype: field.ErrorTypeRequired, + errfield: "endpoints", + }, { + name: "missing path", + gfs: &core.GlusterfsVolumeSource{EndpointsName: "my-endpoint", Path: ""}, + errtype: field.ErrorTypeRequired, + errfield: "path", + }, { + name: "missing endpointname and path", + gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: ""}, + errtype: field.ErrorTypeRequired, + errfield: "endpoints", + }, } for i, tc := range testCases { @@ -3031,31 +2961,27 @@ func TestValidateGlusterfsPersistentVolumeSource(t *testing.T) { gfs *core.GlusterfsPersistentVolumeSource errtype field.ErrorType errfield string - }{ - { - name: "missing endpointname", - gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: "/tmp"}, - errtype: field.ErrorTypeRequired, - errfield: "endpoints", - }, - { - name: "missing path", - gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: ""}, - errtype: field.ErrorTypeRequired, - errfield: "path", - }, - { - name: "non null endpointnamespace with empty string", - gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: "/tmp", EndpointsNamespace: epNs}, - errtype: field.ErrorTypeInvalid, - errfield: "endpointsNamespace", - }, - { - name: "missing endpointname and path", - gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: ""}, - errtype: field.ErrorTypeRequired, - errfield: "endpoints", - }, + }{{ + name: "missing endpointname", + gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: "/tmp"}, + errtype: field.ErrorTypeRequired, + errfield: "endpoints", + }, { + name: "missing path", + gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: ""}, + errtype: field.ErrorTypeRequired, + errfield: "path", + }, { + name: "non null endpointnamespace with empty string", + gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: "/tmp", EndpointsNamespace: epNs}, + errtype: field.ErrorTypeInvalid, + errfield: "endpointsNamespace", + }, { + name: "missing endpointname and path", + gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: ""}, + errtype: field.ErrorTypeRequired, + errfield: "endpoints", + }, } for i, tc := range testCases { @@ -3081,89 +3007,73 @@ func TestValidateCSIVolumeSource(t *testing.T) { csi *core.CSIVolumeSource errtype field.ErrorType errfield string - }{ - { - name: "all required fields ok", - csi: &core.CSIVolumeSource{Driver: "test-driver"}, - }, - { - name: "missing driver name", - csi: &core.CSIVolumeSource{Driver: ""}, - errtype: field.ErrorTypeRequired, - errfield: "driver", - }, - { - name: "driver name: ok no punctuations", - csi: &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd"}, - }, - { - name: "driver name: ok dot only", - csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage.csi.flex"}, - }, - { - name: "driver name: ok dash only", - csi: &core.CSIVolumeSource{Driver: "io-kubernetes-storage-csi-flex"}, - }, - { - name: "driver name: invalid underscore", - csi: &core.CSIVolumeSource{Driver: "io_kubernetes_storage_csi_flex"}, - errtype: field.ErrorTypeInvalid, - errfield: "driver", - }, - { - name: "driver name: invalid dot underscores", - csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage_csi.flex"}, - errtype: field.ErrorTypeInvalid, - errfield: "driver", - }, - { - name: "driver name: ok beginning with number", - csi: &core.CSIVolumeSource{Driver: "2io.kubernetes.storage-csi.flex"}, - }, - { - name: "driver name: ok ending with number", - csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage-csi.flex2"}, - }, - { - name: "driver name: invalid dot dash underscores", - csi: &core.CSIVolumeSource{Driver: "io.kubernetes-storage.csi_flex"}, - errtype: field.ErrorTypeInvalid, - errfield: "driver", - }, + }{{ + name: "all required fields ok", + csi: &core.CSIVolumeSource{Driver: "test-driver"}, + }, { + name: "missing driver name", + csi: &core.CSIVolumeSource{Driver: ""}, + errtype: field.ErrorTypeRequired, + errfield: "driver", + }, { + name: "driver name: ok no punctuations", + csi: &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd"}, + }, { + name: "driver name: ok dot only", + csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage.csi.flex"}, + }, { + name: "driver name: ok dash only", + csi: &core.CSIVolumeSource{Driver: "io-kubernetes-storage-csi-flex"}, + }, { + name: "driver name: invalid underscore", + csi: &core.CSIVolumeSource{Driver: "io_kubernetes_storage_csi_flex"}, + errtype: field.ErrorTypeInvalid, + errfield: "driver", + }, { + name: "driver name: invalid dot underscores", + csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage_csi.flex"}, + errtype: field.ErrorTypeInvalid, + errfield: "driver", + }, { + name: "driver name: ok beginning with number", + csi: &core.CSIVolumeSource{Driver: "2io.kubernetes.storage-csi.flex"}, + }, { + name: "driver name: ok ending with number", + csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage-csi.flex2"}, + }, { + name: "driver name: invalid dot dash underscores", + csi: &core.CSIVolumeSource{Driver: "io.kubernetes-storage.csi_flex"}, + errtype: field.ErrorTypeInvalid, + errfield: "driver", + }, { name: "driver name: ok length 1", csi: &core.CSIVolumeSource{Driver: "a"}, - }, - { + }, { name: "driver name: invalid length > 63", csi: &core.CSIVolumeSource{Driver: strings.Repeat("g", 65)}, errtype: field.ErrorTypeTooLong, errfield: "driver", - }, - { + }, { name: "driver name: invalid start char", csi: &core.CSIVolumeSource{Driver: "_comgooglestoragecsigcepd"}, errtype: field.ErrorTypeInvalid, errfield: "driver", - }, - { + }, { name: "driver name: invalid end char", csi: &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd/"}, errtype: field.ErrorTypeInvalid, errfield: "driver", - }, - { + }, { name: "driver name: invalid separators", csi: &core.CSIVolumeSource{Driver: "com/google/storage/csi~gcepd"}, errtype: field.ErrorTypeInvalid, errfield: "driver", - }, - { + }, { name: "valid nodePublishSecretRef", csi: &core.CSIVolumeSource{Driver: "com.google.gcepd", NodePublishSecretRef: &core.LocalObjectReference{Name: "foobar"}}, - }, - { + }, { name: "nodePublishSecretRef: invalid name missing", csi: &core.CSIVolumeSource{Driver: "com.google.gcepd", NodePublishSecretRef: &core.LocalObjectReference{Name: ""}}, errtype: field.ErrorTypeRequired, @@ -3194,206 +3104,167 @@ func TestValidateCSIPersistentVolumeSource(t *testing.T) { csi *core.CSIPersistentVolumeSource errtype field.ErrorType errfield string - }{ - { - name: "all required fields ok", - csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, - }, - { - name: "with default values ok", - csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"}, - }, - { - name: "missing driver name", - csi: &core.CSIPersistentVolumeSource{VolumeHandle: "test-123"}, - errtype: field.ErrorTypeRequired, - errfield: "driver", - }, - { - name: "missing volume handle", - csi: &core.CSIPersistentVolumeSource{Driver: "my-driver"}, - errtype: field.ErrorTypeRequired, - errfield: "volumeHandle", - }, - { - name: "driver name: ok no punctuations", - csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd", VolumeHandle: "test-123"}, - }, - { - name: "driver name: ok dot only", - csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage.csi.flex", VolumeHandle: "test-123"}, - }, - { - name: "driver name: ok dash only", - csi: &core.CSIPersistentVolumeSource{Driver: "io-kubernetes-storage-csi-flex", VolumeHandle: "test-123"}, - }, - { - name: "driver name: invalid underscore", - csi: &core.CSIPersistentVolumeSource{Driver: "io_kubernetes_storage_csi_flex", VolumeHandle: "test-123"}, - errtype: field.ErrorTypeInvalid, - errfield: "driver", - }, - { - name: "driver name: invalid dot underscores", - csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage_csi.flex", VolumeHandle: "test-123"}, - errtype: field.ErrorTypeInvalid, - errfield: "driver", - }, - { - name: "driver name: ok beginning with number", - csi: &core.CSIPersistentVolumeSource{Driver: "2io.kubernetes.storage-csi.flex", VolumeHandle: "test-123"}, - }, - { - name: "driver name: ok ending with number", - csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage-csi.flex2", VolumeHandle: "test-123"}, - }, - { - name: "driver name: invalid dot dash underscores", - csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes-storage.csi_flex", VolumeHandle: "test-123"}, - errtype: field.ErrorTypeInvalid, - errfield: "driver", - }, - { - name: "driver name: invalid length 0", - csi: &core.CSIPersistentVolumeSource{Driver: "", VolumeHandle: "test-123"}, - errtype: field.ErrorTypeRequired, - errfield: "driver", - }, - { - name: "driver name: ok length 1", - csi: &core.CSIPersistentVolumeSource{Driver: "a", VolumeHandle: "test-123"}, - }, - { - name: "driver name: invalid length > 63", - csi: &core.CSIPersistentVolumeSource{Driver: strings.Repeat("g", 65), VolumeHandle: "test-123"}, - errtype: field.ErrorTypeTooLong, - errfield: "driver", - }, - { - name: "driver name: invalid start char", - csi: &core.CSIPersistentVolumeSource{Driver: "_comgooglestoragecsigcepd", VolumeHandle: "test-123"}, - errtype: field.ErrorTypeInvalid, - errfield: "driver", - }, - { - name: "driver name: invalid end char", - csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd/", VolumeHandle: "test-123"}, - errtype: field.ErrorTypeInvalid, - errfield: "driver", - }, - { - name: "driver name: invalid separators", - csi: &core.CSIPersistentVolumeSource{Driver: "com/google/storage/csi~gcepd", VolumeHandle: "test-123"}, - errtype: field.ErrorTypeInvalid, - errfield: "driver", - }, - { - name: "controllerExpandSecretRef: invalid name missing", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Namespace: "default"}}, - errtype: field.ErrorTypeRequired, - errfield: "controllerExpandSecretRef.name", - }, - { - name: "controllerExpandSecretRef: invalid namespace missing", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar"}}, - errtype: field.ErrorTypeRequired, - errfield: "controllerExpandSecretRef.namespace", - }, - { - name: "valid controllerExpandSecretRef", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, - }, - { - name: "controllerPublishSecretRef: invalid name missing", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Namespace: "default"}}, - errtype: field.ErrorTypeRequired, - errfield: "controllerPublishSecretRef.name", - }, - { - name: "controllerPublishSecretRef: invalid namespace missing", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar"}}, - errtype: field.ErrorTypeRequired, - errfield: "controllerPublishSecretRef.namespace", - }, - { - name: "valid controllerPublishSecretRef", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, - }, - { - name: "valid nodePublishSecretRef", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, - }, - { - name: "nodePublishSecretRef: invalid name missing", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Namespace: "foobar"}}, - errtype: field.ErrorTypeRequired, - errfield: "nodePublishSecretRef.name", - }, - { - name: "nodePublishSecretRef: invalid namespace missing", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar"}}, - errtype: field.ErrorTypeRequired, - errfield: "nodePublishSecretRef.namespace", - }, - { - name: "nodeExpandSecretRef: invalid name missing", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Namespace: "default"}}, - errtype: field.ErrorTypeRequired, - errfield: "nodeExpandSecretRef.name", - }, - { - name: "nodeExpandSecretRef: invalid namespace missing", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar"}}, - errtype: field.ErrorTypeRequired, - errfield: "nodeExpandSecretRef.namespace", - }, - { - name: "valid nodeExpandSecretRef", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, - }, - { - name: "Invalid nodePublishSecretRef", - csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, - }, + }{{ + name: "all required fields ok", + csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, + }, { + name: "with default values ok", + csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"}, + }, { + name: "missing driver name", + csi: &core.CSIPersistentVolumeSource{VolumeHandle: "test-123"}, + errtype: field.ErrorTypeRequired, + errfield: "driver", + }, { + name: "missing volume handle", + csi: &core.CSIPersistentVolumeSource{Driver: "my-driver"}, + errtype: field.ErrorTypeRequired, + errfield: "volumeHandle", + }, { + name: "driver name: ok no punctuations", + csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd", VolumeHandle: "test-123"}, + }, { + name: "driver name: ok dot only", + csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage.csi.flex", VolumeHandle: "test-123"}, + }, { + name: "driver name: ok dash only", + csi: &core.CSIPersistentVolumeSource{Driver: "io-kubernetes-storage-csi-flex", VolumeHandle: "test-123"}, + }, { + name: "driver name: invalid underscore", + csi: &core.CSIPersistentVolumeSource{Driver: "io_kubernetes_storage_csi_flex", VolumeHandle: "test-123"}, + errtype: field.ErrorTypeInvalid, + errfield: "driver", + }, { + name: "driver name: invalid dot underscores", + csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage_csi.flex", VolumeHandle: "test-123"}, + errtype: field.ErrorTypeInvalid, + errfield: "driver", + }, { + name: "driver name: ok beginning with number", + csi: &core.CSIPersistentVolumeSource{Driver: "2io.kubernetes.storage-csi.flex", VolumeHandle: "test-123"}, + }, { + name: "driver name: ok ending with number", + csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage-csi.flex2", VolumeHandle: "test-123"}, + }, { + name: "driver name: invalid dot dash underscores", + csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes-storage.csi_flex", VolumeHandle: "test-123"}, + errtype: field.ErrorTypeInvalid, + errfield: "driver", + }, { + name: "driver name: invalid length 0", + csi: &core.CSIPersistentVolumeSource{Driver: "", VolumeHandle: "test-123"}, + errtype: field.ErrorTypeRequired, + errfield: "driver", + }, { + name: "driver name: ok length 1", + csi: &core.CSIPersistentVolumeSource{Driver: "a", VolumeHandle: "test-123"}, + }, { + name: "driver name: invalid length > 63", + csi: &core.CSIPersistentVolumeSource{Driver: strings.Repeat("g", 65), VolumeHandle: "test-123"}, + errtype: field.ErrorTypeTooLong, + errfield: "driver", + }, { + name: "driver name: invalid start char", + csi: &core.CSIPersistentVolumeSource{Driver: "_comgooglestoragecsigcepd", VolumeHandle: "test-123"}, + errtype: field.ErrorTypeInvalid, + errfield: "driver", + }, { + name: "driver name: invalid end char", + csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd/", VolumeHandle: "test-123"}, + errtype: field.ErrorTypeInvalid, + errfield: "driver", + }, { + name: "driver name: invalid separators", + csi: &core.CSIPersistentVolumeSource{Driver: "com/google/storage/csi~gcepd", VolumeHandle: "test-123"}, + errtype: field.ErrorTypeInvalid, + errfield: "driver", + }, { + name: "controllerExpandSecretRef: invalid name missing", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Namespace: "default"}}, + errtype: field.ErrorTypeRequired, + errfield: "controllerExpandSecretRef.name", + }, { + name: "controllerExpandSecretRef: invalid namespace missing", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar"}}, + errtype: field.ErrorTypeRequired, + errfield: "controllerExpandSecretRef.namespace", + }, { + name: "valid controllerExpandSecretRef", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, + }, { + name: "controllerPublishSecretRef: invalid name missing", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Namespace: "default"}}, + errtype: field.ErrorTypeRequired, + errfield: "controllerPublishSecretRef.name", + }, { + name: "controllerPublishSecretRef: invalid namespace missing", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar"}}, + errtype: field.ErrorTypeRequired, + errfield: "controllerPublishSecretRef.namespace", + }, { + name: "valid controllerPublishSecretRef", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, + }, { + name: "valid nodePublishSecretRef", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, + }, { + name: "nodePublishSecretRef: invalid name missing", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Namespace: "foobar"}}, + errtype: field.ErrorTypeRequired, + errfield: "nodePublishSecretRef.name", + }, { + name: "nodePublishSecretRef: invalid namespace missing", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar"}}, + errtype: field.ErrorTypeRequired, + errfield: "nodePublishSecretRef.namespace", + }, { + name: "nodeExpandSecretRef: invalid name missing", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Namespace: "default"}}, + errtype: field.ErrorTypeRequired, + errfield: "nodeExpandSecretRef.name", + }, { + name: "nodeExpandSecretRef: invalid namespace missing", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar"}}, + errtype: field.ErrorTypeRequired, + errfield: "nodeExpandSecretRef.namespace", + }, { + name: "valid nodeExpandSecretRef", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, + }, { + name: "Invalid nodePublishSecretRef", + csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, + }, // tests with allowDNSSubDomainSecretName flag on/off { name: "valid nodeExpandSecretRef", csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}}, - }, - { + }, { name: "valid long nodeExpandSecretRef", csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}}, - }, - { + }, { name: "Invalid nodeExpandSecretRef", csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}}, errtype: field.ErrorTypeInvalid, errfield: "nodeExpandSecretRef.name", - }, - { + }, { name: "valid nodePublishSecretRef", csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}}, - }, - { + }, { name: "valid long nodePublishSecretRef", csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}}, - }, - { + }, { name: "Invalid nodePublishSecretRef", csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}}, errtype: field.ErrorTypeInvalid, errfield: "nodePublishSecretRef.name", - }, - { + }, { name: "valid ControllerExpandSecretRef", csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}}, - }, - { + }, { name: "valid long ControllerExpandSecretRef", csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}}, - }, - { + }, { name: "Invalid ControllerExpandSecretRef", csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}}, errtype: field.ErrorTypeInvalid, @@ -3446,8 +3317,7 @@ func TestValidateVolumes(t *testing.T) { EmptyDir: &core.EmptyDirVolumeSource{}, }, }, - }, - { + }, { name: "valid num name", vol: core.Volume{ Name: "123", @@ -3455,8 +3325,7 @@ func TestValidateVolumes(t *testing.T) { EmptyDir: &core.EmptyDirVolumeSource{}, }, }, - }, - { + }, { name: "valid alphanum name", vol: core.Volume{ Name: "empty-123", @@ -3464,8 +3333,7 @@ func TestValidateVolumes(t *testing.T) { EmptyDir: &core.EmptyDirVolumeSource{}, }, }, - }, - { + }, { name: "valid numalpha name", vol: core.Volume{ Name: "123-empty", @@ -3473,8 +3341,7 @@ func TestValidateVolumes(t *testing.T) { EmptyDir: &core.EmptyDirVolumeSource{}, }, }, - }, - { + }, { name: "zero-length name", vol: core.Volume{ Name: "", @@ -3484,8 +3351,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "name", }}, - }, - { + }, { name: "name > 63 characters", vol: core.Volume{ Name: strings.Repeat("a", 64), @@ -3496,8 +3362,7 @@ func TestValidateVolumes(t *testing.T) { field: "name", detail: "must be no more than", }}, - }, - { + }, { name: "name has dots", vol: core.Volume{ Name: "a.b.c", @@ -3508,8 +3373,7 @@ func TestValidateVolumes(t *testing.T) { field: "name", detail: "must not contain dots", }}, - }, - { + }, { name: "name not a DNS label", vol: core.Volume{ Name: "Not a DNS label!", @@ -3582,8 +3446,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeNotSupported, field: "type", }}, - }, - { + }, { name: "invalid HostPath backsteps", vol: core.Volume{ Name: "hostpath", @@ -3643,8 +3506,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid GitRepo in .", vol: core.Volume{ Name: "git-repo-dot", @@ -3655,8 +3517,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid GitRepo with .. in name", vol: core.Volume{ Name: "git-repo-dot-dot-foo", @@ -3667,8 +3528,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "GitRepo starts with ../", vol: core.Volume{ Name: "gitrepo", @@ -3684,8 +3544,7 @@ func TestValidateVolumes(t *testing.T) { field: "gitRepo.directory", detail: `must not contain '..'`, }}, - }, - { + }, { name: "GitRepo contains ..", vol: core.Volume{ Name: "gitrepo", @@ -3701,8 +3560,7 @@ func TestValidateVolumes(t *testing.T) { field: "gitRepo.directory", detail: `must not contain '..'`, }}, - }, - { + }, { name: "GitRepo absolute target", vol: core.Volume{ Name: "gitrepo", @@ -3733,8 +3591,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid IQN: eui format", vol: core.Volume{ Name: "iscsi", @@ -3748,8 +3605,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid IQN: naa format", vol: core.Volume{ Name: "iscsi", @@ -3763,8 +3619,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "empty portal", vol: core.Volume{ Name: "iscsi", @@ -3782,8 +3637,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "iscsi.targetPortal", }}, - }, - { + }, { name: "empty iqn", vol: core.Volume{ Name: "iscsi", @@ -3801,8 +3655,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "iscsi.iqn", }}, - }, - { + }, { name: "invalid IQN: iqn format", vol: core.Volume{ Name: "iscsi", @@ -3820,8 +3673,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "iscsi.iqn", }}, - }, - { + }, { name: "invalid IQN: eui format", vol: core.Volume{ Name: "iscsi", @@ -3839,8 +3691,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "iscsi.iqn", }}, - }, - { + }, { name: "invalid IQN: naa format", vol: core.Volume{ Name: "iscsi", @@ -3858,8 +3709,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "iscsi.iqn", }}, - }, - { + }, { name: "valid initiatorName", vol: core.Volume{ Name: "iscsi", @@ -3874,8 +3724,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "invalid initiatorName", vol: core.Volume{ Name: "iscsi", @@ -3894,8 +3743,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "iscsi.initiatorname", }}, - }, - { + }, { name: "empty secret", vol: core.Volume{ Name: "iscsi", @@ -3914,8 +3762,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "iscsi.secretRef", }}, - }, - { + }, { name: "empty secret", vol: core.Volume{ Name: "iscsi", @@ -3946,8 +3793,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid Secret with defaultMode", vol: core.Volume{ Name: "secret", @@ -3958,8 +3804,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid Secret with projection and mode", vol: core.Volume{ Name: "secret", @@ -3974,8 +3819,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid Secret with subdir projection", vol: core.Volume{ Name: "secret", @@ -3989,8 +3833,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "secret with missing path", vol: core.Volume{ Name: "secret", @@ -4005,8 +3848,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "secret.items[0].path", }}, - }, - { + }, { name: "secret with leading ..", vol: core.Volume{ Name: "secret", @@ -4021,8 +3863,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "secret.items[0].path", }}, - }, - { + }, { name: "secret with .. inside", vol: core.Volume{ Name: "secret", @@ -4037,8 +3878,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "secret.items[0].path", }}, - }, - { + }, { name: "secret with invalid positive defaultMode", vol: core.Volume{ Name: "secret", @@ -4053,8 +3893,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "secret.defaultMode", }}, - }, - { + }, { name: "secret with invalid negative defaultMode", vol: core.Volume{ Name: "secret", @@ -4083,8 +3922,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid ConfigMap with defaultMode", vol: core.Volume{ Name: "cfgmap", @@ -4097,8 +3935,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid ConfigMap with projection and mode", vol: core.Volume{ Name: "cfgmap", @@ -4114,8 +3951,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid ConfigMap with subdir projection", vol: core.Volume{ Name: "cfgmap", @@ -4130,8 +3966,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "configmap with missing path", vol: core.Volume{ Name: "cfgmap", @@ -4146,8 +3981,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "configMap.items[0].path", }}, - }, - { + }, { name: "configmap with leading ..", vol: core.Volume{ Name: "cfgmap", @@ -4162,8 +3996,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "configMap.items[0].path", }}, - }, - { + }, { name: "configmap with .. inside", vol: core.Volume{ Name: "cfgmap", @@ -4178,8 +4011,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "configMap.items[0].path", }}, - }, - { + }, { name: "configmap with invalid positive defaultMode", vol: core.Volume{ Name: "cfgmap", @@ -4194,8 +4026,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "configMap.defaultMode", }}, - }, - { + }, { name: "configmap with invalid negative defaultMode", vol: core.Volume{ Name: "cfgmap", @@ -4224,8 +4055,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "empty hosts", vol: core.Volume{ Name: "glusterfs", @@ -4241,8 +4071,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "glusterfs.endpoints", }}, - }, - { + }, { name: "empty path", vol: core.Volume{ Name: "glusterfs", @@ -4270,8 +4099,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "valid Flocker -- datasetName", vol: core.Volume{ Name: "flocker", @@ -4281,8 +4109,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "both empty", vol: core.Volume{ Name: "flocker", @@ -4296,8 +4123,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "flocker", }}, - }, - { + }, { name: "both specified", vol: core.Volume{ Name: "flocker", @@ -4312,8 +4138,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "flocker", }}, - }, - { + }, { name: "slash in flocker datasetName", vol: core.Volume{ Name: "flocker", @@ -4342,8 +4167,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "empty rbd monitors", vol: core.Volume{ Name: "rbd", @@ -4359,8 +4183,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "rbd.monitors", }}, - }, - { + }, { name: "empty image", vol: core.Volume{ Name: "rbd", @@ -4402,8 +4225,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "empty cephfs monitors", vol: core.Volume{ Name: "cephfs", @@ -4425,151 +4247,129 @@ func TestValidateVolumes(t *testing.T) { Name: "downwardapi", VolumeSource: core.VolumeSource{ DownwardAPI: &core.DownwardAPIVolumeSource{ - Items: []core.DownwardAPIVolumeFile{ - { - Path: "labels", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, + Items: []core.DownwardAPIVolumeFile{{ + Path: "labels", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", }, - { - Path: "labels with subscript", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels['key']", - }, + }, { + Path: "labels with subscript", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels['key']", }, - { - Path: "labels with complex subscript", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels['test.example.com/key']", - }, + }, { + Path: "labels with complex subscript", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels['test.example.com/key']", }, - { - Path: "annotations", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.annotations", - }, + }, { + Path: "annotations", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.annotations", }, - { - Path: "annotations with subscript", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.annotations['key']", - }, + }, { + Path: "annotations with subscript", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.annotations['key']", }, - { - Path: "annotations with complex subscript", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.annotations['TEST.EXAMPLE.COM/key']", - }, + }, { + Path: "annotations with complex subscript", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.annotations['TEST.EXAMPLE.COM/key']", }, - { - Path: "namespace", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.namespace", - }, + }, { + Path: "namespace", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.namespace", }, - { - Path: "name", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.name", - }, + }, { + Path: "name", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.name", }, - { - Path: "path/with/subdirs", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, + }, { + Path: "path/with/subdirs", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", }, - { - Path: "path/./withdot", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, + }, { + Path: "path/./withdot", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", }, - { - Path: "path/with/embedded..dotdot", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, + }, { + Path: "path/with/embedded..dotdot", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", }, - { - Path: "path/with/leading/..dotdot", - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, + }, { + Path: "path/with/leading/..dotdot", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", }, - { - Path: "cpu_limit", - ResourceFieldRef: &core.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "limits.cpu", - }, + }, { + Path: "cpu_limit", + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "limits.cpu", }, - { - Path: "cpu_request", - ResourceFieldRef: &core.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "requests.cpu", - }, + }, { + Path: "cpu_request", + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "requests.cpu", }, - { - Path: "memory_limit", - ResourceFieldRef: &core.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "limits.memory", - }, + }, { + Path: "memory_limit", + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "limits.memory", }, - { - Path: "memory_request", - ResourceFieldRef: &core.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "requests.memory", - }, + }, { + Path: "memory_request", + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "requests.memory", }, - }, + }}, }, }, }, - }, - { + }, { name: "hugepages-downwardAPI-enabled", vol: core.Volume{ Name: "downwardapi", VolumeSource: core.VolumeSource{ DownwardAPI: &core.DownwardAPIVolumeSource{ - Items: []core.DownwardAPIVolumeFile{ - { - Path: "hugepages_request", - ResourceFieldRef: &core.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "requests.hugepages-2Mi", - }, + Items: []core.DownwardAPIVolumeFile{{ + Path: "hugepages_request", + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "requests.hugepages-2Mi", }, - { - Path: "hugepages_limit", - ResourceFieldRef: &core.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "limits.hugepages-2Mi", - }, + }, { + Path: "hugepages_limit", + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "limits.hugepages-2Mi", }, - }, + }}, }, }, }, - }, - { + }, { name: "downapi valid defaultMode", vol: core.Volume{ Name: "downapi", @@ -4579,8 +4379,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "downapi valid item mode", vol: core.Volume{ Name: "downapi", @@ -4597,8 +4396,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "downapi invalid positive item mode", vol: core.Volume{ Name: "downapi", @@ -4619,8 +4417,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "downwardAPI.mode", }}, - }, - { + }, { name: "downapi invalid negative item mode", vol: core.Volume{ Name: "downapi", @@ -4641,8 +4438,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "downwardAPI.mode", }}, - }, - { + }, { name: "downapi empty metatada path", vol: core.Volume{ Name: "downapi", @@ -4662,8 +4458,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "downwardAPI.path", }}, - }, - { + }, { name: "downapi absolute path", vol: core.Volume{ Name: "downapi", @@ -4683,8 +4478,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "downwardAPI.path", }}, - }, - { + }, { name: "downapi dot dot path", vol: core.Volume{ Name: "downapi", @@ -4705,8 +4499,7 @@ func TestValidateVolumes(t *testing.T) { field: "downwardAPI.path", detail: `must not contain '..'`, }}, - }, - { + }, { name: "downapi dot dot file name", vol: core.Volume{ Name: "downapi", @@ -4727,8 +4520,7 @@ func TestValidateVolumes(t *testing.T) { field: "downwardAPI.path", detail: `must not start with '..'`, }}, - }, - { + }, { name: "downapi dot dot first level dirent", vol: core.Volume{ Name: "downapi", @@ -4749,8 +4541,7 @@ func TestValidateVolumes(t *testing.T) { field: "downwardAPI.path", detail: `must not start with '..'`, }}, - }, - { + }, { name: "downapi fieldRef and ResourceFieldRef together", vol: core.Volume{ Name: "downapi", @@ -4775,8 +4566,7 @@ func TestValidateVolumes(t *testing.T) { field: "downwardAPI", detail: "fieldRef and resourceFieldRef can not be specified simultaneously", }}, - }, - { + }, { name: "downapi invalid positive defaultMode", vol: core.Volume{ Name: "downapi", @@ -4790,8 +4580,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "downwardAPI.defaultMode", }}, - }, - { + }, { name: "downapi invalid negative defaultMode", vol: core.Volume{ Name: "downapi", @@ -4820,8 +4609,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "FC valid wwids", vol: core.Volume{ Name: "fc", @@ -4833,8 +4621,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "FC empty targetWWNs and wwids", vol: core.Volume{ Name: "fc", @@ -4853,8 +4640,7 @@ func TestValidateVolumes(t *testing.T) { field: "fc.targetWWNs", detail: "must specify either targetWWNs or wwids", }}, - }, - { + }, { name: "FC invalid: both targetWWNs and wwids simultaneously", vol: core.Volume{ Name: "fc", @@ -4873,8 +4659,7 @@ func TestValidateVolumes(t *testing.T) { field: "fc.targetWWNs", detail: "targetWWNs and wwids can not be specified simultaneously", }}, - }, - { + }, { name: "FC valid targetWWNs and empty lun", vol: core.Volume{ Name: "fc", @@ -4892,8 +4677,7 @@ func TestValidateVolumes(t *testing.T) { field: "fc.lun", detail: "lun is required if targetWWNs is specified", }}, - }, - { + }, { name: "FC valid targetWWNs and invalid lun", vol: core.Volume{ Name: "fc", @@ -4938,8 +4722,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "AzureFile empty secret", vol: core.Volume{ Name: "azure-file", @@ -4955,8 +4738,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "azureFile.secretName", }}, - }, - { + }, { name: "AzureFile empty share", vol: core.Volume{ Name: "azure-file", @@ -4989,8 +4771,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "empty registry quobyte", vol: core.Volume{ Name: "quobyte", @@ -5005,8 +4786,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "quobyte.registry", }}, - }, - { + }, { name: "wrong format registry quobyte", vol: core.Volume{ Name: "quobyte", @@ -5022,8 +4802,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "quobyte.registry", }}, - }, - { + }, { name: "wrong format multiple registries quobyte", vol: core.Volume{ Name: "quobyte", @@ -5039,8 +4818,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeInvalid, field: "quobyte.registry", }}, - }, - { + }, { name: "empty volume quobyte", vol: core.Volume{ Name: "quobyte", @@ -5055,8 +4833,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "quobyte.volume", }}, - }, - { + }, { name: "empty tenant quobyte", vol: core.Volume{ Name: "quobyte", @@ -5068,8 +4845,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "too long tenant quobyte", vol: core.Volume{ Name: "quobyte", @@ -5098,8 +4874,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "AzureDisk empty disk name", vol: core.Volume{ Name: "azure-disk", @@ -5114,8 +4889,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "azureDisk.diskName", }}, - }, - { + }, { name: "AzureDisk empty disk uri", vol: core.Volume{ Name: "azure-disk", @@ -5144,8 +4918,7 @@ func TestValidateVolumes(t *testing.T) { }, }, }, - }, - { + }, { name: "ScaleIO with empty name", vol: core.Volume{ Name: "scaleio-volume", @@ -5161,8 +4934,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "scaleIO.volumeName", }}, - }, - { + }, { name: "ScaleIO with empty gateway", vol: core.Volume{ Name: "scaleio-volume", @@ -5178,8 +4950,7 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeRequired, field: "scaleIO.gateway", }}, - }, - { + }, { name: "ScaleIO with empty system", vol: core.Volume{ Name: "scaleio-volume", @@ -5203,23 +4974,20 @@ func TestValidateVolumes(t *testing.T) { Name: "projected-volume", VolumeSource: core.VolumeSource{ Projected: &core.ProjectedVolumeSource{ - Sources: []core.VolumeProjection{ - { - Secret: &core.SecretProjection{ - LocalObjectReference: core.LocalObjectReference{ - Name: "foo", - }, + Sources: []core.VolumeProjection{{ + Secret: &core.SecretProjection{ + LocalObjectReference: core.LocalObjectReference{ + Name: "foo", }, }, - { - Secret: &core.SecretProjection{ - LocalObjectReference: core.LocalObjectReference{ - Name: "foo", - }, + }, { + Secret: &core.SecretProjection{ + LocalObjectReference: core.LocalObjectReference{ + Name: "foo", }, - DownwardAPI: &core.DownwardAPIProjection{}, }, - }, + DownwardAPI: &core.DownwardAPIProjection{}, + }}, }, }, }, @@ -5227,39 +4995,31 @@ func TestValidateVolumes(t *testing.T) { etype: field.ErrorTypeForbidden, field: "projected.sources[1]", }}, - }, - { + }, { name: "ProjectedVolumeSource more than one projection in a source", vol: core.Volume{ Name: "projected-volume", VolumeSource: core.VolumeSource{ Projected: &core.ProjectedVolumeSource{ - Sources: []core.VolumeProjection{ - { - Secret: &core.SecretProjection{}, - }, - { - Secret: &core.SecretProjection{}, - DownwardAPI: &core.DownwardAPIProjection{}, - }, - }, + Sources: []core.VolumeProjection{{ + Secret: &core.SecretProjection{}, + }, { + Secret: &core.SecretProjection{}, + DownwardAPI: &core.DownwardAPIProjection{}, + }}, }, }, }, - errs: []verr{ - { - etype: field.ErrorTypeRequired, - field: "projected.sources[0].secret.name", - }, - { - etype: field.ErrorTypeRequired, - field: "projected.sources[1].secret.name", - }, - { - etype: field.ErrorTypeForbidden, - field: "projected.sources[1]", - }, - }, + errs: []verr{{ + etype: field.ErrorTypeRequired, + field: "projected.sources[0].secret.name", + }, { + etype: field.ErrorTypeRequired, + field: "projected.sources[1].secret.name", + }, { + etype: field.ErrorTypeForbidden, + field: "projected.sources[1]", + }}, }, } @@ -5317,91 +5077,79 @@ func TestValidateReadOnlyPersistentDisks(t *testing.T) { oldVolume []core.Volume gateValue bool expectError bool - }{ - { - name: "gate on, read-only disk, nil old", - gateValue: true, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - oldVolume: []core.Volume(nil), - expectError: false, - }, - { - name: "gate off, read-only disk, nil old", - gateValue: false, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - oldVolume: []core.Volume(nil), - expectError: false, - }, - { - name: "gate on, read-write, nil old", - gateValue: true, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, - oldVolume: []core.Volume(nil), - expectError: false, - }, - { - name: "gate off, read-write, nil old", - gateValue: false, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, - oldVolume: []core.Volume(nil), - expectError: true, - }, - { - name: "gate on, new read-only and old read-write", - gateValue: true, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, - expectError: false, - }, - { - name: "gate off, new read-only and old read-write", - gateValue: false, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, - expectError: false, - }, - { - name: "gate on, new read-write and old read-write", - gateValue: true, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, - expectError: false, - }, - { - name: "gate off, new read-write and old read-write", - gateValue: false, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, - oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, - expectError: false, - }, - { - name: "gate on, new read-only and old read-only", - gateValue: true, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - expectError: false, - }, - { - name: "gate off, new read-only and old read-only", - gateValue: false, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - expectError: false, - }, - { - name: "gate on, new read-write and old read-only", - gateValue: true, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, - oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - expectError: false, - }, - { - name: "gate off, new read-write and old read-only", - gateValue: false, - volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, - oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, - expectError: true, - }, + }{{ + name: "gate on, read-only disk, nil old", + gateValue: true, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + oldVolume: []core.Volume(nil), + expectError: false, + }, { + name: "gate off, read-only disk, nil old", + gateValue: false, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + oldVolume: []core.Volume(nil), + expectError: false, + }, { + name: "gate on, read-write, nil old", + gateValue: true, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, + oldVolume: []core.Volume(nil), + expectError: false, + }, { + name: "gate off, read-write, nil old", + gateValue: false, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, + oldVolume: []core.Volume(nil), + expectError: true, + }, { + name: "gate on, new read-only and old read-write", + gateValue: true, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, + expectError: false, + }, { + name: "gate off, new read-only and old read-write", + gateValue: false, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, + expectError: false, + }, { + name: "gate on, new read-write and old read-write", + gateValue: true, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, + expectError: false, + }, { + name: "gate off, new read-write and old read-write", + gateValue: false, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, + oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, + expectError: false, + }, { + name: "gate on, new read-only and old read-only", + gateValue: true, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + expectError: false, + }, { + name: "gate off, new read-only and old read-only", + gateValue: false, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + expectError: false, + }, { + name: "gate on, new read-write and old read-only", + gateValue: true, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, + oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + expectError: false, + }, { + name: "gate off, new read-write and old read-only", + gateValue: false, + volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, + oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, + expectError: true, + }, } for _, testCase := range cases { t.Run(testCase.name, func(t *testing.T) { @@ -5424,23 +5172,21 @@ func TestHugePagesIsolation(t *testing.T) { pod: &core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - }, - Limits: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - }, + Containers: []core.Container{{ + Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), }, }, - }, + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -5450,25 +5196,23 @@ func TestHugePagesIsolation(t *testing.T) { pod: &core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), - }, - Limits: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), - }, + Containers: []core.Container{{ + Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), }, }, - }, + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -5479,25 +5223,23 @@ func TestHugePagesIsolation(t *testing.T) { pod: &core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), - }, - Limits: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), - }, + Containers: []core.Container{{ + Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), }, }, - }, + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -5507,19 +5249,17 @@ func TestHugePagesIsolation(t *testing.T) { pod: &core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "hugepages-requireCpuOrMemory", Namespace: "ns"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - }, - Limits: core.ResourceList{ - core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - }, + Containers: []core.Container{{ + Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + }, + Limits: core.ResourceList{ + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), }, }, - }, + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -5530,23 +5270,21 @@ func TestHugePagesIsolation(t *testing.T) { pod: &core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - }, - Limits: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - core.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"), - }, + Containers: []core.Container{{ + Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"), }, }, - }, + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -5860,25 +5598,23 @@ func TestValidatePorts(t *testing.T) { } func TestLocalStorageEnvWithFeatureGate(t *testing.T) { - testCases := []core.EnvVar{ - { - Name: "ephemeral-storage-limits", - ValueFrom: &core.EnvVarSource{ - ResourceFieldRef: &core.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "limits.ephemeral-storage", - }, + testCases := []core.EnvVar{{ + Name: "ephemeral-storage-limits", + ValueFrom: &core.EnvVarSource{ + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "limits.ephemeral-storage", }, }, - { - Name: "ephemeral-storage-requests", - ValueFrom: &core.EnvVarSource{ - ResourceFieldRef: &core.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "requests.ephemeral-storage", - }, + }, { + Name: "ephemeral-storage-requests", + ValueFrom: &core.EnvVarSource{ + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "requests.ephemeral-storage", }, }, + }, } for _, testCase := range testCases { if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 { @@ -5888,25 +5624,23 @@ func TestLocalStorageEnvWithFeatureGate(t *testing.T) { } func TestHugePagesEnv(t *testing.T) { - testCases := []core.EnvVar{ - { - Name: "hugepages-limits", - ValueFrom: &core.EnvVarSource{ - ResourceFieldRef: &core.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "limits.hugepages-2Mi", - }, + testCases := []core.EnvVar{{ + Name: "hugepages-limits", + ValueFrom: &core.EnvVarSource{ + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "limits.hugepages-2Mi", }, }, - { - Name: "hugepages-requests", - ValueFrom: &core.EnvVarSource{ - ResourceFieldRef: &core.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "requests.hugepages-2Mi", - }, + }, { + Name: "hugepages-requests", + ValueFrom: &core.EnvVarSource{ + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "requests.hugepages-2Mi", }, }, + }, } // enable gate for _, testCase := range testCases { @@ -5927,8 +5661,7 @@ func TestValidateEnv(t *testing.T) { {Name: "AbC_123", Value: "value"}, {Name: "abc", Value: ""}, {Name: "a.b.c", Value: "value"}, - {Name: "a-b-c", Value: "value"}, - { + {Name: "a-b-c", Value: "value"}, { Name: "abc", ValueFrom: &core.EnvVarSource{ FieldRef: &core.ObjectFieldSelector{ @@ -5936,8 +5669,7 @@ func TestValidateEnv(t *testing.T) { FieldPath: "metadata.annotations['key']", }, }, - }, - { + }, { Name: "abc", ValueFrom: &core.EnvVarSource{ FieldRef: &core.ObjectFieldSelector{ @@ -5945,8 +5677,7 @@ func TestValidateEnv(t *testing.T) { FieldPath: "metadata.labels['key']", }, }, - }, - { + }, { Name: "abc", ValueFrom: &core.EnvVarSource{ FieldRef: &core.ObjectFieldSelector{ @@ -5954,8 +5685,7 @@ func TestValidateEnv(t *testing.T) { FieldPath: "metadata.name", }, }, - }, - { + }, { Name: "abc", ValueFrom: &core.EnvVarSource{ FieldRef: &core.ObjectFieldSelector{ @@ -5963,8 +5693,7 @@ func TestValidateEnv(t *testing.T) { FieldPath: "metadata.namespace", }, }, - }, - { + }, { Name: "abc", ValueFrom: &core.EnvVarSource{ FieldRef: &core.ObjectFieldSelector{ @@ -5972,8 +5701,7 @@ func TestValidateEnv(t *testing.T) { FieldPath: "metadata.uid", }, }, - }, - { + }, { Name: "abc", ValueFrom: &core.EnvVarSource{ FieldRef: &core.ObjectFieldSelector{ @@ -5981,8 +5709,7 @@ func TestValidateEnv(t *testing.T) { FieldPath: "spec.nodeName", }, }, - }, - { + }, { Name: "abc", ValueFrom: &core.EnvVarSource{ FieldRef: &core.ObjectFieldSelector{ @@ -5990,8 +5717,7 @@ func TestValidateEnv(t *testing.T) { FieldPath: "spec.serviceAccountName", }, }, - }, - { + }, { Name: "abc", ValueFrom: &core.EnvVarSource{ FieldRef: &core.ObjectFieldSelector{ @@ -5999,8 +5725,7 @@ func TestValidateEnv(t *testing.T) { FieldPath: "status.hostIP", }, }, - }, - { + }, { Name: "abc", ValueFrom: &core.EnvVarSource{ FieldRef: &core.ObjectFieldSelector{ @@ -6008,8 +5733,7 @@ func TestValidateEnv(t *testing.T) { FieldPath: "status.podIP", }, }, - }, - { + }, { Name: "abc", ValueFrom: &core.EnvVarSource{ FieldRef: &core.ObjectFieldSelector{ @@ -6017,8 +5741,7 @@ func TestValidateEnv(t *testing.T) { FieldPath: "status.podIPs", }, }, - }, - { + }, { Name: "secret_value", ValueFrom: &core.EnvVarSource{ SecretKeyRef: &core.SecretKeySelector{ @@ -6028,8 +5751,7 @@ func TestValidateEnv(t *testing.T) { Key: "secret-key", }, }, - }, - { + }, { Name: "ENV_VAR_1", ValueFrom: &core.EnvVarSource{ ConfigMapKeyRef: &core.ConfigMapKeySelector{ @@ -6049,260 +5771,239 @@ func TestValidateEnv(t *testing.T) { name string envs []core.EnvVar expectedError string - }{ - { - name: "zero-length name", - envs: []core.EnvVar{{Name: ""}}, - expectedError: "[0].name: Required value", - }, - { - name: "illegal character", - envs: []core.EnvVar{{Name: "a!b"}}, - expectedError: `[0].name: Invalid value: "a!b": ` + envVarNameErrMsg, - }, - { - name: "dot only", - envs: []core.EnvVar{{Name: "."}}, - expectedError: `[0].name: Invalid value: ".": must not be`, - }, - { - name: "double dots only", - envs: []core.EnvVar{{Name: ".."}}, - expectedError: `[0].name: Invalid value: "..": must not be`, - }, - { - name: "leading double dots", - envs: []core.EnvVar{{Name: "..abc"}}, - expectedError: `[0].name: Invalid value: "..abc": must not start with`, - }, - { - name: "value and valueFrom specified", - envs: []core.EnvVar{{ - Name: "abc", - Value: "foo", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.name", - }, + }{{ + name: "zero-length name", + envs: []core.EnvVar{{Name: ""}}, + expectedError: "[0].name: Required value", + }, { + name: "illegal character", + envs: []core.EnvVar{{Name: "a!b"}}, + expectedError: `[0].name: Invalid value: "a!b": ` + envVarNameErrMsg, + }, { + name: "dot only", + envs: []core.EnvVar{{Name: "."}}, + expectedError: `[0].name: Invalid value: ".": must not be`, + }, { + name: "double dots only", + envs: []core.EnvVar{{Name: ".."}}, + expectedError: `[0].name: Invalid value: "..": must not be`, + }, { + name: "leading double dots", + envs: []core.EnvVar{{Name: "..abc"}}, + expectedError: `[0].name: Invalid value: "..abc": must not start with`, + }, { + name: "value and valueFrom specified", + envs: []core.EnvVar{{ + Name: "abc", + Value: "foo", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.name", }, - }}, - expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty", - }, - { - name: "valueFrom without a source", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{}, - }}, - expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`", - }, - { - name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.name", - }, - SecretKeyRef: &core.SecretKeySelector{ - LocalObjectReference: core.LocalObjectReference{ - Name: "a-secret", - }, - Key: "a-key", - }, + }, + }}, + expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty", + }, { + name: "valueFrom without a source", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{}, + }}, + expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`", + }, { + name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.name", }, - }}, - expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time", - }, - { - name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set", - envs: []core.EnvVar{{ - Name: "some_var_name", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.name", - }, - ConfigMapKeyRef: &core.ConfigMapKeySelector{ - LocalObjectReference: core.LocalObjectReference{ - Name: "some-config-map", - }, - Key: "some-key", + SecretKeyRef: &core.SecretKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "a-secret", }, + Key: "a-key", }, - }}, - expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`, - }, - { - name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.name", - }, - SecretKeyRef: &core.SecretKeySelector{ - LocalObjectReference: core.LocalObjectReference{ - Name: "a-secret", - }, - Key: "a-key", - }, - ConfigMapKeyRef: &core.ConfigMapKeySelector{ - LocalObjectReference: core.LocalObjectReference{ - Name: "some-config-map", - }, - Key: "some-key", - }, + }, + }}, + expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time", + }, { + name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set", + envs: []core.EnvVar{{ + Name: "some_var_name", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.name", }, - }}, - expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`, - }, - { - name: "valueFrom.secretKeyRef.name invalid", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - SecretKeyRef: &core.SecretKeySelector{ - LocalObjectReference: core.LocalObjectReference{ - Name: "$%^&*#", - }, - Key: "a-key", + ConfigMapKeyRef: &core.ConfigMapKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "some-config-map", }, + Key: "some-key", }, - }}, - }, - { - name: "valueFrom.configMapKeyRef.name invalid", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - ConfigMapKeyRef: &core.ConfigMapKeySelector{ - LocalObjectReference: core.LocalObjectReference{ - Name: "$%^&*#", - }, - Key: "some-key", + }, + }}, + expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`, + }, { + name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.name", + }, + SecretKeyRef: &core.SecretKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "a-secret", }, + Key: "a-key", }, - }}, - }, - { - name: "missing FieldPath on ObjectFieldSelector", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - APIVersion: "v1", + ConfigMapKeyRef: &core.ConfigMapKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "some-config-map", }, + Key: "some-key", }, - }}, - expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`, - }, - { - name: "missing APIVersion on ObjectFieldSelector", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - FieldPath: "metadata.name", + }, + }}, + expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`, + }, { + name: "valueFrom.secretKeyRef.name invalid", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + SecretKeyRef: &core.SecretKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "$%^&*#", }, + Key: "a-key", }, - }}, - expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`, - }, - { - name: "invalid fieldPath", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - FieldPath: "metadata.whoops", - APIVersion: "v1", + }, + }}, + }, { + name: "valueFrom.configMapKeyRef.name invalid", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + ConfigMapKeyRef: &core.ConfigMapKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "$%^&*#", }, + Key: "some-key", }, - }}, - expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`, - }, - { - name: "metadata.name with subscript", - envs: []core.EnvVar{{ - Name: "labels", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - FieldPath: "metadata.name['key']", - APIVersion: "v1", - }, + }, + }}, + }, { + name: "missing FieldPath on ObjectFieldSelector", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", }, - }}, - expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`, - }, - { - name: "metadata.labels without subscript", - envs: []core.EnvVar{{ - Name: "labels", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - FieldPath: "metadata.labels", - APIVersion: "v1", - }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`, + }, { + name: "missing APIVersion on ObjectFieldSelector", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.name", }, - }}, - expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`, - }, - { - name: "metadata.annotations without subscript", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - FieldPath: "metadata.annotations", - APIVersion: "v1", - }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`, + }, { + name: "invalid fieldPath", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.whoops", + APIVersion: "v1", }, - }}, - expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`, - }, - { - name: "metadata.annotations with invalid key", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - FieldPath: "metadata.annotations['invalid~key']", - APIVersion: "v1", - }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`, + }, { + name: "metadata.name with subscript", + envs: []core.EnvVar{{ + Name: "labels", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.name['key']", + APIVersion: "v1", }, - }}, - expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`, - }, - { - name: "metadata.labels with invalid key", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - FieldPath: "metadata.labels['Www.k8s.io/test']", - APIVersion: "v1", - }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`, + }, { + name: "metadata.labels without subscript", + envs: []core.EnvVar{{ + Name: "labels", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.labels", + APIVersion: "v1", }, - }}, - expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`, - }, - { - name: "unsupported fieldPath", - envs: []core.EnvVar{{ - Name: "abc", - ValueFrom: &core.EnvVarSource{ - FieldRef: &core.ObjectFieldSelector{ - FieldPath: "status.phase", - APIVersion: "v1", - }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`, + }, { + name: "metadata.annotations without subscript", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.annotations", + APIVersion: "v1", }, - }}, - expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`, - }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`, + }, { + name: "metadata.annotations with invalid key", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.annotations['invalid~key']", + APIVersion: "v1", + }, + }, + }}, + expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`, + }, { + name: "metadata.labels with invalid key", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.labels['Www.k8s.io/test']", + APIVersion: "v1", + }, + }, + }}, + expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`, + }, { + name: "unsupported fieldPath", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "status.phase", + APIVersion: "v1", + }, + }, + }}, + expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`, + }, } for _, tc := range errorCases { if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 { @@ -6319,41 +6020,35 @@ func TestValidateEnv(t *testing.T) { } func TestValidateEnvFrom(t *testing.T) { - successCase := []core.EnvFromSource{ - { - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "abc"}, - }, + successCase := []core.EnvFromSource{{ + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, }, - { - Prefix: "pre_", - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "abc"}, - }, + }, { + Prefix: "pre_", + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, }, - { - Prefix: "a.b", - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "abc"}, - }, + }, { + Prefix: "a.b", + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, }, - { - SecretRef: &core.SecretEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "abc"}, - }, + }, { + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, }, - { - Prefix: "pre_", - SecretRef: &core.SecretEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "abc"}, - }, + }, { + Prefix: "pre_", + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, }, - { - Prefix: "a.b", - SecretRef: &core.SecretEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "abc"}, - }, + }, { + Prefix: "a.b", + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, }, + }, } if errs := ValidateEnvFrom(successCase, field.NewPath("field")); len(errs) != 0 { t.Errorf("expected success: %v", errs) @@ -6363,108 +6058,80 @@ func TestValidateEnvFrom(t *testing.T) { name string envs []core.EnvFromSource expectedError string - }{ - { - name: "zero-length name", - envs: []core.EnvFromSource{ - { - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: ""}}, - }, - }, - expectedError: "field[0].configMapRef.name: Required value", - }, - { - name: "invalid name", - envs: []core.EnvFromSource{ - { - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "$"}}, - }, - }, - expectedError: "field[0].configMapRef.name: Invalid value", - }, - { - name: "invalid prefix", - envs: []core.EnvFromSource{ - { - Prefix: "a!b", - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, - }, - }, - expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg, - }, - { - name: "zero-length name", - envs: []core.EnvFromSource{ - { - SecretRef: &core.SecretEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: ""}}, - }, - }, - expectedError: "field[0].secretRef.name: Required value", - }, - { - name: "invalid name", - envs: []core.EnvFromSource{ - { - SecretRef: &core.SecretEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "&"}}, - }, - }, - expectedError: "field[0].secretRef.name: Invalid value", - }, - { - name: "invalid prefix", - envs: []core.EnvFromSource{ - { - Prefix: "a!b", - SecretRef: &core.SecretEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, - }, - }, - expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg, - }, - { - name: "no refs", - envs: []core.EnvFromSource{ - {}, - }, - expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`", - }, - { - name: "multiple refs", - envs: []core.EnvFromSource{ - { - SecretRef: &core.SecretEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, - }, - }, - expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time", - }, - { - name: "invalid secret ref name", - envs: []core.EnvFromSource{ - { - SecretRef: &core.SecretEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}}, - }, - }, - expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, - }, - { - name: "invalid config ref name", - envs: []core.EnvFromSource{ - { - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}}, - }, - }, - expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, + }{{ + name: "zero-length name", + envs: []core.EnvFromSource{{ + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: ""}}, + }}, + expectedError: "field[0].configMapRef.name: Required value", + }, { + name: "invalid name", + envs: []core.EnvFromSource{{ + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "$"}}, + }}, + expectedError: "field[0].configMapRef.name: Invalid value", + }, { + name: "invalid prefix", + envs: []core.EnvFromSource{{ + Prefix: "a!b", + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, + }}, + expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg, + }, { + name: "zero-length name", + envs: []core.EnvFromSource{{ + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: ""}}, + }}, + expectedError: "field[0].secretRef.name: Required value", + }, { + name: "invalid name", + envs: []core.EnvFromSource{{ + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "&"}}, + }}, + expectedError: "field[0].secretRef.name: Invalid value", + }, { + name: "invalid prefix", + envs: []core.EnvFromSource{{ + Prefix: "a!b", + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, + }}, + expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg, + }, { + name: "no refs", + envs: []core.EnvFromSource{ + {}, }, + expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`", + }, { + name: "multiple refs", + envs: []core.EnvFromSource{{ + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, + }}, + expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time", + }, { + name: "invalid secret ref name", + envs: []core.EnvFromSource{{ + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}}, + }}, + expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, + }, { + name: "invalid config ref name", + envs: []core.EnvFromSource{{ + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}}, + }}, + expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, + }, } for _, tc := range errorCases { if errs := ValidateEnvFrom(tc.envs, field.NewPath("field")); len(errs) == 0 { @@ -6580,43 +6247,35 @@ func TestValidateSubpathMutuallyExclusive(t *testing.T) { expectError bool }{ "subpath and subpathexpr not specified": { - []core.VolumeMount{ - { - Name: "abc-123", - MountPath: "/bab", - }, - }, + []core.VolumeMount{{ + Name: "abc-123", + MountPath: "/bab", + }}, false, }, "subpath expr specified": { - []core.VolumeMount{ - { - Name: "abc-123", - MountPath: "/bab", - SubPathExpr: "$(POD_NAME)", - }, - }, + []core.VolumeMount{{ + Name: "abc-123", + MountPath: "/bab", + SubPathExpr: "$(POD_NAME)", + }}, false, }, "subpath specified": { - []core.VolumeMount{ - { - Name: "abc-123", - MountPath: "/bab", - SubPath: "baz", - }, - }, + []core.VolumeMount{{ + Name: "abc-123", + MountPath: "/bab", + SubPath: "baz", + }}, false, }, "subpath and subpathexpr specified": { - []core.VolumeMount{ - { - Name: "abc-123", - MountPath: "/bab", - SubPath: "baz", - SubPathExpr: "$(POD_NAME)", - }, - }, + []core.VolumeMount{{ + Name: "abc-123", + MountPath: "/bab", + SubPath: "baz", + SubPathExpr: "$(POD_NAME)", + }}, true, }, } @@ -6661,22 +6320,18 @@ func TestValidateDisabledSubpathExpr(t *testing.T) { expectError bool }{ "subpath expr not specified": { - []core.VolumeMount{ - { - Name: "abc-123", - MountPath: "/bab", - }, - }, + []core.VolumeMount{{ + Name: "abc-123", + MountPath: "/bab", + }}, false, }, "subpath expr specified": { - []core.VolumeMount{ - { - Name: "abc-123", - MountPath: "/bab", - SubPathExpr: "$(POD_NAME)", - }, - }, + []core.VolumeMount{{ + Name: "abc-123", + MountPath: "/bab", + SubPathExpr: "$(POD_NAME)", + }}, false, }, } @@ -6718,79 +6373,67 @@ func TestValidateMountPropagation(t *testing.T) { mount core.VolumeMount container *core.Container expectError bool - }{ - { - // implicitly non-privileged container + no propagation - core.VolumeMount{Name: "foo", MountPath: "/foo"}, - defaultContainer, - false, - }, - { - // implicitly non-privileged container + HostToContainer - core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, - defaultContainer, - false, - }, - { - // non-privileged container + None - core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationNone}, - defaultContainer, - false, - }, - { - // error: implicitly non-privileged container + Bidirectional - core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, - defaultContainer, - true, - }, - { - // explicitly non-privileged container + no propagation - core.VolumeMount{Name: "foo", MountPath: "/foo"}, - nonPrivilegedContainer, - false, - }, - { - // explicitly non-privileged container + HostToContainer - core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, - nonPrivilegedContainer, - false, - }, - { - // explicitly non-privileged container + HostToContainer - core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, - nonPrivilegedContainer, - true, - }, - { - // privileged container + no propagation - core.VolumeMount{Name: "foo", MountPath: "/foo"}, - privilegedContainer, - false, - }, - { - // privileged container + HostToContainer - core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, - privilegedContainer, - false, - }, - { - // privileged container + Bidirectional - core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, - privilegedContainer, - false, - }, - { - // error: privileged container + invalid mount propagation - core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid}, - privilegedContainer, - true, - }, - { - // no container + Bidirectional - core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, - nil, - false, - }, + }{{ + // implicitly non-privileged container + no propagation + core.VolumeMount{Name: "foo", MountPath: "/foo"}, + defaultContainer, + false, + }, { + // implicitly non-privileged container + HostToContainer + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, + defaultContainer, + false, + }, { + // non-privileged container + None + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationNone}, + defaultContainer, + false, + }, { + // error: implicitly non-privileged container + Bidirectional + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, + defaultContainer, + true, + }, { + // explicitly non-privileged container + no propagation + core.VolumeMount{Name: "foo", MountPath: "/foo"}, + nonPrivilegedContainer, + false, + }, { + // explicitly non-privileged container + HostToContainer + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, + nonPrivilegedContainer, + false, + }, { + // explicitly non-privileged container + HostToContainer + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, + nonPrivilegedContainer, + true, + }, { + // privileged container + no propagation + core.VolumeMount{Name: "foo", MountPath: "/foo"}, + privilegedContainer, + false, + }, { + // privileged container + HostToContainer + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, + privilegedContainer, + false, + }, { + // privileged container + Bidirectional + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, + privilegedContainer, + false, + }, { + // error: privileged container + invalid mount propagation + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid}, + privilegedContainer, + true, + }, { + // no container + Bidirectional + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, + nil, + false, + }, } volumes := []core.Volume{ @@ -6922,103 +6565,93 @@ func Test_validateProbe(t *testing.T) { name string args args want field.ErrorList - }{ - { - args: args{ - probe: &core.Probe{}, - fldPath: fldPath, - }, - want: field.ErrorList{field.Required(fldPath, "must specify a handler type")}, + }{{ + args: args{ + probe: &core.Probe{}, + fldPath: fldPath, }, - { - args: args{ - probe: &core.Probe{ - ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, - }, - fldPath: fldPath, + want: field.ErrorList{field.Required(fldPath, "must specify a handler type")}, + }, { + args: args{ + probe: &core.Probe{ + ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, }, - want: field.ErrorList{}, + fldPath: fldPath, }, - { - args: args{ - probe: &core.Probe{ - ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, - InitialDelaySeconds: -1, - }, - fldPath: fldPath, + want: field.ErrorList{}, + }, { + args: args{ + probe: &core.Probe{ + ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, + InitialDelaySeconds: -1, }, - want: field.ErrorList{field.Invalid(fldPath.Child("initialDelaySeconds"), -1, "must be greater than or equal to 0")}, + fldPath: fldPath, }, - { - args: args{ - probe: &core.Probe{ - ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, - TimeoutSeconds: -1, - }, - fldPath: fldPath, + want: field.ErrorList{field.Invalid(fldPath.Child("initialDelaySeconds"), -1, "must be greater than or equal to 0")}, + }, { + args: args{ + probe: &core.Probe{ + ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, + TimeoutSeconds: -1, }, - want: field.ErrorList{field.Invalid(fldPath.Child("timeoutSeconds"), -1, "must be greater than or equal to 0")}, + fldPath: fldPath, }, - { - args: args{ - probe: &core.Probe{ - ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, - PeriodSeconds: -1, - }, - fldPath: fldPath, + want: field.ErrorList{field.Invalid(fldPath.Child("timeoutSeconds"), -1, "must be greater than or equal to 0")}, + }, { + args: args{ + probe: &core.Probe{ + ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, + PeriodSeconds: -1, }, - want: field.ErrorList{field.Invalid(fldPath.Child("periodSeconds"), -1, "must be greater than or equal to 0")}, + fldPath: fldPath, }, - { - args: args{ - probe: &core.Probe{ - ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, - SuccessThreshold: -1, - }, - fldPath: fldPath, + want: field.ErrorList{field.Invalid(fldPath.Child("periodSeconds"), -1, "must be greater than or equal to 0")}, + }, { + args: args{ + probe: &core.Probe{ + ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, + SuccessThreshold: -1, }, - want: field.ErrorList{field.Invalid(fldPath.Child("successThreshold"), -1, "must be greater than or equal to 0")}, + fldPath: fldPath, }, - { - args: args{ - probe: &core.Probe{ - ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, - FailureThreshold: -1, - }, - fldPath: fldPath, + want: field.ErrorList{field.Invalid(fldPath.Child("successThreshold"), -1, "must be greater than or equal to 0")}, + }, { + args: args{ + probe: &core.Probe{ + ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, + FailureThreshold: -1, }, - want: field.ErrorList{field.Invalid(fldPath.Child("failureThreshold"), -1, "must be greater than or equal to 0")}, + fldPath: fldPath, }, - { - args: args{ - probe: &core.Probe{ - ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, - TerminationGracePeriodSeconds: utilpointer.Int64(-1), - }, - fldPath: fldPath, + want: field.ErrorList{field.Invalid(fldPath.Child("failureThreshold"), -1, "must be greater than or equal to 0")}, + }, { + args: args{ + probe: &core.Probe{ + ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, + TerminationGracePeriodSeconds: utilpointer.Int64(-1), }, - want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), -1, "must be greater than 0")}, + fldPath: fldPath, }, - { - args: args{ - probe: &core.Probe{ - ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, - TerminationGracePeriodSeconds: utilpointer.Int64(0), - }, - fldPath: fldPath, + want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), -1, "must be greater than 0")}, + }, { + args: args{ + probe: &core.Probe{ + ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, + TerminationGracePeriodSeconds: utilpointer.Int64(0), }, - want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), 0, "must be greater than 0")}, + fldPath: fldPath, }, - { - args: args{ - probe: &core.Probe{ - ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, - TerminationGracePeriodSeconds: utilpointer.Int64(1), - }, - fldPath: fldPath, + want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), 0, "must be greater than 0")}, + }, { + args: args{ + probe: &core.Probe{ + ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, + TerminationGracePeriodSeconds: utilpointer.Int64(1), }, - want: field.ErrorList{}, + fldPath: fldPath, }, + want: field.ErrorList{}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -7244,52 +6877,46 @@ func TestValidateEphemeralContainers(t *testing.T) { {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug2", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, }, - "Single Container with Target": { - { - EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - TargetContainerName: "ctr", - }, - }, - "All allowed fields": { - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ + "Single Container with Target": {{ + EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + TargetContainerName: "ctr", + }}, + "All allowed fields": {{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debug", - Image: "image", - Command: []string{"bash"}, - Args: []string{"bash"}, - WorkingDir: "/", - EnvFrom: []core.EnvFromSource{ - { - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{Name: "dummy"}, - Optional: &[]bool{true}[0], - }, - }, + Name: "debug", + Image: "image", + Command: []string{"bash"}, + Args: []string{"bash"}, + WorkingDir: "/", + EnvFrom: []core.EnvFromSource{{ + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "dummy"}, + Optional: &[]bool{true}[0], }, - Env: []core.EnvVar{ - {Name: "TEST", Value: "TRUE"}, - }, - VolumeMounts: []core.VolumeMount{ - {Name: "vol", MountPath: "/vol"}, - }, - VolumeDevices: []core.VolumeDevice{ - {Name: "blk", DevicePath: "/dev/block"}, - }, - TerminationMessagePath: "/dev/termination-log", - TerminationMessagePolicy: "File", - ImagePullPolicy: "IfNotPresent", - SecurityContext: &core.SecurityContext{ - Capabilities: &core.Capabilities{ - Add: []core.Capability{"SYS_ADMIN"}, - }, - }, - Stdin: true, - StdinOnce: true, - TTY: true, + }}, + Env: []core.EnvVar{ + {Name: "TEST", Value: "TRUE"}, }, + VolumeMounts: []core.VolumeMount{ + {Name: "vol", MountPath: "/vol"}, + }, + VolumeDevices: []core.VolumeDevice{ + {Name: "blk", DevicePath: "/dev/block"}, + }, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + ImagePullPolicy: "IfNotPresent", + SecurityContext: &core.SecurityContext{ + Capabilities: &core.Capabilities{ + Add: []core.Capability{"SYS_ADMIN"}, + }, + }, + Stdin: true, + StdinOnce: true, + TTY: true, }, - }, + }}, } { if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}); len(errs) != 0 { t.Errorf("expected success for '%s' but got errors: %v", title, errs) @@ -7301,292 +6928,248 @@ func TestValidateEphemeralContainers(t *testing.T) { title, line string ephemeralContainers []core.EphemeralContainer expectedErrors field.ErrorList - }{ - { - "Name Collision with Container.Containers", - line(), - []core.EphemeralContainer{ - {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}}, + }{{ + "Name Collision with Container.Containers", + line(), + []core.EphemeralContainer{ + {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, }, - { - "Name Collision with Container.InitContainers", - line(), - []core.EphemeralContainer{ - {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}}, + field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}}, + }, { + "Name Collision with Container.InitContainers", + line(), + []core.EphemeralContainer{ + {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, }, - { - "Name Collision with EphemeralContainers", - line(), - []core.EphemeralContainer{ - {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[1].name"}}, + field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}}, + }, { + "Name Collision with EphemeralContainers", + line(), + []core.EphemeralContainer{ + {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, }, - { - "empty Container", - line(), - []core.EphemeralContainer{ - {EphemeralContainerCommon: core.EphemeralContainerCommon{}}, - }, - field.ErrorList{ - {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"}, - {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].image"}, - {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].terminationMessagePolicy"}, - {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].imagePullPolicy"}, - }, + field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[1].name"}}, + }, { + "empty Container", + line(), + []core.EphemeralContainer{ + {EphemeralContainerCommon: core.EphemeralContainerCommon{}}, }, - { - "empty Container Name", - line(), - []core.EphemeralContainer{ - {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - field.ErrorList{{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"}}, + field.ErrorList{ + {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"}, + {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].image"}, + {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].terminationMessagePolicy"}, + {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].imagePullPolicy"}, }, - { - "whitespace padded image name", - line(), - []core.EphemeralContainer{ - {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: " image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "ephemeralContainers[0].image"}}, + }, { + "empty Container Name", + line(), + []core.EphemeralContainer{ + {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, }, - { - "invalid image pull policy", - line(), - []core.EphemeralContainer{ - {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "PullThreeTimes", TerminationMessagePolicy: "File"}}, - }, - field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "ephemeralContainers[0].imagePullPolicy"}}, + field.ErrorList{{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"}}, + }, { + "whitespace padded image name", + line(), + []core.EphemeralContainer{ + {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: " image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, }, - { - "TargetContainerName doesn't exist", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - TargetContainerName: "bogus", - }, - }, - field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[0].targetContainerName"}}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "ephemeralContainers[0].image"}}, + }, { + "invalid image pull policy", + line(), + []core.EphemeralContainer{ + {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "PullThreeTimes", TerminationMessagePolicy: "File"}}, }, - { - "Targets an ephemeral container", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - }, - { - EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debugception", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - TargetContainerName: "debug", - }, - }, - field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[1].targetContainerName"}}, - }, - { - "Container uses disallowed field: Lifecycle", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - Lifecycle: &core.Lifecycle{ - PreStop: &core.LifecycleHandler{ - Exec: &core.ExecAction{Command: []string{"ls", "-l"}}, - }, - }, + field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "ephemeralContainers[0].imagePullPolicy"}}, + }, { + "TargetContainerName doesn't exist", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + TargetContainerName: "bogus", + }}, + field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[0].targetContainerName"}}, + }, { + "Targets an ephemeral container", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debugception", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + TargetContainerName: "debug", + }}, + field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[1].targetContainerName"}}, + }, { + "Container uses disallowed field: Lifecycle", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + Lifecycle: &core.Lifecycle{ + PreStop: &core.LifecycleHandler{ + Exec: &core.ExecAction{Command: []string{"ls", "-l"}}, }, }, }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}}, - }, - { - "Container uses disallowed field: LivenessProbe", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - LivenessProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, - }, - SuccessThreshold: 1, - }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}}, + }, { + "Container uses disallowed field: LivenessProbe", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + LivenessProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, + }, + SuccessThreshold: 1, + }, + }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].livenessProbe"}}, + }, { + "Container uses disallowed field: Ports", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + Ports: []core.ContainerPort{ + {Protocol: "TCP", ContainerPort: 80}, + }, + }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].ports"}}, + }, { + "Container uses disallowed field: ReadinessProbe", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + ReadinessProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, }, }, }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].livenessProbe"}}, - }, - { - "Container uses disallowed field: Ports", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - Ports: []core.ContainerPort{ - {Protocol: "TCP", ContainerPort: 80}, - }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].readinessProbe"}}, + }, { + "Container uses disallowed field: StartupProbe", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + StartupProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, + }, + SuccessThreshold: 1, + }, + }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].startupProbe"}}, + }, { + "Container uses disallowed field: Resources", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + Resources: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), }, }, }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].ports"}}, - }, - { - "Container uses disallowed field: ReadinessProbe", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - ReadinessProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, - }, - }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resources"}}, + }, { + "Container uses disallowed field: VolumeMount.SubPath", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + VolumeMounts: []core.VolumeMount{ + {Name: "vol", MountPath: "/vol"}, + {Name: "vol", MountPath: "/volsub", SubPath: "foo"}, + }, + }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPath"}}, + }, { + "Container uses disallowed field: VolumeMount.SubPathExpr", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + VolumeMounts: []core.VolumeMount{ + {Name: "vol", MountPath: "/vol"}, + {Name: "vol", MountPath: "/volsub", SubPathExpr: "$(POD_NAME)"}, + }, + }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPathExpr"}}, + }, { + "Disallowed field with other errors should only return a single Forbidden", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + Lifecycle: &core.Lifecycle{ + PreStop: &core.LifecycleHandler{ + Exec: &core.ExecAction{Command: []string{}}, }, }, }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].readinessProbe"}}, - }, - { - "Container uses disallowed field: StartupProbe", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - StartupProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, - }, - SuccessThreshold: 1, - }, - }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}}, + }, { + "Container uses disallowed field: ResizePolicy", + line(), + []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "resources-resize-policy", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + ResizePolicy: []core.ContainerResizePolicy{ + {ResourceName: "cpu", RestartPolicy: "NotRequired"}, }, }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].startupProbe"}}, - }, - { - "Container uses disallowed field: Resources", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - Resources: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - }, - }, - }, - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resources"}}, - }, - { - "Container uses disallowed field: VolumeMount.SubPath", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - VolumeMounts: []core.VolumeMount{ - {Name: "vol", MountPath: "/vol"}, - {Name: "vol", MountPath: "/volsub", SubPath: "foo"}, - }, - }, - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPath"}}, - }, - { - "Container uses disallowed field: VolumeMount.SubPathExpr", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - VolumeMounts: []core.VolumeMount{ - {Name: "vol", MountPath: "/vol"}, - {Name: "vol", MountPath: "/volsub", SubPathExpr: "$(POD_NAME)"}, - }, - }, - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPathExpr"}}, - }, - { - "Disallowed field with other errors should only return a single Forbidden", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - Lifecycle: &core.Lifecycle{ - PreStop: &core.LifecycleHandler{ - Exec: &core.ExecAction{Command: []string{}}, - }, - }, - }, - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}}, - }, - { - "Container uses disallowed field: ResizePolicy", - line(), - []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "resources-resize-policy", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - ResizePolicy: []core.ContainerResizePolicy{ - {ResourceName: "cpu", RestartPolicy: "NotRequired"}, - }, - }, - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resizePolicy"}}, - }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resizePolicy"}}, + }, } for _, tc := range tcs { @@ -7702,8 +7285,7 @@ func TestValidateContainers(t *testing.T) { {Name: "def", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, {Name: "ghi", Image: " some ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, {Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - {Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - { + {Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, { Name: "life-123", Image: "image", Lifecycle: &core.Lifecycle{ @@ -7713,8 +7295,7 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "resources-test", Image: "image", Resources: core.ResourceRequirements{ @@ -7726,8 +7307,7 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "resources-test-with-request-and-limit", Image: "image", Resources: core.ResourceRequirements{ @@ -7742,8 +7322,7 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "resources-request-limit-simple", Image: "image", Resources: core.ResourceRequirements{ @@ -7756,8 +7335,7 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "resources-request-limit-edge", Image: "image", Resources: core.ResourceRequirements{ @@ -7774,8 +7352,7 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "resources-request-limit-partials", Image: "image", Resources: core.ResourceRequirements{ @@ -7790,8 +7367,7 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "resources-request", Image: "image", Resources: core.ResourceRequirements{ @@ -7802,8 +7378,7 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "resources-resize-policy", Image: "image", ResizePolicy: []core.ContainerResizePolicy{ @@ -7812,8 +7387,7 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "same-host-port-different-protocol", Image: "image", Ports: []core.ContainerPort{ @@ -7822,36 +7396,30 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "fallback-to-logs-termination-message", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "FallbackToLogsOnError", - }, - { + }, { Name: "file-termination-message", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "env-from-source", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - EnvFrom: []core.EnvFromSource{ - { - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{ - Name: "test", - }, + EnvFrom: []core.EnvFromSource{{ + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{ + Name: "test", }, }, - }, + }}, }, - {Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)}, - { + {Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)}, { Name: "live-123", Image: "image", LivenessProbe: &core.Probe{ @@ -7864,8 +7432,7 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "startup-123", Image: "image", StartupProbe: &core.Probe{ @@ -7878,8 +7445,7 @@ func TestValidateContainers(t *testing.T) { }, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - { + }, { Name: "resize-policy-cpu", Image: "image", ImagePullPolicy: "IfNotPresent", @@ -7887,8 +7453,7 @@ func TestValidateContainers(t *testing.T) { ResizePolicy: []core.ContainerResizePolicy{ {ResourceName: "cpu", RestartPolicy: "NotRequired"}, }, - }, - { + }, { Name: "resize-policy-mem", Image: "image", ImagePullPolicy: "IfNotPresent", @@ -7896,8 +7461,7 @@ func TestValidateContainers(t *testing.T) { ResizePolicy: []core.ContainerResizePolicy{ {ResourceName: "memory", RestartPolicy: "RestartContainer"}, }, - }, - { + }, { Name: "resize-policy-cpu-and-mem", Image: "image", ImagePullPolicy: "IfNotPresent", @@ -7919,648 +7483,550 @@ func TestValidateContainers(t *testing.T) { title, line string containers []core.Container expectedErrors field.ErrorList - }{ - { - "zero-length name", - line(), - []core.Container{{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].name"}}, + }{{ + "zero-length name", + line(), + []core.Container{{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].name"}}, + }, { + "zero-length-image", + line(), + []core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}}, + }, { + "name > 63 characters", + line(), + []core.Container{{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}}, + }, { + "name not a DNS label", + line(), + []core.Container{{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}}, + }, { + "name not unique", + line(), + []core.Container{ + {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, }, - { - "zero-length-image", - line(), - []core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}}, + field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].name"}}, + }, { + "zero-length image", + line(), + []core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}}, + }, { + "host port not unique", + line(), + []core.Container{ + {Name: "abc", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}}, + ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + {Name: "def", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}}, + ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, }, - { - "name > 63 characters", - line(), - []core.Container{{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}}, + field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].ports[0].hostPort"}}, + }, { + "invalid env var name", + line(), + []core.Container{ + {Name: "abc", Image: "image", Env: []core.EnvVar{{Name: "ev!1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, }, - { - "name not a DNS label", - line(), - []core.Container{{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].env[0].name"}}, + }, { + "unknown volume name", + line(), + []core.Container{ + {Name: "abc", Image: "image", VolumeMounts: []core.VolumeMount{{Name: "anything", MountPath: "/foo"}}, + ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, }, - { - "name not unique", - line(), - []core.Container{ - {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - }, - field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].name"}}, - }, - { - "zero-length image", - line(), - []core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}}, - }, - { - "host port not unique", - line(), - []core.Container{ - {Name: "abc", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}}, - ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - {Name: "def", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}}, - ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - }, - field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].ports[0].hostPort"}}, - }, - { - "invalid env var name", - line(), - []core.Container{ - {Name: "abc", Image: "image", Env: []core.EnvVar{{Name: "ev!1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].env[0].name"}}, - }, - { - "unknown volume name", - line(), - []core.Container{ - {Name: "abc", Image: "image", VolumeMounts: []core.VolumeMount{{Name: "anything", MountPath: "/foo"}}, - ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - }, - field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "containers[0].volumeMounts[0].name"}}, - }, - { - "invalid lifecycle, no exec command.", - line(), - []core.Container{ - { - Name: "life-123", - Image: "image", - Lifecycle: &core.Lifecycle{ - PreStop: &core.LifecycleHandler{ - Exec: &core.ExecAction{}, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", + field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "containers[0].volumeMounts[0].name"}}, + }, { + "invalid lifecycle, no exec command.", + line(), + []core.Container{{ + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.LifecycleHandler{ + Exec: &core.ExecAction{}, }, }, - field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.exec.command"}}, - }, - { - "invalid lifecycle, no http path.", - line(), - []core.Container{ - { - Name: "life-123", - Image: "image", - Lifecycle: &core.Lifecycle{ - PreStop: &core.LifecycleHandler{ - HTTPGet: &core.HTTPGetAction{ - Port: intstr.FromInt32(80), - Scheme: "HTTP", - }, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.httpGet.path"}}, - }, - { - "invalid lifecycle, no http port.", - line(), - []core.Container{ - { - Name: "life-123", - Image: "image", - Lifecycle: &core.Lifecycle{ - PreStop: &core.LifecycleHandler{ - HTTPGet: &core.HTTPGetAction{ - Path: "/", - Scheme: "HTTP", - }, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.httpGet.port"}}, - }, - { - "invalid lifecycle, no http scheme.", - line(), - []core.Container{ - { - Name: "life-123", - Image: "image", - Lifecycle: &core.Lifecycle{ - PreStop: &core.LifecycleHandler{ - HTTPGet: &core.HTTPGetAction{ - Path: "/", - Port: intstr.FromInt32(80), - }, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].lifecycle.preStop.httpGet.scheme"}}, - }, - { - "invalid lifecycle, no tcp socket port.", - line(), - []core.Container{ - { - Name: "life-123", - Image: "image", - Lifecycle: &core.Lifecycle{ - PreStop: &core.LifecycleHandler{ - TCPSocket: &core.TCPSocketAction{}, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}}, - }, - { - "invalid lifecycle, zero tcp socket port.", - line(), - []core.Container{ - { - Name: "life-123", - Image: "image", - Lifecycle: &core.Lifecycle{ - PreStop: &core.LifecycleHandler{ - TCPSocket: &core.TCPSocketAction{ - Port: intstr.FromInt32(0), - }, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}}, - }, - { - "invalid lifecycle, no action.", - line(), - []core.Container{ - { - Name: "life-123", - Image: "image", - Lifecycle: &core.Lifecycle{ - PreStop: &core.LifecycleHandler{}, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop"}}, - }, - { - "invalid readiness probe, terminationGracePeriodSeconds set.", - line(), - []core.Container{ - { - Name: "life-123", - Image: "image", - ReadinessProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{ - Port: intstr.FromInt32(80), - }, - }, - TerminationGracePeriodSeconds: utilpointer.Int64(10), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}}, - }, - { - "invalid liveness probe, no tcp socket port.", - line(), - []core.Container{ - { - Name: "live-123", - Image: "image", - LivenessProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{}, - }, - SuccessThreshold: 1, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.tcpSocket.port"}}, - }, - { - "invalid liveness probe, no action.", - line(), - []core.Container{ - { - Name: "live-123", - Image: "image", - LivenessProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{}, - SuccessThreshold: 1, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].livenessProbe"}}, - }, - { - "invalid liveness probe, successThreshold != 1", - line(), - []core.Container{ - { - Name: "live-123", - Image: "image", - LivenessProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{ - Port: intstr.FromInt32(80), - }, - }, - SuccessThreshold: 2, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}}, - }, - { - "invalid startup probe, successThreshold != 1", - line(), - []core.Container{ - { - Name: "startup-123", - Image: "image", - StartupProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{ - Port: intstr.FromInt32(80), - }, - }, - SuccessThreshold: 2, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}}, - }, - { - "invalid liveness probe, negative numbers", - line(), - []core.Container{ - { - Name: "live-123", - Image: "image", - LivenessProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{ - Port: intstr.FromInt32(80), - }, - }, - InitialDelaySeconds: -1, - TimeoutSeconds: -1, - PeriodSeconds: -1, - SuccessThreshold: -1, - FailureThreshold: -1, - TerminationGracePeriodSeconds: utilpointer.Int64(-1), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{ - {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.initialDelaySeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.timeoutSeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.periodSeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.failureThreshold"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.terminationGracePeriodSeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}, - }, - }, - { - "invalid readiness probe, negative numbers", - line(), - []core.Container{ - { - Name: "ready-123", - Image: "image", - ReadinessProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{ - Port: intstr.FromInt32(80), - }, - }, - InitialDelaySeconds: -1, - TimeoutSeconds: -1, - PeriodSeconds: -1, - SuccessThreshold: -1, - FailureThreshold: -1, - TerminationGracePeriodSeconds: utilpointer.Int64(-1), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{ - {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.initialDelaySeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.timeoutSeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.periodSeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.successThreshold"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.failureThreshold"}, - // terminationGracePeriodSeconds returns multiple validation errors here: - // containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must be greater than 0 - {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}, - // containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must not be set for readinessProbes - {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}, - }, - }, - { - "invalid startup probe, negative numbers", - line(), - []core.Container{ - { - Name: "startup-123", - Image: "image", - StartupProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{ - Port: intstr.FromInt32(80), - }, - }, - InitialDelaySeconds: -1, - TimeoutSeconds: -1, - PeriodSeconds: -1, - SuccessThreshold: -1, - FailureThreshold: -1, - TerminationGracePeriodSeconds: utilpointer.Int64(-1), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{ - {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.initialDelaySeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.timeoutSeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.periodSeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.failureThreshold"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.terminationGracePeriodSeconds"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}, - }, - }, - { - "invalid message termination policy", - line(), - []core.Container{ - { - Name: "life-123", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "Unknown", - }, - }, - field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].terminationMessagePolicy"}}, - }, - { - "empty message termination policy", - line(), - []core.Container{ - { - Name: "life-123", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "", - }, - }, - field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].terminationMessagePolicy"}}, - }, - { - "privilege disabled", - line(), - []core.Container{ - { - Name: "abc", - Image: "image", - SecurityContext: fakeValidSecurityContext(true), - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].securityContext.privileged"}}, - }, - { - "invalid compute resource", - line(), - []core.Container{ - { - Name: "abc-123", - Image: "image", - Resources: core.ResourceRequirements{ - Limits: core.ResourceList{ - "disk": resource.MustParse("10G"), - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{ - {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"}, - }, - }, - { - "Resource CPU invalid", - line(), - []core.Container{ - { - Name: "abc-123", - Image: "image", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("-10", "0"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[cpu]"}}, - }, - { - "Resource Requests CPU invalid", - line(), - []core.Container{ - { - Name: "abc-123", - Image: "image", - Resources: core.ResourceRequirements{ - Requests: getResourceLimits("-10", "0"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests[cpu]"}}, - }, - { - "Resource Memory invalid", - line(), - []core.Container{ - { - Name: "abc-123", - Image: "image", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("0", "-10"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[memory]"}}, - }, - { - "Request limit simple invalid", - line(), - []core.Container{ - { - Name: "abc-123", - Image: "image", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("5", "3"), - Requests: getResourceLimits("6", "3"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}}, - }, - { - "Invalid storage limit request", - line(), - []core.Container{ - { - Name: "abc-123", - Image: "image", - Resources: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceName("attachable-volumes-aws-ebs"): *resource.NewQuantity(10, resource.DecimalSI), - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{ - {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"}, - {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"}, - }, - }, - { - "CPU request limit multiple invalid", - line(), - []core.Container{ - { - Name: "abc-123", - Image: "image", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("5", "3"), - Requests: getResourceLimits("6", "3"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}}, - }, - { - "Memory request limit multiple invalid", - line(), - []core.Container{ - { - Name: "abc-123", - Image: "image", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("5", "3"), - Requests: getResourceLimits("5", "4"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}}, - }, - { - "Invalid env from", - line(), - []core.Container{ - { - Name: "env-from-source", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - EnvFrom: []core.EnvFromSource{ - { - ConfigMapRef: &core.ConfigMapEnvSource{ - LocalObjectReference: core.LocalObjectReference{ - Name: "$%^&*#", - }, - }, - }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.exec.command"}}, + }, { + "invalid lifecycle, no http path.", + line(), + []core.Container{{ + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.LifecycleHandler{ + HTTPGet: &core.HTTPGetAction{ + Port: intstr.FromInt32(80), + Scheme: "HTTP", }, }, }, - field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].envFrom[0].configMapRef.name"}}, - }, - { - "Unsupported resize policy for memory", - line(), - []core.Container{ - { - Name: "resize-policy-mem-invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - ResizePolicy: []core.ContainerResizePolicy{ - {ResourceName: "memory", RestartPolicy: "RestartContainerrrr"}, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.httpGet.path"}}, + }, { + "invalid lifecycle, no http port.", + line(), + []core.Container{{ + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.LifecycleHandler{ + HTTPGet: &core.HTTPGetAction{ + Path: "/", + Scheme: "HTTP", }, }, }, - field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}}, - }, - { - "Unsupported resize policy for CPU", - line(), - []core.Container{ - { - Name: "resize-policy-cpu-invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - ResizePolicy: []core.ContainerResizePolicy{ - {ResourceName: "cpu", RestartPolicy: "RestartNotRequired"}, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.httpGet.port"}}, + }, { + "invalid lifecycle, no http scheme.", + line(), + []core.Container{{ + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.LifecycleHandler{ + HTTPGet: &core.HTTPGetAction{ + Path: "/", + Port: intstr.FromInt32(80), }, }, }, - field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}}, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].lifecycle.preStop.httpGet.scheme"}}, + }, { + "invalid lifecycle, no tcp socket port.", + line(), + []core.Container{{ + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.LifecycleHandler{ + TCPSocket: &core.TCPSocketAction{}, + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}}, + }, { + "invalid lifecycle, zero tcp socket port.", + line(), + []core.Container{{ + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.LifecycleHandler{ + TCPSocket: &core.TCPSocketAction{ + Port: intstr.FromInt32(0), + }, + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}}, + }, { + "invalid lifecycle, no action.", + line(), + []core.Container{{ + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.LifecycleHandler{}, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop"}}, + }, { + "invalid readiness probe, terminationGracePeriodSeconds set.", + line(), + []core.Container{{ + Name: "life-123", + Image: "image", + ReadinessProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{ + Port: intstr.FromInt32(80), + }, + }, + TerminationGracePeriodSeconds: utilpointer.Int64(10), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}}, + }, { + "invalid liveness probe, no tcp socket port.", + line(), + []core.Container{{ + Name: "live-123", + Image: "image", + LivenessProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{}, + }, + SuccessThreshold: 1, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.tcpSocket.port"}}, + }, { + "invalid liveness probe, no action.", + line(), + []core.Container{{ + Name: "live-123", + Image: "image", + LivenessProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{}, + SuccessThreshold: 1, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].livenessProbe"}}, + }, { + "invalid liveness probe, successThreshold != 1", + line(), + []core.Container{{ + Name: "live-123", + Image: "image", + LivenessProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{ + Port: intstr.FromInt32(80), + }, + }, + SuccessThreshold: 2, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}}, + }, { + "invalid startup probe, successThreshold != 1", + line(), + []core.Container{{ + Name: "startup-123", + Image: "image", + StartupProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{ + Port: intstr.FromInt32(80), + }, + }, + SuccessThreshold: 2, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}}, + }, { + "invalid liveness probe, negative numbers", + line(), + []core.Container{{ + Name: "live-123", + Image: "image", + LivenessProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{ + Port: intstr.FromInt32(80), + }, + }, + InitialDelaySeconds: -1, + TimeoutSeconds: -1, + PeriodSeconds: -1, + SuccessThreshold: -1, + FailureThreshold: -1, + TerminationGracePeriodSeconds: utilpointer.Int64(-1), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{ + {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.initialDelaySeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.timeoutSeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.periodSeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.failureThreshold"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.terminationGracePeriodSeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}, }, + }, { + "invalid readiness probe, negative numbers", + line(), + []core.Container{{ + Name: "ready-123", + Image: "image", + ReadinessProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{ + Port: intstr.FromInt32(80), + }, + }, + InitialDelaySeconds: -1, + TimeoutSeconds: -1, + PeriodSeconds: -1, + SuccessThreshold: -1, + FailureThreshold: -1, + TerminationGracePeriodSeconds: utilpointer.Int64(-1), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{ + {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.initialDelaySeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.timeoutSeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.periodSeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.successThreshold"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.failureThreshold"}, + // terminationGracePeriodSeconds returns multiple validation errors here: + // containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must be greater than 0 + {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}, + // containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must not be set for readinessProbes + {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}, + }, + }, { + "invalid startup probe, negative numbers", + line(), + []core.Container{{ + Name: "startup-123", + Image: "image", + StartupProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{ + Port: intstr.FromInt32(80), + }, + }, + InitialDelaySeconds: -1, + TimeoutSeconds: -1, + PeriodSeconds: -1, + SuccessThreshold: -1, + FailureThreshold: -1, + TerminationGracePeriodSeconds: utilpointer.Int64(-1), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{ + {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.initialDelaySeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.timeoutSeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.periodSeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.failureThreshold"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.terminationGracePeriodSeconds"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}, + }, + }, { + "invalid message termination policy", + line(), + []core.Container{{ + Name: "life-123", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "Unknown", + }}, + field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].terminationMessagePolicy"}}, + }, { + "empty message termination policy", + line(), + []core.Container{{ + Name: "life-123", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "", + }}, + field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].terminationMessagePolicy"}}, + }, { + "privilege disabled", + line(), + []core.Container{{ + Name: "abc", + Image: "image", + SecurityContext: fakeValidSecurityContext(true), + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].securityContext.privileged"}}, + }, { + "invalid compute resource", + line(), + []core.Container{{ + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: core.ResourceList{ + "disk": resource.MustParse("10G"), + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{ + {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"}, + }, + }, { + "Resource CPU invalid", + line(), + []core.Container{{ + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("-10", "0"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[cpu]"}}, + }, { + "Resource Requests CPU invalid", + line(), + []core.Container{{ + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Requests: getResourceLimits("-10", "0"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests[cpu]"}}, + }, { + "Resource Memory invalid", + line(), + []core.Container{{ + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("0", "-10"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[memory]"}}, + }, { + "Request limit simple invalid", + line(), + []core.Container{{ + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("5", "3"), + Requests: getResourceLimits("6", "3"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}}, + }, { + "Invalid storage limit request", + line(), + []core.Container{{ + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName("attachable-volumes-aws-ebs"): *resource.NewQuantity(10, resource.DecimalSI), + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{ + {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"}, + {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"}, + }, + }, { + "CPU request limit multiple invalid", + line(), + []core.Container{{ + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("5", "3"), + Requests: getResourceLimits("6", "3"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}}, + }, { + "Memory request limit multiple invalid", + line(), + []core.Container{{ + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("5", "3"), + Requests: getResourceLimits("5", "4"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}}, + }, { + "Invalid env from", + line(), + []core.Container{{ + Name: "env-from-source", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + EnvFrom: []core.EnvFromSource{{ + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{ + Name: "$%^&*#", + }, + }, + }}, + }}, + field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].envFrom[0].configMapRef.name"}}, + }, { + "Unsupported resize policy for memory", + line(), + []core.Container{{ + Name: "resize-policy-mem-invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + ResizePolicy: []core.ContainerResizePolicy{ + {ResourceName: "memory", RestartPolicy: "RestartContainerrrr"}, + }, + }}, + field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}}, + }, { + "Unsupported resize policy for CPU", + line(), + []core.Container{{ + Name: "resize-policy-cpu-invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + ResizePolicy: []core.ContainerResizePolicy{ + {ResourceName: "cpu", RestartPolicy: "RestartNotRequired"}, + }, + }}, + field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}}, + }, } for _, tc := range errorCases { t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) { @@ -8583,36 +8049,33 @@ func TestValidateInitContainers(t *testing.T) { AllowPrivileged: true, }) - containers := []core.Container{ - { - Name: "app", - Image: "nginx", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, + containers := []core.Container{{ + Name: "app", + Image: "nginx", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, } - successCase := []core.Container{ - { - Name: "container-1-same-host-port-different-protocol", - Image: "image", - Ports: []core.ContainerPort{ - {ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, - {ContainerPort: 80, HostPort: 80, Protocol: "UDP"}, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", + successCase := []core.Container{{ + Name: "container-1-same-host-port-different-protocol", + Image: "image", + Ports: []core.ContainerPort{ + {ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, + {ContainerPort: 80, HostPort: 80, Protocol: "UDP"}, }, - { - Name: "container-2-same-host-port-different-protocol", - Image: "image", - Ports: []core.ContainerPort{ - {ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, - {ContainerPort: 80, HostPort: 80, Protocol: "UDP"}, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, { + Name: "container-2-same-host-port-different-protocol", + Image: "image", + Ports: []core.ContainerPort{ + {ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, + {ContainerPort: 80, HostPort: 80, Protocol: "UDP"}, }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, } if errs := validateInitContainers(successCase, containers, volumeDevices, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 { t.Errorf("expected success: %v", errs) @@ -8625,184 +8088,150 @@ func TestValidateInitContainers(t *testing.T) { title, line string initContainers []core.Container expectedErrors field.ErrorList - }{ - { - "empty name", - line(), - []core.Container{ - { - Name: "", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", + }{{ + "empty name", + line(), + []core.Container{{ + Name: "", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].name", BadValue: ""}}, + }, { + "name collision with regular container", + line(), + []core.Container{{ + Name: "app", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].name", BadValue: "app"}}, + }, { + "invalid termination message policy", + line(), + []core.Container{{ + Name: "init", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "Unknown", + }}, + field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].terminationMessagePolicy", BadValue: core.TerminationMessagePolicy("Unknown")}}, + }, { + "duplicate names", + line(), + []core.Container{{ + Name: "init", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, { + Name: "init", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[1].name", BadValue: "init"}}, + }, { + "duplicate ports", + line(), + []core.Container{{ + Name: "abc", + Image: "image", + Ports: []core.ContainerPort{{ + ContainerPort: 8080, HostPort: 8080, Protocol: "TCP", + }, { + ContainerPort: 8080, HostPort: 8080, Protocol: "TCP", + }}, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].ports[1].hostPort", BadValue: "TCP//8080"}}, + }, { + "uses disallowed field: Lifecycle", + line(), + []core.Container{{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + Lifecycle: &core.Lifecycle{ + PreStop: &core.LifecycleHandler{ + Exec: &core.ExecAction{Command: []string{"ls", "-l"}}, }, }, - field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].name", BadValue: ""}}, - }, - { - "name collision with regular container", - line(), - []core.Container{ - { - Name: "app", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].lifecycle", BadValue: ""}}, + }, { + "uses disallowed field: LivenessProbe", + line(), + []core.Container{{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + LivenessProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, + }, + SuccessThreshold: 1, + }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].livenessProbe", BadValue: ""}}, + }, { + "uses disallowed field: ReadinessProbe", + line(), + []core.Container{{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + ReadinessProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, }, }, - field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].name", BadValue: "app"}}, - }, - { - "invalid termination message policy", - line(), - []core.Container{ - { - Name: "init", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "Unknown", + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].readinessProbe", BadValue: ""}}, + }, { + "Container uses disallowed field: StartupProbe", + line(), + []core.Container{{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + StartupProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, }, + SuccessThreshold: 1, }, - field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].terminationMessagePolicy", BadValue: core.TerminationMessagePolicy("Unknown")}}, - }, - { - "duplicate names", - line(), - []core.Container{ - { - Name: "init", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "init", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}}, + }, { + "Disallowed field with other errors should only return a single Forbidden", + line(), + []core.Container{{ + Name: "debug", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + StartupProbe: &core.Probe{ + ProbeHandler: core.ProbeHandler{ + TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, }, + InitialDelaySeconds: -1, + TimeoutSeconds: -1, + PeriodSeconds: -1, + SuccessThreshold: -1, + FailureThreshold: -1, + TerminationGracePeriodSeconds: utilpointer.Int64(-1), }, - field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[1].name", BadValue: "init"}}, - }, - { - "duplicate ports", - line(), - []core.Container{ - { - Name: "abc", - Image: "image", - Ports: []core.ContainerPort{ - { - ContainerPort: 8080, HostPort: 8080, Protocol: "TCP", - }, - { - ContainerPort: 8080, HostPort: 8080, Protocol: "TCP", - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].ports[1].hostPort", BadValue: "TCP//8080"}}, - }, - { - "uses disallowed field: Lifecycle", - line(), - []core.Container{ - { - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - Lifecycle: &core.Lifecycle{ - PreStop: &core.LifecycleHandler{ - Exec: &core.ExecAction{Command: []string{"ls", "-l"}}, - }, - }, - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].lifecycle", BadValue: ""}}, - }, - { - "uses disallowed field: LivenessProbe", - line(), - []core.Container{ - { - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - LivenessProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, - }, - SuccessThreshold: 1, - }, - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].livenessProbe", BadValue: ""}}, - }, - { - "uses disallowed field: ReadinessProbe", - line(), - []core.Container{ - { - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - ReadinessProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, - }, - }, - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].readinessProbe", BadValue: ""}}, - }, - { - "Container uses disallowed field: StartupProbe", - line(), - []core.Container{ - { - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - StartupProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, - }, - SuccessThreshold: 1, - }, - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}}, - }, - { - "Disallowed field with other errors should only return a single Forbidden", - line(), - []core.Container{ - { - Name: "debug", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - StartupProbe: &core.Probe{ - ProbeHandler: core.ProbeHandler{ - TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, - }, - InitialDelaySeconds: -1, - TimeoutSeconds: -1, - PeriodSeconds: -1, - SuccessThreshold: -1, - FailureThreshold: -1, - TerminationGracePeriodSeconds: utilpointer.Int64(-1), - }, - }, - }, - field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}}, - }, + }}, + field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}}, + }, } for _, tc := range errorCases { t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) { @@ -8874,249 +8303,229 @@ func TestValidatePodDNSConfig(t *testing.T) { dnsPolicy *core.DNSPolicy opts PodValidationOptions expectedError bool - }{ - { - desc: "valid: empty DNSConfig", - dnsConfig: &core.PodDNSConfig{}, - expectedError: false, + }{{ + desc: "valid: empty DNSConfig", + dnsConfig: &core.PodDNSConfig{}, + expectedError: false, + }, { + desc: "valid: 1 option", + dnsConfig: &core.PodDNSConfig{ + Options: []core.PodDNSConfigOption{ + {Name: "ndots", Value: &testOptionValue}, + }, }, - { - desc: "valid: 1 option", - dnsConfig: &core.PodDNSConfig{ - Options: []core.PodDNSConfigOption{ - {Name: "ndots", Value: &testOptionValue}, - }, - }, - expectedError: false, + expectedError: false, + }, { + desc: "valid: 1 nameserver", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"127.0.0.1"}, }, - { - desc: "valid: 1 nameserver", - dnsConfig: &core.PodDNSConfig{ - Nameservers: []string{"127.0.0.1"}, - }, - expectedError: false, + expectedError: false, + }, { + desc: "valid: DNSNone with 1 nameserver", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"127.0.0.1"}, }, - { - desc: "valid: DNSNone with 1 nameserver", - dnsConfig: &core.PodDNSConfig{ - Nameservers: []string{"127.0.0.1"}, - }, - dnsPolicy: &testDNSNone, - expectedError: false, + dnsPolicy: &testDNSNone, + expectedError: false, + }, { + desc: "valid: 1 search path", + dnsConfig: &core.PodDNSConfig{ + Searches: []string{"custom"}, }, - { - desc: "valid: 1 search path", - dnsConfig: &core.PodDNSConfig{ - Searches: []string{"custom"}, - }, - expectedError: false, + expectedError: false, + }, { + desc: "valid: 1 search path with trailing period", + dnsConfig: &core.PodDNSConfig{ + Searches: []string{"custom."}, }, - { - desc: "valid: 1 search path with trailing period", - dnsConfig: &core.PodDNSConfig{ - Searches: []string{"custom."}, - }, - expectedError: false, + expectedError: false, + }, { + desc: "valid: 3 nameservers and 6 search paths(legacy)", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"}, + Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local."}, }, - { - desc: "valid: 3 nameservers and 6 search paths(legacy)", - dnsConfig: &core.PodDNSConfig{ - Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"}, - Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local."}, - }, - expectedError: false, + expectedError: false, + }, { + desc: "valid: 3 nameservers and 32 search paths", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"}, + Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local.", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"}, }, - { - desc: "valid: 3 nameservers and 32 search paths", - dnsConfig: &core.PodDNSConfig{ - Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"}, - Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local.", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"}, - }, - opts: PodValidationOptions{ - AllowExpandedDNSConfig: true, - }, - expectedError: false, + opts: PodValidationOptions{ + AllowExpandedDNSConfig: true, }, - { - desc: "valid: 256 characters in search path list(legacy)", - dnsConfig: &core.PodDNSConfig{ - // We can have 256 - (6 - 1) = 251 characters in total for 6 search paths. - Searches: []string{ - generateTestSearchPathFunc(1), - generateTestSearchPathFunc(50), - generateTestSearchPathFunc(50), - generateTestSearchPathFunc(50), - generateTestSearchPathFunc(50), - generateTestSearchPathFunc(50), - }, + expectedError: false, + }, { + desc: "valid: 256 characters in search path list(legacy)", + dnsConfig: &core.PodDNSConfig{ + // We can have 256 - (6 - 1) = 251 characters in total for 6 search paths. + Searches: []string{ + generateTestSearchPathFunc(1), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), }, - expectedError: false, }, - { - desc: "valid: 2048 characters in search path list", - dnsConfig: &core.PodDNSConfig{ - // We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths. - Searches: []string{ - generateTestSearchPathFunc(64), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - }, + expectedError: false, + }, { + desc: "valid: 2048 characters in search path list", + dnsConfig: &core.PodDNSConfig{ + // We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths. + Searches: []string{ + generateTestSearchPathFunc(64), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), }, - opts: PodValidationOptions{ - AllowExpandedDNSConfig: true, - }, - expectedError: false, }, - { - desc: "valid: ipv6 nameserver", - dnsConfig: &core.PodDNSConfig{ - Nameservers: []string{"FE80::0202:B3FF:FE1E:8329"}, - }, - expectedError: false, + opts: PodValidationOptions{ + AllowExpandedDNSConfig: true, }, - { - desc: "invalid: 4 nameservers", - dnsConfig: &core.PodDNSConfig{ - Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"}, - }, - expectedError: true, + expectedError: false, + }, { + desc: "valid: ipv6 nameserver", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"FE80::0202:B3FF:FE1E:8329"}, }, - { - desc: "invalid: 7 search paths(legacy)", - dnsConfig: &core.PodDNSConfig{ - Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local", "exceeded"}, - }, - expectedError: true, + expectedError: false, + }, { + desc: "invalid: 4 nameservers", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"}, }, - { - desc: "invalid: 33 search paths", - dnsConfig: &core.PodDNSConfig{ - Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local.", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33"}, - }, - opts: PodValidationOptions{ - AllowExpandedDNSConfig: true, - }, - expectedError: true, + expectedError: true, + }, { + desc: "invalid: 7 search paths(legacy)", + dnsConfig: &core.PodDNSConfig{ + Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local", "exceeded"}, }, - { - desc: "invalid: 257 characters in search path list", - dnsConfig: &core.PodDNSConfig{ - // We can have 256 - (6 - 1) = 251 characters in total for 6 search paths. - Searches: []string{ - generateTestSearchPathFunc(2), - generateTestSearchPathFunc(50), - generateTestSearchPathFunc(50), - generateTestSearchPathFunc(50), - generateTestSearchPathFunc(50), - generateTestSearchPathFunc(50), - }, - }, - expectedError: true, + expectedError: true, + }, { + desc: "invalid: 33 search paths", + dnsConfig: &core.PodDNSConfig{ + Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local.", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33"}, }, - { - desc: "invalid: 2049 characters in search path list", - dnsConfig: &core.PodDNSConfig{ - // We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths. - Searches: []string{ - generateTestSearchPathFunc(65), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - generateTestSearchPathFunc(63), - }, - }, - opts: PodValidationOptions{ - AllowExpandedDNSConfig: true, - }, - expectedError: true, + opts: PodValidationOptions{ + AllowExpandedDNSConfig: true, }, - { - desc: "invalid search path", - dnsConfig: &core.PodDNSConfig{ - Searches: []string{"custom?"}, + expectedError: true, + }, { + desc: "invalid: 257 characters in search path list", + dnsConfig: &core.PodDNSConfig{ + // We can have 256 - (6 - 1) = 251 characters in total for 6 search paths. + Searches: []string{ + generateTestSearchPathFunc(2), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), }, - expectedError: true, }, - { - desc: "invalid nameserver", - dnsConfig: &core.PodDNSConfig{ - Nameservers: []string{"invalid"}, + expectedError: true, + }, { + desc: "invalid: 2049 characters in search path list", + dnsConfig: &core.PodDNSConfig{ + // We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths. + Searches: []string{ + generateTestSearchPathFunc(65), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), + generateTestSearchPathFunc(63), }, - expectedError: true, }, - { - desc: "invalid empty option name", - dnsConfig: &core.PodDNSConfig{ - Options: []core.PodDNSConfigOption{ - {Value: &testOptionValue}, - }, + opts: PodValidationOptions{ + AllowExpandedDNSConfig: true, + }, + expectedError: true, + }, { + desc: "invalid search path", + dnsConfig: &core.PodDNSConfig{ + Searches: []string{"custom?"}, + }, + expectedError: true, + }, { + desc: "invalid nameserver", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"invalid"}, + }, + expectedError: true, + }, { + desc: "invalid empty option name", + dnsConfig: &core.PodDNSConfig{ + Options: []core.PodDNSConfigOption{ + {Value: &testOptionValue}, }, - expectedError: true, }, - { - desc: "invalid: DNSNone with 0 nameserver", - dnsConfig: &core.PodDNSConfig{ - Searches: []string{"custom"}, - }, - dnsPolicy: &testDNSNone, - expectedError: true, + expectedError: true, + }, { + desc: "invalid: DNSNone with 0 nameserver", + dnsConfig: &core.PodDNSConfig{ + Searches: []string{"custom"}, }, + dnsPolicy: &testDNSNone, + expectedError: true, + }, } for _, tc := range testCases { @@ -9137,30 +8546,22 @@ func TestValidatePodReadinessGates(t *testing.T) { successCases := []struct { desc string readinessGates []core.PodReadinessGate - }{ - { - "no gate", - []core.PodReadinessGate{}, - }, - { - "one readiness gate", - []core.PodReadinessGate{ - { - ConditionType: core.PodConditionType("example.com/condition"), - }, - }, - }, - { - "two readiness gates", - []core.PodReadinessGate{ - { - ConditionType: core.PodConditionType("example.com/condition1"), - }, - { - ConditionType: core.PodConditionType("example.com/condition2"), - }, - }, - }, + }{{ + "no gate", + []core.PodReadinessGate{}, + }, { + "one readiness gate", + []core.PodReadinessGate{{ + ConditionType: core.PodConditionType("example.com/condition"), + }}, + }, { + "two readiness gates", + []core.PodReadinessGate{{ + ConditionType: core.PodConditionType("example.com/condition1"), + }, { + ConditionType: core.PodConditionType("example.com/condition2"), + }}, + }, } for _, tc := range successCases { if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) != 0 { @@ -9171,15 +8572,12 @@ func TestValidatePodReadinessGates(t *testing.T) { errorCases := []struct { desc string readinessGates []core.PodReadinessGate - }{ - { - "invalid condition type", - []core.PodReadinessGate{ - { - ConditionType: core.PodConditionType("invalid/condition/type"), - }, - }, - }, + }{{ + "invalid condition type", + []core.PodReadinessGate{{ + ConditionType: core.PodConditionType("invalid/condition/type"), + }}, + }, } for _, tc := range errorCases { if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) == 0 { @@ -9192,46 +8590,34 @@ func TestValidatePodConditions(t *testing.T) { successCases := []struct { desc string podConditions []core.PodCondition - }{ - { - "no condition", - []core.PodCondition{}, - }, - { - "one system condition", - []core.PodCondition{ - { - Type: core.PodReady, - Status: core.ConditionTrue, - }, - }, - }, - { - "one system condition and one custom condition", - []core.PodCondition{ - { - Type: core.PodReady, - Status: core.ConditionTrue, - }, - { - Type: core.PodConditionType("example.com/condition"), - Status: core.ConditionFalse, - }, - }, - }, - { - "two custom condition", - []core.PodCondition{ - { - Type: core.PodConditionType("foobar"), - Status: core.ConditionTrue, - }, - { - Type: core.PodConditionType("example.com/condition"), - Status: core.ConditionFalse, - }, - }, - }, + }{{ + "no condition", + []core.PodCondition{}, + }, { + "one system condition", + []core.PodCondition{{ + Type: core.PodReady, + Status: core.ConditionTrue, + }}, + }, { + "one system condition and one custom condition", + []core.PodCondition{{ + Type: core.PodReady, + Status: core.ConditionTrue, + }, { + Type: core.PodConditionType("example.com/condition"), + Status: core.ConditionFalse, + }}, + }, { + "two custom condition", + []core.PodCondition{{ + Type: core.PodConditionType("foobar"), + Status: core.ConditionTrue, + }, { + Type: core.PodConditionType("example.com/condition"), + Status: core.ConditionFalse, + }}, + }, } for _, tc := range successCases { @@ -9243,20 +8629,16 @@ func TestValidatePodConditions(t *testing.T) { errorCases := []struct { desc string podConditions []core.PodCondition - }{ - { - "one system condition and a invalid custom condition", - []core.PodCondition{ - { - Type: core.PodReady, - Status: core.ConditionStatus("True"), - }, - { - Type: core.PodConditionType("invalid/custom/condition"), - Status: core.ConditionStatus("True"), - }, - }, - }, + }{{ + "one system condition and a invalid custom condition", + []core.PodCondition{{ + Type: core.PodReady, + Status: core.ConditionStatus("True"), + }, { + Type: core.PodConditionType("invalid/custom/condition"), + Status: core.ConditionStatus("True"), + }}, + }, } for _, tc := range errorCases { if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) == 0 { @@ -9637,28 +9019,24 @@ func TestValidatePodSpec(t *testing.T) { DNSPolicy: core.DNSClusterFirst, }, "disallowed resources resize policy for init containers": { - InitContainers: []core.Container{ - { - Name: "initctr", - Image: "initimage", - ResizePolicy: []core.ContainerResizePolicy{ - {ResourceName: "cpu", RestartPolicy: "NotRequired"}, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", + InitContainers: []core.Container{{ + Name: "initctr", + Image: "initimage", + ResizePolicy: []core.ContainerResizePolicy{ + {ResourceName: "cpu", RestartPolicy: "NotRequired"}, }, - }, - Containers: []core.Container{ - { - Name: "ctr", - Image: "image", - ResizePolicy: []core.ContainerResizePolicy{ - {ResourceName: "cpu", RestartPolicy: "NotRequired"}, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, + Containers: []core.Container{{ + Name: "ctr", + Image: "image", + ResizePolicy: []core.ContainerResizePolicy{ + {ResourceName: "cpu", RestartPolicy: "NotRequired"}, }, - }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -9755,39 +9133,29 @@ func TestValidatePod(t *testing.T) { &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "key2", - Operator: core.NodeSelectorOpIn, - Values: []string{"value1", "value2"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"host1"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "key2", + Operator: core.NodeSelectorOpIn, + Values: []string{"value1", "value2"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"host1"}, + }}, + }}, }, - PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ - { - Weight: 10, - Preference: core.NodeSelectorTerm{ - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "foo", - Operator: core.NodeSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ + Weight: 10, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "foo", + Operator: core.NodeSelectorOpIn, + Values: []string{"bar"}, + }}, }, - }, + }}, }, }, ), @@ -9815,20 +9183,16 @@ func TestValidatePod(t *testing.T) { &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{}, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{}, + }}, }, - PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ - { - Weight: 10, - Preference: core.NodeSelectorTerm{ - MatchExpressions: []core.NodeSelectorRequirement{}, - }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ + Weight: 10, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{}, }, - }, + }}, }, }, ), @@ -9853,48 +9217,38 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAffinity: &core.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ - { + RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value1", "value2"}, + }}, + }, + TopologyKey: "zone", + Namespaces: []string{"ns"}, + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value1", "value2"}, + }}, + }, + }}, + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"value1", "value2"}, - }, - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value1", "value2"}, + }}, }, - TopologyKey: "zone", Namespaces: []string{"ns"}, - NamespaceSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"value1", "value2"}, - }, - }, - }, + TopologyKey: "region", }, - }, - PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: core.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - TopologyKey: "region", - }, - }, - }, + }}, }, }), }, @@ -9918,37 +9272,29 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAntiAffinity: &core.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ - { + RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpExists, + }}, + }, + TopologyKey: "zone", + Namespaces: []string{"ns"}, + }}, + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpExists, - }, - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpDoesNotExist, + }}, }, - TopologyKey: "zone", Namespaces: []string{"ns"}, + TopologyKey: "region", }, - }, - PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: core.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpDoesNotExist, - }, - }, - }, - Namespaces: []string{"ns"}, - TopologyKey: "region", - }, - }, - }, + }}, }, }), }, @@ -10204,42 +9550,36 @@ func TestValidatePod(t *testing.T) { RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, SecurityContext: &core.PodSecurityContext{ - Sysctls: []core.Sysctl{ - { - Name: "kernel.shmmni", - Value: "32768", - }, - { - Name: "kernel.shmmax", - Value: "1000000000", - }, - { - Name: "knet.ipv4.route.min_pmtu", - Value: "1000", - }, - }, + Sysctls: []core.Sysctl{{ + Name: "kernel.shmmni", + Value: "32768", + }, { + Name: "kernel.shmmax", + Value: "1000000000", + }, { + Name: "knet.ipv4.route.min_pmtu", + Value: "1000", + }}, }, }, }, "valid extended resources for init container": { ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, Spec: core.PodSpec{ - InitContainers: []core.Container{ - { - Name: "valid-extended", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("10"), - }, - Limits: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("10"), - }, + InitContainers: []core.Container{{ + Name: "valid-extended", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("10"), + }, + Limits: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("10"), }, - TerminationMessagePolicy: "File", }, - }, + TerminationMessagePolicy: "File", + }}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, @@ -10249,22 +9589,20 @@ func TestValidatePod(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, Spec: core.PodSpec{ InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - Containers: []core.Container{ - { - Name: "valid-extended", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("10"), - }, - Limits: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("10"), - }, + Containers: []core.Container{{ + Name: "valid-extended", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("10"), + }, + Limits: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("10"), }, - TerminationMessagePolicy: "File", }, - }, + TerminationMessagePolicy: "File", + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -10276,24 +9614,20 @@ func TestValidatePod(t *testing.T) { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - Volumes: []core.Volume{ - { - Name: "projected-volume", - VolumeSource: core.VolumeSource{ - Projected: &core.ProjectedVolumeSource{ - Sources: []core.VolumeProjection{ - { - ServiceAccountToken: &core.ServiceAccountTokenProjection{ - Audience: "foo-audience", - ExpirationSeconds: 6000, - Path: "foo-path", - }, - }, + Volumes: []core.Volume{{ + Name: "projected-volume", + VolumeSource: core.VolumeSource{ + Projected: &core.ProjectedVolumeSource{ + Sources: []core.VolumeProjection{{ + ServiceAccountToken: &core.ServiceAccountTokenProjection{ + Audience: "foo-audience", + ExpirationSeconds: 6000, + Path: "foo-path", }, - }, + }}, }, }, - }, + }}, }, }, "ephemeral volume + PVC, no conflict between them": { @@ -10417,15 +9751,11 @@ func TestValidatePod(t *testing.T) { Spec: validPodSpec(&core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "key1", - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "key1", + }}, + }}, }, }, }), @@ -10441,16 +9771,12 @@ func TestValidatePod(t *testing.T) { Spec: validPodSpec(&core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "invalid key ___@#", - Operator: core.NodeSelectorOpExists, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "invalid key ___@#", + Operator: core.NodeSelectorOpExists, + }}, + }}, }, }, }), @@ -10466,17 +9792,13 @@ func TestValidatePod(t *testing.T) { Spec: validPodSpec(&core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"host1", "host2"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"host1", "host2"}, + }}, + }}, }, }, }), @@ -10492,16 +9814,12 @@ func TestValidatePod(t *testing.T) { Spec: validPodSpec(&core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpExists, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpExists, + }}, + }}, }, }, }), @@ -10517,17 +9835,13 @@ func TestValidatePod(t *testing.T) { Spec: validPodSpec(&core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.namespace", - Operator: core.NodeSelectorOpIn, - Values: []string{"ns1"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.namespace", + Operator: core.NodeSelectorOpIn, + Values: []string{"ns1"}, + }}, + }}, }, }, }), @@ -10542,20 +9856,16 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ NodeAffinity: &core.NodeAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ - { - Weight: 199, - Preference: core.NodeSelectorTerm{ - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "foo", - Operator: core.NodeSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ + Weight: 199, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "foo", + Operator: core.NodeSelectorOpIn, + Values: []string{"bar"}, + }}, }, - }, + }}, }, }), }, @@ -10585,24 +9895,20 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAffinity: &core.PodAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ - { - Weight: 109, - PodAffinityTerm: core.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - TopologyKey: "region", + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ + Weight: 109, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value1", "value2"}, + }}, }, + Namespaces: []string{"ns"}, + TopologyKey: "region", }, - }, + }}, }, }), }, @@ -10616,24 +9922,20 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAntiAffinity: &core.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: core.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpExists, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - TopologyKey: "region", + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpExists, + Values: []string{"value1", "value2"}, + }}, }, + Namespaces: []string{"ns"}, + TopologyKey: "region", }, - }, + }}, }, }), }, @@ -10647,23 +9949,19 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAntiAffinity: &core.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: core.PodAffinityTerm{ - NamespaceSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpIn, - }, - }, - }, - Namespaces: []string{"ns"}, - TopologyKey: "region", + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpIn, + }}, }, + Namespaces: []string{"ns"}, + TopologyKey: "region", }, - }, + }}, }, }), }, @@ -10677,24 +9975,20 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAntiAffinity: &core.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: core.PodAffinityTerm{ - NamespaceSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpExists, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - TopologyKey: "region", + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpExists, + Values: []string{"value1", "value2"}, + }}, }, + Namespaces: []string{"ns"}, + TopologyKey: "region", }, - }, + }}, }, }), }, @@ -10708,23 +10002,19 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAffinity: &core.PodAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: core.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - Namespaces: []string{"INVALID_NAMESPACE"}, - TopologyKey: "region", + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpExists, + }}, }, + Namespaces: []string{"INVALID_NAMESPACE"}, + TopologyKey: "region", }, - }, + }}, }, }), }, @@ -10738,20 +10028,16 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAffinity: &core.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, + RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value1", "value2"}, + }}, }, - }, + Namespaces: []string{"ns"}, + }}, }, }), }, @@ -10765,20 +10051,16 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAntiAffinity: &core.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, + RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value1", "value2"}, + }}, }, - }, + Namespaces: []string{"ns"}, + }}, }, }), }, @@ -10792,23 +10074,19 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAffinity: &core.PodAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: core.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value1", "value2"}, + }}, }, + Namespaces: []string{"ns"}, }, - }, + }}, }, }), }, @@ -10822,23 +10100,19 @@ func TestValidatePod(t *testing.T) { }, Spec: validPodSpec(&core.Affinity{ PodAntiAffinity: &core.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: core.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key2", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value1", "value2"}, + }}, }, + Namespaces: []string{"ns"}, }, - }, + }}, }, }), }, @@ -11067,21 +10341,19 @@ func TestValidatePod(t *testing.T) { spec: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName("invalid-name"): resource.MustParse("2"), - }, - Limits: core.ResourceList{ - core.ResourceName("invalid-name"): resource.MustParse("2"), - }, + Containers: []core.Container{{ + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("invalid-name"): resource.MustParse("2"), + }, + Limits: core.ResourceList{ + core.ResourceName("invalid-name"): resource.MustParse("2"), }, }, - }, + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -11092,21 +10364,19 @@ func TestValidatePod(t *testing.T) { spec: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("2"), - }, - Limits: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("1"), - }, + Containers: []core.Container{{ + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("2"), + }, + Limits: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("1"), }, }, - }, + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -11117,18 +10387,16 @@ func TestValidatePod(t *testing.T) { spec: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("2"), - }, + Containers: []core.Container{{ + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("2"), }, }, - }, + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -11139,18 +10407,16 @@ func TestValidatePod(t *testing.T) { spec: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("500m"), - }, + Containers: []core.Container{{ + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("500m"), }, }, - }, + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -11161,18 +10427,16 @@ func TestValidatePod(t *testing.T) { spec: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, Spec: core.PodSpec{ - InitContainers: []core.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("500m"), - }, + InitContainers: []core.Container{{ + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("500m"), }, }, - }, + }}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, @@ -11184,21 +10448,19 @@ func TestValidatePod(t *testing.T) { spec: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("5"), - }, - Limits: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("2.5"), - }, + Containers: []core.Container{{ + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("5"), + }, + Limits: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("2.5"), }, }, - }, + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -11209,21 +10471,19 @@ func TestValidatePod(t *testing.T) { spec: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, Spec: core.PodSpec{ - InitContainers: []core.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("2.5"), - }, - Limits: core.ResourceList{ - core.ResourceName("example.com/a"): resource.MustParse("2.5"), - }, + InitContainers: []core.Container{{ + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("2.5"), + }, + Limits: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("2.5"), }, }, - }, + }}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, @@ -11260,24 +10520,20 @@ func TestValidatePod(t *testing.T) { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - Volumes: []core.Volume{ - { - Name: "projected-volume", - VolumeSource: core.VolumeSource{ - Projected: &core.ProjectedVolumeSource{ - Sources: []core.VolumeProjection{ - { - ServiceAccountToken: &core.ServiceAccountTokenProjection{ - Audience: "foo-audience", - ExpirationSeconds: 6000, - Path: "foo-path", - }, - }, + Volumes: []core.Volume{{ + Name: "projected-volume", + VolumeSource: core.VolumeSource{ + Projected: &core.ProjectedVolumeSource{ + Sources: []core.VolumeProjection{{ + ServiceAccountToken: &core.ServiceAccountTokenProjection{ + Audience: "foo-audience", + ExpirationSeconds: 6000, + Path: "foo-path", }, - }, + }}, }, }, - }, + }}, }, }, }, @@ -11373,61 +10629,57 @@ func TestValidatePodCreateWithSchedulingGates(t *testing.T) { pod *core.Pod featureEnabled bool wantFieldErrors field.ErrorList - }{ - { - name: "create a Pod with nodeName and schedulingGates, feature disabled", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, - Spec: core.PodSpec{ - NodeName: "node", - SchedulingGates: []core.PodSchedulingGate{ - {Name: "foo"}, - }, + }{{ + name: "create a Pod with nodeName and schedulingGates, feature disabled", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, + Spec: core.PodSpec{ + NodeName: "node", + SchedulingGates: []core.PodSchedulingGate{ + {Name: "foo"}, }, }, - featureEnabled: false, - wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")}, }, - { - name: "create a Pod with nodeName and schedulingGates, feature enabled", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, - Spec: core.PodSpec{ - NodeName: "node", - SchedulingGates: []core.PodSchedulingGate{ - {Name: "foo"}, - }, + featureEnabled: false, + wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")}, + }, { + name: "create a Pod with nodeName and schedulingGates, feature enabled", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, + Spec: core.PodSpec{ + NodeName: "node", + SchedulingGates: []core.PodSchedulingGate{ + {Name: "foo"}, }, }, - featureEnabled: true, - wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")}, }, - { - name: "create a Pod with schedulingGates, feature disabled", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, - Spec: core.PodSpec{ - SchedulingGates: []core.PodSchedulingGate{ - {Name: "foo"}, - }, + featureEnabled: true, + wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")}, + }, { + name: "create a Pod with schedulingGates, feature disabled", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, + Spec: core.PodSpec{ + SchedulingGates: []core.PodSchedulingGate{ + {Name: "foo"}, }, }, - featureEnabled: false, - wantFieldErrors: nil, }, - { - name: "create a Pod with schedulingGates, feature enabled", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, - Spec: core.PodSpec{ - SchedulingGates: []core.PodSchedulingGate{ - {Name: "foo"}, - }, + featureEnabled: false, + wantFieldErrors: nil, + }, { + name: "create a Pod with schedulingGates, feature enabled", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, + Spec: core.PodSpec{ + SchedulingGates: []core.PodSchedulingGate{ + {Name: "foo"}, }, }, - featureEnabled: true, - wantFieldErrors: nil, }, + featureEnabled: true, + wantFieldErrors: nil, + }, } for _, tt := range tests { @@ -11464,8 +10716,7 @@ func TestValidatePodUpdate(t *testing.T) { err string test string }{ - {new: core.Pod{}, old: core.Pod{}, err: "", test: "nothing"}, - { + {new: core.Pod{}, old: core.Pod{}, err: "", test: "nothing"}, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, }, @@ -11474,8 +10725,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "metadata.name", test: "ids", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -11494,8 +10744,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "", test: "labels", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -11514,95 +10763,76 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "", test: "annotations", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Image: "foo:V1", - }, - }, + Containers: []core.Container{{ + Image: "foo:V1", + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Image: "foo:V2", - }, - { - Image: "bar:V2", - }, - }, + Containers: []core.Container{{ + Image: "foo:V2", + }, { + Image: "bar:V2", + }}, }, }, err: "may not add or remove containers", test: "less containers", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Image: "foo:V1", - }, - { - Image: "bar:V2", - }, - }, + Containers: []core.Container{{ + Image: "foo:V1", + }, { + Image: "bar:V2", + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Image: "foo:V2", - }, - }, + Containers: []core.Container{{ + Image: "foo:V2", + }}, }, }, err: "may not add or remove containers", test: "more containers", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, Spec: core.PodSpec{ - InitContainers: []core.Container{ - { - Image: "foo:V1", - }, - }, + InitContainers: []core.Container{{ + Image: "foo:V1", + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - InitContainers: []core.Container{ - { - Image: "foo:V2", - }, - { - Image: "bar:V2", - }, - }, + InitContainers: []core.Container{{ + Image: "foo:V2", + }, { + Image: "bar:V2", + }}, }, }, err: "may not add or remove containers", test: "more init containers", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, @@ -11613,8 +10843,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "metadata.deletionTimestamp", test: "deletion timestamp removed", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now}, Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, @@ -11625,8 +10854,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "metadata.deletionTimestamp", test: "deletion timestamp added", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace}, Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, @@ -11637,137 +10865,114 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "metadata.deletionGracePeriodSeconds", test: "deletion grace period seconds changed", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - Image: "foo:V1", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - }, - }, + Containers: []core.Container{{ + Name: "container", + Image: "foo:V1", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - Image: "foo:V2", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - }, - }, + Containers: []core.Container{{ + Name: "container", + Image: "foo:V2", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + }}, }, }, err: "", test: "image change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - InitContainers: []core.Container{ - { - Name: "container", - Image: "foo:V1", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - }, - }, + InitContainers: []core.Container{{ + Name: "container", + Image: "foo:V1", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - InitContainers: []core.Container{ - { - Name: "container", - Image: "foo:V2", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - }, - }, + InitContainers: []core.Container{{ + Name: "container", + Image: "foo:V2", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + }}, }, }, err: "", test: "init container image change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - }, - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - Image: "foo:V2", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - }, - }, + Containers: []core.Container{{ + Name: "container", + Image: "foo:V2", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + }}, }, }, err: "spec.containers[0].image", test: "image change to empty", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - InitContainers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - }, - }, + InitContainers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - InitContainers: []core.Container{ - { - Name: "container", - Image: "foo:V2", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - }, - }, + InitContainers: []core.Container{{ + Name: "container", + Image: "foo:V2", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + }}, }, }, err: "spec.initContainers[0].image", test: "init container image change to empty", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - EphemeralContainers: []core.EphemeralContainer{ - { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "ephemeral", - Image: "busybox", - }, + EphemeralContainers: []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "ephemeral", + Image: "busybox", }, - }, + }}, }, }, old: core.Pod{ @@ -11776,8 +10981,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "Forbidden: pod updates may not change fields other than", test: "ephemeralContainer changes are not allowed via normal pod update", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{}, }, @@ -11786,8 +10990,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "", test: "activeDeadlineSeconds no change, nil", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{ ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, @@ -11800,8 +11003,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "", test: "activeDeadlineSeconds no change, set", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{ ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, @@ -11810,8 +11012,7 @@ func TestValidatePodUpdate(t *testing.T) { old: core.Pod{}, err: "", test: "activeDeadlineSeconds change to positive from nil", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{ ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, @@ -11824,8 +11025,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "", test: "activeDeadlineSeconds change to smaller positive", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{ ActiveDeadlineSeconds: &activeDeadlineSecondsLarger, @@ -11849,8 +11049,7 @@ func TestValidatePodUpdate(t *testing.T) { old: core.Pod{}, err: "spec.activeDeadlineSeconds", test: "activeDeadlineSeconds change to negative from nil", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{ ActiveDeadlineSeconds: &activeDeadlineSecondsNegative, @@ -11863,8 +11062,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.activeDeadlineSeconds", test: "activeDeadlineSeconds change to negative from positive", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{ ActiveDeadlineSeconds: &activeDeadlineSecondsZero, @@ -11877,8 +11075,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.activeDeadlineSeconds", test: "activeDeadlineSeconds change to zero from positive", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{ ActiveDeadlineSeconds: &activeDeadlineSecondsZero, @@ -11887,8 +11084,7 @@ func TestValidatePodUpdate(t *testing.T) { old: core.Pod{}, err: "spec.activeDeadlineSeconds", test: "activeDeadlineSeconds change to zero from nil", - }, - { + }, { new: core.Pod{}, old: core.Pod{ Spec: core.PodSpec{ @@ -11897,599 +11093,516 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.activeDeadlineSeconds", test: "activeDeadlineSeconds change to nil from positive", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResources("200m", "0", "1Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResources("200m", "0", "1Gi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResources("100m", "0", "1Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResources("100m", "0", "1Gi"), }, - }, + }}, }, }, err: "", test: "cpu limit change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V1", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V1", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("100m", "200Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("100m", "200Mi"), }, - }, + }}, }, }, err: "", test: "memory limit change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V1", - Resources: core.ResourceRequirements{ - Limits: getResources("100m", "100Mi", "1Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V1", + Resources: core.ResourceRequirements{ + Limits: getResources("100m", "100Mi", "1Gi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResources("100m", "100Mi", "2Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResources("100m", "100Mi", "2Gi"), }, - }, + }}, }, }, err: "Forbidden: pod updates may not change fields other than", test: "storage limit change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V1", - Resources: core.ResourceRequirements{ - Requests: getResourceLimits("100m", "0"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V1", + Resources: core.ResourceRequirements{ + Requests: getResourceLimits("100m", "0"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Requests: getResourceLimits("200m", "0"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Requests: getResourceLimits("200m", "0"), }, - }, + }}, }, }, err: "", test: "cpu request change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V1", - Resources: core.ResourceRequirements{ - Requests: getResourceLimits("0", "200Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V1", + Resources: core.ResourceRequirements{ + Requests: getResourceLimits("0", "200Mi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Requests: getResourceLimits("0", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Requests: getResourceLimits("0", "100Mi"), }, - }, + }}, }, }, err: "", test: "memory request change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V1", - Resources: core.ResourceRequirements{ - Requests: getResources("100m", "0", "2Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V1", + Resources: core.ResourceRequirements{ + Requests: getResources("100m", "0", "2Gi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Requests: getResources("100m", "0", "1Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Requests: getResources("100m", "0", "1Gi"), }, - }, + }}, }, }, err: "Forbidden: pod updates may not change fields other than", test: "storage request change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V1", - Resources: core.ResourceRequirements{ - Limits: getResources("200m", "400Mi", "1Gi"), - Requests: getResources("200m", "400Mi", "1Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V1", + Resources: core.ResourceRequirements{ + Limits: getResources("200m", "400Mi", "1Gi"), + Requests: getResources("200m", "400Mi", "1Gi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V1", - Resources: core.ResourceRequirements{ - Limits: getResources("100m", "100Mi", "1Gi"), - Requests: getResources("100m", "100Mi", "1Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V1", + Resources: core.ResourceRequirements{ + Limits: getResources("100m", "100Mi", "1Gi"), + Requests: getResources("100m", "100Mi", "1Gi"), }, - }, + }}, }, }, err: "", test: "Pod QoS unchanged, guaranteed -> guaranteed", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V1", - Resources: core.ResourceRequirements{ - Limits: getResources("200m", "200Mi", "2Gi"), - Requests: getResources("100m", "100Mi", "1Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V1", + Resources: core.ResourceRequirements{ + Limits: getResources("200m", "200Mi", "2Gi"), + Requests: getResources("100m", "100Mi", "1Gi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V1", - Resources: core.ResourceRequirements{ - Limits: getResources("400m", "400Mi", "2Gi"), - Requests: getResources("200m", "200Mi", "1Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V1", + Resources: core.ResourceRequirements{ + Limits: getResources("400m", "400Mi", "2Gi"), + Requests: getResources("200m", "200Mi", "1Gi"), }, - }, + }}, }, }, err: "", test: "Pod QoS unchanged, burstable -> burstable", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("200m", "200Mi"), - Requests: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("200m", "200Mi"), + Requests: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Requests: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Requests: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, err: "", test: "Pod QoS unchanged, burstable -> burstable, add limits", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Requests: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Requests: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("200m", "200Mi"), - Requests: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("200m", "200Mi"), + Requests: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, err: "", test: "Pod QoS unchanged, burstable -> burstable, remove limits", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResources("400m", "", "1Gi"), - Requests: getResources("300m", "", "1Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResources("400m", "", "1Gi"), + Requests: getResources("300m", "", "1Gi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResources("200m", "500Mi", "1Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResources("200m", "500Mi", "1Gi"), }, - }, + }}, }, }, err: "", test: "Pod QoS unchanged, burstable -> burstable, add requests", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResources("400m", "500Mi", "2Gi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResources("400m", "500Mi", "2Gi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResources("200m", "300Mi", "2Gi"), - Requests: getResourceLimits("100m", "200Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResources("200m", "300Mi", "2Gi"), + Requests: getResourceLimits("100m", "200Mi"), }, - }, + }}, }, }, err: "", test: "Pod QoS unchanged, burstable -> burstable, remove requests", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("200m", "200Mi"), - Requests: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("200m", "200Mi"), + Requests: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("100m", "100Mi"), - Requests: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("100m", "100Mi"), + Requests: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, err: "Pod QoS is immutable", test: "Pod QoS change, guaranteed -> burstable", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("100m", "100Mi"), - Requests: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("100m", "100Mi"), + Requests: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Requests: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Requests: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, err: "Pod QoS is immutable", test: "Pod QoS change, burstable -> guaranteed", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("200m", "200Mi"), - Requests: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("200m", "200Mi"), + Requests: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - }, - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + }}, }, }, err: "Pod QoS is immutable", test: "Pod QoS change, besteffort -> burstable", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - }, - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "container", - TerminationMessagePolicy: "File", - ImagePullPolicy: "Always", - Image: "foo:V2", - Resources: core.ResourceRequirements{ - Limits: getResourceLimits("200m", "200Mi"), - Requests: getResourceLimits("100m", "100Mi"), - }, + Containers: []core.Container{{ + Name: "container", + TerminationMessagePolicy: "File", + ImagePullPolicy: "Always", + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("200m", "200Mi"), + Requests: getResourceLimits("100m", "100Mi"), }, - }, + }}, }, }, err: "Pod QoS is immutable", test: "Pod QoS change, burstable -> besteffort", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Image: "foo:V1", - }, - }, + Containers: []core.Container{{ + Image: "foo:V1", + }}, SecurityContext: &core.PodSecurityContext{ FSGroupChangePolicy: &validfsGroupChangePolicy, }, @@ -12498,11 +11611,9 @@ func TestValidatePodUpdate(t *testing.T) { old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Image: "foo:V2", - }, - }, + Containers: []core.Container{{ + Image: "foo:V2", + }}, SecurityContext: &core.PodSecurityContext{ FSGroupChangePolicy: nil, }, @@ -12510,38 +11621,32 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec: Forbidden: pod updates may not change fields", test: "fsGroupChangePolicy change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Image: "foo:V1", - Ports: []core.ContainerPort{ - {HostPort: 8080, ContainerPort: 80}, - }, + Containers: []core.Container{{ + Image: "foo:V1", + Ports: []core.ContainerPort{ + {HostPort: 8080, ContainerPort: 80}, }, - }, + }}, }, }, old: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Image: "foo:V2", - Ports: []core.ContainerPort{ - {HostPort: 8000, ContainerPort: 80}, - }, + Containers: []core.Container{{ + Image: "foo:V2", + Ports: []core.ContainerPort{ + {HostPort: 8000, ContainerPort: 80}, }, - }, + }}, }, }, err: "spec: Forbidden: pod updates may not change fields", test: "port change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12560,8 +11665,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "", test: "bad label change", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12582,8 +11686,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.tolerations: Forbidden", test: "existing toleration value modified in pod spec updates", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12604,8 +11707,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.tolerations: Forbidden", test: "existing toleration value modified in pod spec updates with modified tolerationSeconds", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12625,8 +11727,7 @@ func TestValidatePodUpdate(t *testing.T) { }}, err: "", test: "modified tolerationSeconds in existing toleration value in pod spec updates", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12646,8 +11747,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.tolerations: Forbidden", test: "toleration modified in updates to an unscheduled pod", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12668,8 +11768,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "", test: "tolerations unmodified in updates to a scheduled pod", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12692,8 +11791,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "", test: "added valid new toleration to existing tolerations in pod spec updates", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ NodeName: "node1", @@ -12711,32 +11809,27 @@ func TestValidatePodUpdate(t *testing.T) { }}, err: "spec.tolerations[1].effect", test: "added invalid new toleration to existing tolerations in pod spec updates", - }, - { + }, { new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}}, old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, err: "spec: Forbidden: pod updates may not change fields", test: "removed nodeName from pod spec", - }, - { + }, { new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}}, old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}}, err: "metadata.annotations[kubernetes.io/config.mirror]", test: "added mirror pod annotation", - }, - { + }, { new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}}, old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}}, err: "metadata.annotations[kubernetes.io/config.mirror]", test: "removed mirror pod annotation", - }, - { + }, { new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}}, Spec: core.PodSpec{NodeName: "foo"}}, old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "bar"}}, Spec: core.PodSpec{NodeName: "foo"}}, err: "metadata.annotations[kubernetes.io/config.mirror]", test: "changed mirror pod annotation", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12757,8 +11850,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec: Forbidden: pod updates", test: "changed priority class name", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12779,8 +11871,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec: Forbidden: pod updates", test: "removed priority class name", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12799,8 +11890,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "", test: "update termination grace period seconds", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12819,8 +11909,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec: Forbidden: pod updates", test: "update termination grace period seconds not 1", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12841,8 +11930,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12863,8 +11951,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.securityContext.seLinuxOptions: Forbidden", test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set, we'd get SELinux errors as well", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12881,8 +11968,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", test: "invalid PodOS update, IdentifyPodOS featuregate set", - }, - { + }, { new: core.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -12901,8 +11987,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "Forbidden: pod updates may not change fields other than ", test: "update pod spec OS to a valid value, featuregate disabled", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{ SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}}, @@ -12911,8 +11996,7 @@ func TestValidatePodUpdate(t *testing.T) { old: core.Pod{}, err: "Forbidden: only deletion is allowed, but found new scheduling gate 'foo'", test: "update pod spec schedulingGates: add new scheduling gate", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{ SchedulingGates: []core.PodSchedulingGate{{Name: "bar"}}, @@ -12925,8 +12009,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "Forbidden: only deletion is allowed, but found new scheduling gate 'bar'", test: "update pod spec schedulingGates: mutating an existing scheduling gate", - }, - { + }, { new: core.Pod{ Spec: core.PodSpec{ SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, @@ -12939,8 +12022,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "Forbidden: only deletion is allowed, but found new scheduling gate 'baz'", test: "update pod spec schedulingGates: mutating an existing scheduling gate along with deletion", - }, - { + }, { new: core.Pod{}, old: core.Pod{ Spec: core.PodSpec{ @@ -12949,8 +12031,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "", test: "update pod spec schedulingGates: legal deletion", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, @@ -12966,8 +12047,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", test: "node selector is immutable when AllowMutableNodeSelector is false", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, @@ -12985,8 +12065,7 @@ func TestValidatePodUpdate(t *testing.T) { AllowMutableNodeSelectorAndNodeAffinity: true, }, test: "adding node selector is allowed for gated pods", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ NodeSelector: map[string]string{ @@ -13007,8 +12086,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", test: "adding node selector is not allowed for non-gated pods", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ NodeSelector: map[string]string{ @@ -13027,8 +12105,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.nodeSelector: Invalid value:", test: "removing node selector is not allowed for gated pods", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ NodeSelector: map[string]string{ @@ -13042,8 +12119,7 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", test: "removing node selector is not allowed for non-gated pods", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ NodeSelector: map[string]string{ @@ -13064,8 +12140,7 @@ func TestValidatePodUpdate(t *testing.T) { AllowMutableNodeSelectorAndNodeAffinity: true, }, test: "old pod spec has scheduling gate, new pod spec does not, and node selector is added", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ NodeSelector: map[string]string{ @@ -13087,24 +12162,19 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.nodeSelector: Invalid value:", test: "modifying value of existing node selector is not allowed", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13117,29 +12187,22 @@ func TestValidatePodUpdate(t *testing.T) { NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ // Add 1 MatchExpression and 1 MatchField. - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - { - Key: "expr2", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo2"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }, { + Key: "expr2", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo2"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13150,24 +12213,19 @@ func TestValidatePodUpdate(t *testing.T) { AllowMutableNodeSelectorAndNodeAffinity: true, }, test: "addition to nodeAffinity is allowed for gated pods", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13187,24 +12245,19 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:", test: "old RequiredDuringSchedulingIgnoredDuringExecution is non-nil, new RequiredDuringSchedulingIgnoredDuringExecution is nil, pod is gated", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13216,29 +12269,22 @@ func TestValidatePodUpdate(t *testing.T) { NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ // Add 1 MatchExpression and 1 MatchField. - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - { - Key: "expr2", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo2"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }, { + Key: "expr2", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo2"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13249,24 +12295,19 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", test: "addition to nodeAffinity is not allowed for non-gated pods", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13279,29 +12320,22 @@ func TestValidatePodUpdate(t *testing.T) { NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ // Add 1 MatchExpression and 1 MatchField. - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - { - Key: "expr2", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo2"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }, { + Key: "expr2", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo2"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13311,24 +12345,19 @@ func TestValidatePodUpdate(t *testing.T) { AllowMutableNodeSelectorAndNodeAffinity: true, }, test: "old pod spec has scheduling gate, new pod spec does not, and node affinity addition occurs", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13340,17 +12369,13 @@ func TestValidatePodUpdate(t *testing.T) { Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13362,31 +12387,24 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:", test: "nodeAffinity deletion from MatchExpressions not allowed", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13399,17 +12417,13 @@ func TestValidatePodUpdate(t *testing.T) { NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ // Add 1 MatchExpression and 1 MatchField. - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13421,31 +12435,24 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:", test: "nodeAffinity deletion from MatchFields not allowed", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13458,24 +12465,18 @@ func TestValidatePodUpdate(t *testing.T) { NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ // Add 1 MatchExpression and 1 MatchField. - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"bar"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"bar"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13487,31 +12488,24 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:", test: "nodeAffinity modification of item in MatchExpressions not allowed", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13523,24 +12517,18 @@ func TestValidatePodUpdate(t *testing.T) { Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"bar"}, + }}, + }}, }, }, }, @@ -13552,31 +12540,24 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:", test: "nodeAffinity modification of item in MatchFields not allowed", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13588,40 +12569,29 @@ func TestValidatePodUpdate(t *testing.T) { Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo2"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"bar2"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"bar"}, + }}, + }, { + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo2"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"bar2"}, + }}, + }}, }, }, }, @@ -13633,26 +12603,21 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:", test: "nodeSelectorTerms addition on gated pod should fail", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ - { - Weight: 1.0, - Preference: core.NodeSelectorTerm{ - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ + Weight: 1.0, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, }, - }, + }}, }, }, SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, @@ -13662,20 +12627,16 @@ func TestValidatePodUpdate(t *testing.T) { Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ - { - Weight: 1.0, - Preference: core.NodeSelectorTerm{ - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo2"}, - }, - }, - }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ + Weight: 1.0, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo2"}, + }}, }, - }, + }}, }, }, SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, @@ -13685,26 +12646,21 @@ func TestValidatePodUpdate(t *testing.T) { AllowMutableNodeSelectorAndNodeAffinity: true, }, test: "preferredDuringSchedulingIgnoredDuringExecution can modified for gated pods", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ - { - Weight: 1.0, - Preference: core.NodeSelectorTerm{ - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ + Weight: 1.0, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, }, - }, + }}, }, }, SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, @@ -13714,32 +12670,25 @@ func TestValidatePodUpdate(t *testing.T) { Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ - { - Weight: 1.0, - Preference: core.NodeSelectorTerm{ - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - { - Key: "expr2", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo2"}, - }, - }, - MatchFields: []core.NodeSelectorRequirement{ - { - Key: "metadata.name", - Operator: core.NodeSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ + Weight: 1.0, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }, { + Key: "expr2", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo2"}, + }}, + MatchFields: []core.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: core.NodeSelectorOpIn, + Values: []string{"bar"}, + }}, }, - }, + }}, }, }, SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, @@ -13749,26 +12698,21 @@ func TestValidatePodUpdate(t *testing.T) { AllowMutableNodeSelectorAndNodeAffinity: true, }, test: "preferredDuringSchedulingIgnoredDuringExecution can have additions for gated pods", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ - { - Weight: 1.0, - Preference: core.NodeSelectorTerm{ - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ + Weight: 1.0, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, }, - }, + }}, }, }, SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, @@ -13783,24 +12727,19 @@ func TestValidatePodUpdate(t *testing.T) { AllowMutableNodeSelectorAndNodeAffinity: true, }, test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13818,26 +12757,21 @@ func TestValidatePodUpdate(t *testing.T) { }, err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:", test: "new node affinity is nil", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ - { - Weight: 1.0, - Preference: core.NodeSelectorTerm{ - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ + Weight: 1.0, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, }, - }, + }}, }, }, SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, @@ -13852,8 +12786,7 @@ func TestValidatePodUpdate(t *testing.T) { AllowMutableNodeSelectorAndNodeAffinity: true, }, test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods", - }, - { + }, { old: core.Pod{ Spec: core.PodSpec{ Affinity: &core.Affinity{ @@ -13873,17 +12806,13 @@ func TestValidatePodUpdate(t *testing.T) { Affinity: &core.Affinity{ NodeAffinity: &core.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ - NodeSelectorTerms: []core.NodeSelectorTerm{ - { - MatchExpressions: []core.NodeSelectorRequirement{ - { - Key: "expr", - Operator: core.NodeSelectorOpIn, - Values: []string{"foo"}, - }, - }, - }, - }, + NodeSelectorTerms: []core.NodeSelectorTerm{{ + MatchExpressions: []core.NodeSelectorRequirement{{ + Key: "expr", + Operator: core.NodeSelectorOpIn, + Values: []string{"foo"}, + }}, + }}, }, }, }, @@ -13944,503 +12873,493 @@ func TestValidatePodStatusUpdate(t *testing.T) { old core.Pod err string test string - }{ - { - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: core.PodSpec{ - NodeName: "node1", - }, - Status: core.PodStatus{ - NominatedNodeName: "node1", - }, + }{{ + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: core.PodSpec{ - NodeName: "node1", - }, - Status: core.PodStatus{}, + Spec: core.PodSpec{ + NodeName: "node1", + }, + Status: core.PodStatus{ + NominatedNodeName: "node1", }, - "", - "removed nominatedNodeName", }, - { - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: core.PodSpec{ - NodeName: "node1", - }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: core.PodSpec{ - NodeName: "node1", - }, - Status: core.PodStatus{ - NominatedNodeName: "node1", - }, + Spec: core.PodSpec{ + NodeName: "node1", }, - "", - "add valid nominatedNodeName", + Status: core.PodStatus{}, }, - { - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: core.PodSpec{ - NodeName: "node1", - }, - Status: core.PodStatus{ - NominatedNodeName: "Node1", - }, + "", + "removed nominatedNodeName", + }, { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: core.PodSpec{ - NodeName: "node1", - }, + Spec: core.PodSpec{ + NodeName: "node1", }, - "nominatedNodeName", - "Add invalid nominatedNodeName", }, - { - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: core.PodSpec{ - NodeName: "node1", - }, - Status: core.PodStatus{ - NominatedNodeName: "node1", - }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: core.PodSpec{ - NodeName: "node1", - }, - Status: core.PodStatus{ - NominatedNodeName: "node2", - }, + Spec: core.PodSpec{ + NodeName: "node1", + }, + Status: core.PodStatus{ + NominatedNodeName: "node1", }, - "", - "Update nominatedNodeName", }, - { - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - InitContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "alpine", - Name: "init", - Ready: false, - Started: proto.Bool(false), - State: core.ContainerState{ - Waiting: &core.ContainerStateWaiting{ - Reason: "PodInitializing", - }, - }, - }}, - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - Name: "main", - Ready: false, - Started: proto.Bool(false), - State: core.ContainerState{ - Waiting: &core.ContainerStateWaiting{ - Reason: "PodInitializing", - }, - }, - }}, - }, + "", + "add valid nominatedNodeName", + }, { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, + Spec: core.PodSpec{ + NodeName: "node1", + }, + Status: core.PodStatus{ + NominatedNodeName: "Node1", }, - "", - "Container statuses pending", }, - { - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - InitContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "init", - Ready: true, - State: core.ContainerState{ - Terminated: &core.ContainerStateTerminated{ - ContainerID: "docker://numbers", - Reason: "Completed", - }, - }, - }}, - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "nginx", - Ready: true, - Started: proto.Bool(true), - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - InitContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "alpine", - Name: "init", - Ready: false, - State: core.ContainerState{ - Waiting: &core.ContainerStateWaiting{ - Reason: "PodInitializing", - }, - }, - }}, - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - Name: "main", - Ready: false, - Started: proto.Bool(false), - State: core.ContainerState{ - Waiting: &core.ContainerStateWaiting{ - Reason: "PodInitializing", - }, - }, - }}, - }, + Spec: core.PodSpec{ + NodeName: "node1", }, - "", - "Container statuses running", }, - { - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "nginx", - Ready: true, - Started: proto.Bool(true), - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - EphemeralContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "busybox", - Name: "debug", - Ready: false, - State: core.ContainerState{ - Waiting: &core.ContainerStateWaiting{ - Reason: "PodInitializing", - }, - }, - }}, - }, + "nominatedNodeName", + "Add invalid nominatedNodeName", + }, { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "nginx", - Ready: true, - Started: proto.Bool(true), - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - }, + Spec: core.PodSpec{ + NodeName: "node1", + }, + Status: core.PodStatus{ + NominatedNodeName: "node1", }, - "", - "Container statuses add ephemeral container", }, - { - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "nginx", - Ready: true, - Started: proto.Bool(true), - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - EphemeralContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "busybox", - ImageID: "docker-pullable://busybox@sha256:d0gf00d", - Name: "debug", - Ready: false, - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "nginx", - Ready: true, - Started: proto.Bool(true), - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - EphemeralContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "busybox", - Name: "debug", - Ready: false, - State: core.ContainerState{ - Waiting: &core.ContainerStateWaiting{ - Reason: "PodInitializing", - }, - }, - }}, - }, + Spec: core.PodSpec{ + NodeName: "node1", + }, + Status: core.PodStatus{ + NominatedNodeName: "node2", }, - "", - "Container statuses ephemeral container running", }, - { - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "nginx", - Ready: true, - Started: proto.Bool(true), - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - EphemeralContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "busybox", - ImageID: "docker-pullable://busybox@sha256:d0gf00d", - Name: "debug", - Ready: false, - State: core.ContainerState{ - Terminated: &core.ContainerStateTerminated{ - ContainerID: "docker://numbers", - Reason: "Completed", - StartedAt: metav1.NewTime(time.Now()), - FinishedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - }, + "", + "Update nominatedNodeName", + }, { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "nginx", - Ready: true, - Started: proto.Bool(true), - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, + Status: core.PodStatus{ + InitContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "alpine", + Name: "init", + Ready: false, + Started: proto.Bool(false), + State: core.ContainerState{ + Waiting: &core.ContainerStateWaiting{ + Reason: "PodInitializing", }, - }}, - EphemeralContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "busybox", - ImageID: "docker-pullable://busybox@sha256:d0gf00d", - Name: "debug", - Ready: false, - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, + }, + }}, + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + Name: "main", + Ready: false, + Started: proto.Bool(false), + State: core.ContainerState{ + Waiting: &core.ContainerStateWaiting{ + Reason: "PodInitializing", }, - }}, - }, + }, + }}, }, - "", - "Container statuses ephemeral container exited", }, - { - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - InitContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "init", - Ready: true, - State: core.ContainerState{ - Terminated: &core.ContainerStateTerminated{ - ContainerID: "docker://numbers", - Reason: "Completed", - }, - }, - }}, - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "nginx", - Ready: true, - Started: proto.Bool(true), - State: core.ContainerState{ - Terminated: &core.ContainerStateTerminated{ - ContainerID: "docker://numbers", - Reason: "Completed", - StartedAt: metav1.NewTime(time.Now()), - FinishedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - EphemeralContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "busybox", - ImageID: "docker-pullable://busybox@sha256:d0gf00d", - Name: "debug", - Ready: false, - State: core.ContainerState{ - Terminated: &core.ContainerStateTerminated{ - ContainerID: "docker://numbers", - Reason: "Completed", - StartedAt: metav1.NewTime(time.Now()), - FinishedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", }, - core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: core.PodStatus{ - InitContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "init", - Ready: true, - State: core.ContainerState{ - Terminated: &core.ContainerStateTerminated{ - ContainerID: "docker://numbers", - Reason: "Completed", - }, - }, - }}, - ContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "nginx:alpine", - ImageID: "docker-pullable://nginx@sha256:d0gf00d", - Name: "nginx", - Ready: true, - Started: proto.Bool(true), - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - EphemeralContainerStatuses: []core.ContainerStatus{{ - ContainerID: "docker://numbers", - Image: "busybox", - ImageID: "docker-pullable://busybox@sha256:d0gf00d", - Name: "debug", - Ready: false, - State: core.ContainerState{ - Running: &core.ContainerStateRunning{ - StartedAt: metav1.NewTime(time.Now()), - }, - }, - }}, - }, - }, - "", - "Container statuses all containers terminated", }, + "", + "Container statuses pending", + }, { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.PodStatus{ + InitContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "init", + Ready: true, + State: core.ContainerState{ + Terminated: &core.ContainerStateTerminated{ + ContainerID: "docker://numbers", + Reason: "Completed", + }, + }, + }}, + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "nginx", + Ready: true, + Started: proto.Bool(true), + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.PodStatus{ + InitContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "alpine", + Name: "init", + Ready: false, + State: core.ContainerState{ + Waiting: &core.ContainerStateWaiting{ + Reason: "PodInitializing", + }, + }, + }}, + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + Name: "main", + Ready: false, + Started: proto.Bool(false), + State: core.ContainerState{ + Waiting: &core.ContainerStateWaiting{ + Reason: "PodInitializing", + }, + }, + }}, + }, + }, + "", + "Container statuses running", + }, { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.PodStatus{ + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "nginx", + Ready: true, + Started: proto.Bool(true), + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + EphemeralContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "busybox", + Name: "debug", + Ready: false, + State: core.ContainerState{ + Waiting: &core.ContainerStateWaiting{ + Reason: "PodInitializing", + }, + }, + }}, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.PodStatus{ + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "nginx", + Ready: true, + Started: proto.Bool(true), + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + }, + }, + "", + "Container statuses add ephemeral container", + }, { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.PodStatus{ + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "nginx", + Ready: true, + Started: proto.Bool(true), + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + EphemeralContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "busybox", + ImageID: "docker-pullable://busybox@sha256:d0gf00d", + Name: "debug", + Ready: false, + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.PodStatus{ + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "nginx", + Ready: true, + Started: proto.Bool(true), + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + EphemeralContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "busybox", + Name: "debug", + Ready: false, + State: core.ContainerState{ + Waiting: &core.ContainerStateWaiting{ + Reason: "PodInitializing", + }, + }, + }}, + }, + }, + "", + "Container statuses ephemeral container running", + }, { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.PodStatus{ + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "nginx", + Ready: true, + Started: proto.Bool(true), + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + EphemeralContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "busybox", + ImageID: "docker-pullable://busybox@sha256:d0gf00d", + Name: "debug", + Ready: false, + State: core.ContainerState{ + Terminated: &core.ContainerStateTerminated{ + ContainerID: "docker://numbers", + Reason: "Completed", + StartedAt: metav1.NewTime(time.Now()), + FinishedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.PodStatus{ + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "nginx", + Ready: true, + Started: proto.Bool(true), + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + EphemeralContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "busybox", + ImageID: "docker-pullable://busybox@sha256:d0gf00d", + Name: "debug", + Ready: false, + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + }, + }, + "", + "Container statuses ephemeral container exited", + }, { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.PodStatus{ + InitContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "init", + Ready: true, + State: core.ContainerState{ + Terminated: &core.ContainerStateTerminated{ + ContainerID: "docker://numbers", + Reason: "Completed", + }, + }, + }}, + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "nginx", + Ready: true, + Started: proto.Bool(true), + State: core.ContainerState{ + Terminated: &core.ContainerStateTerminated{ + ContainerID: "docker://numbers", + Reason: "Completed", + StartedAt: metav1.NewTime(time.Now()), + FinishedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + EphemeralContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "busybox", + ImageID: "docker-pullable://busybox@sha256:d0gf00d", + Name: "debug", + Ready: false, + State: core.ContainerState{ + Terminated: &core.ContainerStateTerminated{ + ContainerID: "docker://numbers", + Reason: "Completed", + StartedAt: metav1.NewTime(time.Now()), + FinishedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.PodStatus{ + InitContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "init", + Ready: true, + State: core.ContainerState{ + Terminated: &core.ContainerStateTerminated{ + ContainerID: "docker://numbers", + Reason: "Completed", + }, + }, + }}, + ContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "nginx:alpine", + ImageID: "docker-pullable://nginx@sha256:d0gf00d", + Name: "nginx", + Ready: true, + Started: proto.Bool(true), + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + EphemeralContainerStatuses: []core.ContainerStatus{{ + ContainerID: "docker://numbers", + Image: "busybox", + ImageID: "docker-pullable://busybox@sha256:d0gf00d", + Name: "debug", + Ready: false, + State: core.ContainerState{ + Running: &core.ContainerStateRunning{ + StartedAt: metav1.NewTime(time.Now()), + }, + }, + }}, + }, + }, + "", + "Container statuses all containers terminated", + }, } for _, test := range tests { @@ -14548,313 +13467,300 @@ func TestValidatePodEphemeralContainersUpdate(t *testing.T) { name string new, old *core.Pod err string - }{ - { - "no ephemeral containers", - makePod([]core.EphemeralContainer{}), - makePod([]core.EphemeralContainer{}), - "", - }, - { - "No change in Ephemeral Containers", - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", + }{{ + "no ephemeral containers", + makePod([]core.EphemeralContainer{}), + makePod([]core.EphemeralContainer{}), + "", + }, { + "No change in Ephemeral Containers", + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger2", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger2", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + "", + }, { + "Ephemeral Container list order changes", + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger2", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger2", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + "", + }, { + "Add an Ephemeral Container", + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + makePod([]core.EphemeralContainer{}), + "", + }, { + "Add two Ephemeral Containers", + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger1", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger2", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + makePod([]core.EphemeralContainer{}), + "", + }, { + "Add to an existing Ephemeral Containers", + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger2", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + "", + }, { + "Add to an existing Ephemeral Containers, list order changes", + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger3", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger2", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger2", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + "", + }, { + "Remove an Ephemeral Container", + makePod([]core.EphemeralContainer{}), + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + "may not be removed", + }, { + "Replace an Ephemeral Container", + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "firstone", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "thentheother", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + "may not be removed", + }, { + "Change an Ephemeral Containers", + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger1", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger2", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + makePod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger1", + Image: "debian", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, { + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger2", + Image: "busybox", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }}), + "may not be changed", + }, { + "Ephemeral container with potential conflict with regular containers, but conflict not present", + makeWindowsHostPod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger1", + Image: "image", + ImagePullPolicy: "IfNotPresent", + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: proto.Bool(true), + }, }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger2", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", + TerminationMessagePolicy: "File", + }, + }}), + makeWindowsHostPod(nil), + "", + }, { + "Ephemeral container with potential conflict with regular containers, and conflict is present", + makeWindowsHostPod([]core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + Name: "debugger1", + Image: "image", + ImagePullPolicy: "IfNotPresent", + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: proto.Bool(false), + }, }, - }}), - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger2", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - "", - }, - { - "Ephemeral Container list order changes", - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger2", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger2", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - "", - }, - { - "Add an Ephemeral Container", - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - makePod([]core.EphemeralContainer{}), - "", - }, - { - "Add two Ephemeral Containers", - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger1", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger2", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - makePod([]core.EphemeralContainer{}), - "", - }, - { - "Add to an existing Ephemeral Containers", - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger2", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - "", - }, - { - "Add to an existing Ephemeral Containers, list order changes", - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger3", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger2", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger2", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - "", - }, - { - "Remove an Ephemeral Container", - makePod([]core.EphemeralContainer{}), - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - "may not be removed", - }, - { - "Replace an Ephemeral Container", - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "firstone", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "thentheother", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - "may not be removed", - }, - { - "Change an Ephemeral Containers", - makePod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger1", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger2", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - makePod([]core.EphemeralContainer{{ + TerminationMessagePolicy: "File", + }, + }}), + makeWindowsHostPod(nil), + "spec.ephemeralContainers[0].securityContext.windowsOptions.hostProcess: Invalid value: false: pod hostProcess value must be identical", + }, { + "Add ephemeral container to static pod", + func() *core.Pod { + p := makePod(nil) + p.Spec.NodeName = "some-name" + p.ObjectMeta.Annotations = map[string]string{ + core.MirrorPodAnnotationKey: "foo", + } + p.Spec.EphemeralContainers = []core.EphemeralContainer{{ EphemeralContainerCommon: core.EphemeralContainerCommon{ Name: "debugger1", Image: "debian", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", }, - }, { - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger2", - Image: "busybox", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }}), - "may not be changed", - }, - { - "Ephemeral container with potential conflict with regular containers, but conflict not present", - makeWindowsHostPod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger1", - Image: "image", - ImagePullPolicy: "IfNotPresent", - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: proto.Bool(true), - }, - }, - TerminationMessagePolicy: "File", - }, - }}), - makeWindowsHostPod(nil), - "", - }, - { - "Ephemeral container with potential conflict with regular containers, and conflict is present", - makeWindowsHostPod([]core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger1", - Image: "image", - ImagePullPolicy: "IfNotPresent", - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: proto.Bool(false), - }, - }, - TerminationMessagePolicy: "File", - }, - }}), - makeWindowsHostPod(nil), - "spec.ephemeralContainers[0].securityContext.windowsOptions.hostProcess: Invalid value: false: pod hostProcess value must be identical", - }, - { - "Add ephemeral container to static pod", - func() *core.Pod { - p := makePod(nil) - p.Spec.NodeName = "some-name" - p.ObjectMeta.Annotations = map[string]string{ - core.MirrorPodAnnotationKey: "foo", - } - p.Spec.EphemeralContainers = []core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - Name: "debugger1", - Image: "debian", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }} - return p - }(), - func() *core.Pod { - p := makePod(nil) - p.Spec.NodeName = "some-name" - p.ObjectMeta.Annotations = map[string]string{ - core.MirrorPodAnnotationKey: "foo", - } - return p - }(), - "Forbidden: static pods do not support ephemeral containers", - }, + }} + return p + }(), + func() *core.Pod { + p := makePod(nil) + p.Spec.NodeName = "some-name" + p.ObjectMeta.Annotations = map[string]string{ + core.MirrorPodAnnotationKey: "foo", + } + return p + }(), + "Forbidden: static pods do not support ephemeral containers", + }, } for _, tc := range tests { @@ -14883,669 +13789,591 @@ func TestValidateServiceCreate(t *testing.T) { tweakSvc func(svc *core.Service) // given a basic valid service, each test case can customize it numErrs int featureGates []featuregate.Feature - }{ - { - name: "missing namespace", - tweakSvc: func(s *core.Service) { - s.Namespace = "" - }, - numErrs: 1, - }, - { - name: "invalid namespace", - tweakSvc: func(s *core.Service) { - s.Namespace = "-123" - }, - numErrs: 1, - }, - { - name: "missing name", - tweakSvc: func(s *core.Service) { - s.Name = "" - }, - numErrs: 1, - }, - { - name: "invalid name", - tweakSvc: func(s *core.Service) { - s.Name = "-123" - }, - numErrs: 1, - }, - { - name: "too long name", - tweakSvc: func(s *core.Service) { - s.Name = strings.Repeat("a", 64) - }, - numErrs: 1, - }, - { - name: "invalid generateName", - tweakSvc: func(s *core.Service) { - s.GenerateName = "-123" - }, - numErrs: 1, - }, - { - name: "too long generateName", - tweakSvc: func(s *core.Service) { - s.GenerateName = strings.Repeat("a", 64) - }, - numErrs: 1, - }, - { - name: "invalid label", - tweakSvc: func(s *core.Service) { - s.Labels["NoUppercaseOrSpecialCharsLike=Equals"] = "bar" - }, - numErrs: 1, - }, - { - name: "invalid annotation", - tweakSvc: func(s *core.Service) { - s.Annotations["NoSpecialCharsLike=Equals"] = "bar" - }, - numErrs: 1, - }, - { - name: "nil selector", - tweakSvc: func(s *core.Service) { - s.Spec.Selector = nil - }, - numErrs: 0, - }, - { - name: "invalid selector", - tweakSvc: func(s *core.Service) { - s.Spec.Selector["NoSpecialCharsLike=Equals"] = "bar" - }, - numErrs: 1, - }, - { - name: "missing session affinity", - tweakSvc: func(s *core.Service) { - s.Spec.SessionAffinity = "" - }, - numErrs: 1, - }, - { - name: "missing type", - tweakSvc: func(s *core.Service) { - s.Spec.Type = "" - }, - numErrs: 1, - }, - { - name: "missing ports", - tweakSvc: func(s *core.Service) { - s.Spec.Ports = nil - }, - numErrs: 1, - }, - { - name: "missing ports but headless", - tweakSvc: func(s *core.Service) { - s.Spec.Ports = nil - s.Spec.ClusterIP = core.ClusterIPNone - s.Spec.ClusterIPs = []string{core.ClusterIPNone} - }, - numErrs: 0, - }, - { - name: "empty port[0] name", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Name = "" - }, - numErrs: 0, - }, - { - name: "empty port[1] name", - tweakSvc: func(s *core.Service) { - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 1, - }, - { - name: "empty multi-port port[0] name", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Name = "" - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 1, - }, - { - name: "invalid port name", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Name = "INVALID" - }, - numErrs: 1, - }, - { - name: "missing protocol", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Protocol = "" - }, - numErrs: 1, - }, - { - name: "invalid protocol", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Protocol = "INVALID" - }, - numErrs: 1, - }, - { - name: "invalid cluster ip", - tweakSvc: func(s *core.Service) { - s.Spec.ClusterIP = "invalid" - s.Spec.ClusterIPs = []string{"invalid"} - }, - numErrs: 1, - }, - { - name: "missing port", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Port = 0 - }, - numErrs: 1, - }, - { - name: "invalid port", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Port = 65536 - }, - numErrs: 1, - }, - { - name: "invalid TargetPort int", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].TargetPort = intstr.FromInt32(65536) - }, - numErrs: 1, - }, - { - name: "valid port headless", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Port = 11722 - s.Spec.Ports[0].TargetPort = intstr.FromInt32(11722) - s.Spec.ClusterIP = core.ClusterIPNone - s.Spec.ClusterIPs = []string{core.ClusterIPNone} - }, - numErrs: 0, - }, - { - name: "invalid port headless 1", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Port = 11722 - s.Spec.Ports[0].TargetPort = intstr.FromInt32(11721) - s.Spec.ClusterIP = core.ClusterIPNone - s.Spec.ClusterIPs = []string{core.ClusterIPNone} - }, - // in the v1 API, targetPorts on headless services were tolerated. - // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. - // numErrs: 1, - numErrs: 0, - }, - { - name: "invalid port headless 2", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Port = 11722 - s.Spec.Ports[0].TargetPort = intstr.FromString("target") - s.Spec.ClusterIP = core.ClusterIPNone - s.Spec.ClusterIPs = []string{core.ClusterIPNone} - }, - // in the v1 API, targetPorts on headless services were tolerated. - // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. - // numErrs: 1, - numErrs: 0, - }, - { - name: "invalid publicIPs localhost", - tweakSvc: func(s *core.Service) { - s.Spec.ExternalIPs = []string{"127.0.0.1"} - }, - numErrs: 1, - }, - { - name: "invalid publicIPs unspecified", - tweakSvc: func(s *core.Service) { - s.Spec.ExternalIPs = []string{"0.0.0.0"} - }, - numErrs: 1, - }, - { - name: "invalid publicIPs loopback", - tweakSvc: func(s *core.Service) { - s.Spec.ExternalIPs = []string{"127.0.0.1"} - }, - numErrs: 1, - }, - { - name: "invalid publicIPs host", - tweakSvc: func(s *core.Service) { - s.Spec.ExternalIPs = []string{"myhost.mydomain"} - }, - numErrs: 1, - }, - { - name: "dup port name", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Name = "p" - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 1, - }, - { - name: "valid load balancer protocol UDP 1", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.Ports[0].Protocol = "UDP" - }, - numErrs: 0, - }, - { - name: "valid load balancer protocol UDP 2", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.Ports[0] = core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)} - }, - numErrs: 0, - }, - { - name: "load balancer with mix protocol", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 0, - }, - { - name: "valid 1", - tweakSvc: func(s *core.Service) { - // do nothing - }, - numErrs: 0, - }, - { - name: "valid 2", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].Protocol = "UDP" - s.Spec.Ports[0].TargetPort = intstr.FromInt32(12345) - }, - numErrs: 0, - }, - { - name: "valid 3", - tweakSvc: func(s *core.Service) { - s.Spec.Ports[0].TargetPort = intstr.FromString("http") - }, - numErrs: 0, - }, - { - name: "valid cluster ip - none ", - tweakSvc: func(s *core.Service) { - s.Spec.ClusterIP = core.ClusterIPNone - s.Spec.ClusterIPs = []string{core.ClusterIPNone} - }, - numErrs: 0, - }, - { - name: "valid cluster ip - empty", - tweakSvc: func(s *core.Service) { - s.Spec.ClusterIPs = nil - s.Spec.Ports[0].TargetPort = intstr.FromString("http") - }, - numErrs: 0, - }, - { - name: "valid type - clusterIP", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeClusterIP - }, - numErrs: 0, - }, - { - name: "valid type - loadbalancer", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - }, - numErrs: 0, - }, - { - name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=false", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) - }, - numErrs: 0, - }, - { - name: "invalid type - missing AllocateLoadBalancerNodePorts for loadbalancer type", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - }, - numErrs: 1, - }, - { - name: "valid type loadbalancer 2 ports", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 0, - }, - { - name: "valid external load balancer 2 ports", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 0, - }, - { - name: "duplicate nodeports", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)}) - }, - numErrs: 1, - }, - { - name: "duplicate nodeports (different protocols)", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt32(2)}) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 3, Protocol: "SCTP", NodePort: 1, TargetPort: intstr.FromInt32(3)}) - }, - numErrs: 0, - }, - { - name: "invalid duplicate ports (with same protocol)", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(80)}) - }, - numErrs: 1, - }, - { - name: "valid duplicate ports (with different protocols)", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(80)}) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 12345, Protocol: "SCTP", TargetPort: intstr.FromInt32(8088)}) - }, - numErrs: 0, - }, - { - name: "valid type - cluster", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeClusterIP - }, - numErrs: 0, - }, - { - name: "valid type - nodeport", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - }, - numErrs: 0, - }, - { - name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=true", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - }, - numErrs: 0, - }, - { - name: "valid type loadbalancer 2 ports", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 0, - }, - { - name: "valid type loadbalancer with NodePort", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 0, - }, - { - name: "valid type=NodePort service with NodePort", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 0, - }, - { - name: "valid type=NodePort service without NodePort", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 0, - }, - { - name: "valid cluster service without NodePort", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 0, - }, - { - name: "invalid cluster service with NodePort", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 1, - }, - { - name: "invalid public service with duplicate NodePort", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p1", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p2", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)}) - }, - numErrs: 1, - }, - { - name: "valid type=LoadBalancer", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 0, - }, - { - // For now we open firewalls, and its insecure if we open 10250, remove this - // when we have better protections in place. - name: "invalid port type=LoadBalancer", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) - }, - numErrs: 1, - }, - { - name: "valid LoadBalancer source range annotation", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/8, 5.6.7.8/16" - }, - numErrs: 0, - }, - { - name: "empty LoadBalancer source range annotation", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "" - }, - numErrs: 0, - }, - { - name: "invalid LoadBalancer source range annotation (hostname)", - tweakSvc: func(s *core.Service) { - s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar" - }, - numErrs: 2, - }, - { - name: "invalid LoadBalancer source range annotation (invalid CIDR)", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33" - }, - numErrs: 1, - }, - { - name: "invalid source range for non LoadBalancer type service", - tweakSvc: func(s *core.Service) { - s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"} - }, - numErrs: 1, - }, - { - name: "valid LoadBalancer source range", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"} - }, - numErrs: 0, - }, - { - name: "empty LoadBalancer source range", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.LoadBalancerSourceRanges = []string{" "} - }, - numErrs: 1, - }, - { - name: "invalid LoadBalancer source range", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.LoadBalancerSourceRanges = []string{"foo.bar"} - }, - numErrs: 1, - }, - { - name: "valid ExternalName", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeExternalName - s.Spec.ExternalName = "foo.bar.example.com" - }, - numErrs: 0, - }, - { - name: "valid ExternalName (trailing dot)", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeExternalName - s.Spec.ExternalName = "foo.bar.example.com." - }, - numErrs: 0, - }, - { - name: "invalid ExternalName clusterIP (valid IP)", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeExternalName - s.Spec.ClusterIP = "1.2.3.4" - s.Spec.ClusterIPs = []string{"1.2.3.4"} - s.Spec.ExternalName = "foo.bar.example.com" - }, - numErrs: 1, - }, - { - name: "invalid ExternalName clusterIP (None)", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeExternalName - s.Spec.ClusterIP = "None" - s.Spec.ClusterIPs = []string{"None"} - s.Spec.ExternalName = "foo.bar.example.com" - }, - numErrs: 1, - }, - { - name: "invalid ExternalName (not a DNS name)", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeExternalName - s.Spec.ExternalName = "-123" - }, - numErrs: 1, - }, - { - name: "LoadBalancer type cannot have None ClusterIP", - tweakSvc: func(s *core.Service) { - s.Spec.ClusterIP = "None" - s.Spec.ClusterIPs = []string{"None"} - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - }, - numErrs: 1, - }, - { - name: "invalid node port with clusterIP None", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) - s.Spec.ClusterIP = "None" - s.Spec.ClusterIPs = []string{"None"} - }, - numErrs: 1, - }, + }{{ + name: "missing namespace", + tweakSvc: func(s *core.Service) { + s.Namespace = "" + }, + numErrs: 1, + }, { + name: "invalid namespace", + tweakSvc: func(s *core.Service) { + s.Namespace = "-123" + }, + numErrs: 1, + }, { + name: "missing name", + tweakSvc: func(s *core.Service) { + s.Name = "" + }, + numErrs: 1, + }, { + name: "invalid name", + tweakSvc: func(s *core.Service) { + s.Name = "-123" + }, + numErrs: 1, + }, { + name: "too long name", + tweakSvc: func(s *core.Service) { + s.Name = strings.Repeat("a", 64) + }, + numErrs: 1, + }, { + name: "invalid generateName", + tweakSvc: func(s *core.Service) { + s.GenerateName = "-123" + }, + numErrs: 1, + }, { + name: "too long generateName", + tweakSvc: func(s *core.Service) { + s.GenerateName = strings.Repeat("a", 64) + }, + numErrs: 1, + }, { + name: "invalid label", + tweakSvc: func(s *core.Service) { + s.Labels["NoUppercaseOrSpecialCharsLike=Equals"] = "bar" + }, + numErrs: 1, + }, { + name: "invalid annotation", + tweakSvc: func(s *core.Service) { + s.Annotations["NoSpecialCharsLike=Equals"] = "bar" + }, + numErrs: 1, + }, { + name: "nil selector", + tweakSvc: func(s *core.Service) { + s.Spec.Selector = nil + }, + numErrs: 0, + }, { + name: "invalid selector", + tweakSvc: func(s *core.Service) { + s.Spec.Selector["NoSpecialCharsLike=Equals"] = "bar" + }, + numErrs: 1, + }, { + name: "missing session affinity", + tweakSvc: func(s *core.Service) { + s.Spec.SessionAffinity = "" + }, + numErrs: 1, + }, { + name: "missing type", + tweakSvc: func(s *core.Service) { + s.Spec.Type = "" + }, + numErrs: 1, + }, { + name: "missing ports", + tweakSvc: func(s *core.Service) { + s.Spec.Ports = nil + }, + numErrs: 1, + }, { + name: "missing ports but headless", + tweakSvc: func(s *core.Service) { + s.Spec.Ports = nil + s.Spec.ClusterIP = core.ClusterIPNone + s.Spec.ClusterIPs = []string{core.ClusterIPNone} + }, + numErrs: 0, + }, { + name: "empty port[0] name", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Name = "" + }, + numErrs: 0, + }, { + name: "empty port[1] name", + tweakSvc: func(s *core.Service) { + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 1, + }, { + name: "empty multi-port port[0] name", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Name = "" + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 1, + }, { + name: "invalid port name", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Name = "INVALID" + }, + numErrs: 1, + }, { + name: "missing protocol", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Protocol = "" + }, + numErrs: 1, + }, { + name: "invalid protocol", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Protocol = "INVALID" + }, + numErrs: 1, + }, { + name: "invalid cluster ip", + tweakSvc: func(s *core.Service) { + s.Spec.ClusterIP = "invalid" + s.Spec.ClusterIPs = []string{"invalid"} + }, + numErrs: 1, + }, { + name: "missing port", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Port = 0 + }, + numErrs: 1, + }, { + name: "invalid port", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Port = 65536 + }, + numErrs: 1, + }, { + name: "invalid TargetPort int", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].TargetPort = intstr.FromInt32(65536) + }, + numErrs: 1, + }, { + name: "valid port headless", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Port = 11722 + s.Spec.Ports[0].TargetPort = intstr.FromInt32(11722) + s.Spec.ClusterIP = core.ClusterIPNone + s.Spec.ClusterIPs = []string{core.ClusterIPNone} + }, + numErrs: 0, + }, { + name: "invalid port headless 1", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Port = 11722 + s.Spec.Ports[0].TargetPort = intstr.FromInt32(11721) + s.Spec.ClusterIP = core.ClusterIPNone + s.Spec.ClusterIPs = []string{core.ClusterIPNone} + }, + // in the v1 API, targetPorts on headless services were tolerated. + // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. + // numErrs: 1, + numErrs: 0, + }, { + name: "invalid port headless 2", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Port = 11722 + s.Spec.Ports[0].TargetPort = intstr.FromString("target") + s.Spec.ClusterIP = core.ClusterIPNone + s.Spec.ClusterIPs = []string{core.ClusterIPNone} + }, + // in the v1 API, targetPorts on headless services were tolerated. + // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. + // numErrs: 1, + numErrs: 0, + }, { + name: "invalid publicIPs localhost", + tweakSvc: func(s *core.Service) { + s.Spec.ExternalIPs = []string{"127.0.0.1"} + }, + numErrs: 1, + }, { + name: "invalid publicIPs unspecified", + tweakSvc: func(s *core.Service) { + s.Spec.ExternalIPs = []string{"0.0.0.0"} + }, + numErrs: 1, + }, { + name: "invalid publicIPs loopback", + tweakSvc: func(s *core.Service) { + s.Spec.ExternalIPs = []string{"127.0.0.1"} + }, + numErrs: 1, + }, { + name: "invalid publicIPs host", + tweakSvc: func(s *core.Service) { + s.Spec.ExternalIPs = []string{"myhost.mydomain"} + }, + numErrs: 1, + }, { + name: "dup port name", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Name = "p" + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 1, + }, { + name: "valid load balancer protocol UDP 1", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.Ports[0].Protocol = "UDP" + }, + numErrs: 0, + }, { + name: "valid load balancer protocol UDP 2", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.Ports[0] = core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)} + }, + numErrs: 0, + }, { + name: "load balancer with mix protocol", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 0, + }, { + name: "valid 1", + tweakSvc: func(s *core.Service) { + // do nothing + }, + numErrs: 0, + }, { + name: "valid 2", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Protocol = "UDP" + s.Spec.Ports[0].TargetPort = intstr.FromInt32(12345) + }, + numErrs: 0, + }, { + name: "valid 3", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].TargetPort = intstr.FromString("http") + }, + numErrs: 0, + }, { + name: "valid cluster ip - none ", + tweakSvc: func(s *core.Service) { + s.Spec.ClusterIP = core.ClusterIPNone + s.Spec.ClusterIPs = []string{core.ClusterIPNone} + }, + numErrs: 0, + }, { + name: "valid cluster ip - empty", + tweakSvc: func(s *core.Service) { + s.Spec.ClusterIPs = nil + s.Spec.Ports[0].TargetPort = intstr.FromString("http") + }, + numErrs: 0, + }, { + name: "valid type - clusterIP", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + }, + numErrs: 0, + }, { + name: "valid type - loadbalancer", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + }, + numErrs: 0, + }, { + name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=false", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) + }, + numErrs: 0, + }, { + name: "invalid type - missing AllocateLoadBalancerNodePorts for loadbalancer type", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + }, + numErrs: 1, + }, { + name: "valid type loadbalancer 2 ports", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 0, + }, { + name: "valid external load balancer 2 ports", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 0, + }, { + name: "duplicate nodeports", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)}) + }, + numErrs: 1, + }, { + name: "duplicate nodeports (different protocols)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt32(2)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 3, Protocol: "SCTP", NodePort: 1, TargetPort: intstr.FromInt32(3)}) + }, + numErrs: 0, + }, { + name: "invalid duplicate ports (with same protocol)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(80)}) + }, + numErrs: 1, + }, { + name: "valid duplicate ports (with different protocols)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(80)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 12345, Protocol: "SCTP", TargetPort: intstr.FromInt32(8088)}) + }, + numErrs: 0, + }, { + name: "valid type - cluster", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + }, + numErrs: 0, + }, { + name: "valid type - nodeport", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + }, + numErrs: 0, + }, { + name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=true", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + }, + numErrs: 0, + }, { + name: "valid type loadbalancer 2 ports", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 0, + }, { + name: "valid type loadbalancer with NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 0, + }, { + name: "valid type=NodePort service with NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 0, + }, { + name: "valid type=NodePort service without NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 0, + }, { + name: "valid cluster service without NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 0, + }, { + name: "invalid cluster service with NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 1, + }, { + name: "invalid public service with duplicate NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p1", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p2", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)}) + }, + numErrs: 1, + }, { + name: "valid type=LoadBalancer", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 0, + }, { + // For now we open firewalls, and its insecure if we open 10250, remove this + // when we have better protections in place. + name: "invalid port type=LoadBalancer", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) + }, + numErrs: 1, + }, { + name: "valid LoadBalancer source range annotation", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/8, 5.6.7.8/16" + }, + numErrs: 0, + }, { + name: "empty LoadBalancer source range annotation", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "" + }, + numErrs: 0, + }, { + name: "invalid LoadBalancer source range annotation (hostname)", + tweakSvc: func(s *core.Service) { + s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar" + }, + numErrs: 2, + }, { + name: "invalid LoadBalancer source range annotation (invalid CIDR)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33" + }, + numErrs: 1, + }, { + name: "invalid source range for non LoadBalancer type service", + tweakSvc: func(s *core.Service) { + s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"} + }, + numErrs: 1, + }, { + name: "valid LoadBalancer source range", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"} + }, + numErrs: 0, + }, { + name: "empty LoadBalancer source range", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.LoadBalancerSourceRanges = []string{" "} + }, + numErrs: 1, + }, { + name: "invalid LoadBalancer source range", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.LoadBalancerSourceRanges = []string{"foo.bar"} + }, + numErrs: 1, + }, { + name: "valid ExternalName", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeExternalName + s.Spec.ExternalName = "foo.bar.example.com" + }, + numErrs: 0, + }, { + name: "valid ExternalName (trailing dot)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeExternalName + s.Spec.ExternalName = "foo.bar.example.com." + }, + numErrs: 0, + }, { + name: "invalid ExternalName clusterIP (valid IP)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeExternalName + s.Spec.ClusterIP = "1.2.3.4" + s.Spec.ClusterIPs = []string{"1.2.3.4"} + s.Spec.ExternalName = "foo.bar.example.com" + }, + numErrs: 1, + }, { + name: "invalid ExternalName clusterIP (None)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeExternalName + s.Spec.ClusterIP = "None" + s.Spec.ClusterIPs = []string{"None"} + s.Spec.ExternalName = "foo.bar.example.com" + }, + numErrs: 1, + }, { + name: "invalid ExternalName (not a DNS name)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeExternalName + s.Spec.ExternalName = "-123" + }, + numErrs: 1, + }, { + name: "LoadBalancer type cannot have None ClusterIP", + tweakSvc: func(s *core.Service) { + s.Spec.ClusterIP = "None" + s.Spec.ClusterIPs = []string{"None"} + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + }, + numErrs: 1, + }, { + name: "invalid node port with clusterIP None", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) + s.Spec.ClusterIP = "None" + s.Spec.ClusterIPs = []string{"None"} + }, + numErrs: 1, + }, // ESIPP section begins. { name: "invalid externalTraffic field", @@ -15555,15 +14383,13 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ExternalTrafficPolicy = "invalid" }, numErrs: 1, - }, - { + }, { name: "nil internalTraffic field when feature gate is on", tweakSvc: func(s *core.Service) { s.Spec.InternalTrafficPolicy = nil }, numErrs: 1, - }, - { + }, { name: "internalTrafficPolicy field nil when type is ExternalName", tweakSvc: func(s *core.Service) { s.Spec.InternalTrafficPolicy = nil @@ -15571,8 +14397,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ExternalName = "foo.bar.com" }, numErrs: 0, - }, - { + }, { // Typically this should fail validation, but in v1.22 we have existing clusters // that may have allowed internalTrafficPolicy when Type=ExternalName. // This test case ensures we don't break compatibility for internalTrafficPolicy @@ -15585,32 +14410,28 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ExternalName = "foo.bar.com" }, numErrs: 0, - }, - { + }, { name: "invalid internalTraffic field", tweakSvc: func(s *core.Service) { invalid := core.ServiceInternalTrafficPolicy("invalid") s.Spec.InternalTrafficPolicy = &invalid }, numErrs: 1, - }, - { + }, { name: "internalTrafficPolicy field set to Cluster", tweakSvc: func(s *core.Service) { cluster := core.ServiceInternalTrafficPolicyCluster s.Spec.InternalTrafficPolicy = &cluster }, numErrs: 0, - }, - { + }, { name: "internalTrafficPolicy field set to Local", tweakSvc: func(s *core.Service) { local := core.ServiceInternalTrafficPolicyLocal s.Spec.InternalTrafficPolicy = &local }, numErrs: 0, - }, - { + }, { name: "negative healthCheckNodePort field", tweakSvc: func(s *core.Service) { s.Spec.Type = core.ServiceTypeLoadBalancer @@ -15619,8 +14440,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.HealthCheckNodePort = -1 }, numErrs: 1, - }, - { + }, { name: "negative healthCheckNodePort field", tweakSvc: func(s *core.Service) { s.Spec.Type = core.ServiceTypeLoadBalancer @@ -15643,8 +14463,7 @@ func TestValidateServiceCreate(t *testing.T) { } }, numErrs: 1, - }, - { + }, { name: "sessionAffinityConfig can't be set when session affinity is None", tweakSvc: func(s *core.Service) { s.Spec.Type = core.ServiceTypeLoadBalancer @@ -15667,8 +14486,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{invalidServiceIPFamily} }, numErrs: 1, - }, - { + }, { name: "invalid, service with invalid ipFamilies (2nd)", tweakSvc: func(s *core.Service) { invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family") @@ -15676,16 +14494,14 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, invalidServiceIPFamily} }, numErrs: 1, - }, - { + }, { name: "IPFamilyPolicy(singleStack) is set for two families", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &singleStack s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 0, // this validated in alloc code. - }, - { + }, { name: "valid, IPFamilyPolicy(preferDualStack) is set for two families (note: alloc sets families)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &preferDualStack @@ -15701,53 +14517,46 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol, core.IPv4Protocol} }, numErrs: 1, - }, - { + }, { name: "invalid, service with same ip families", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv6Protocol} }, numErrs: 1, - }, - { + }, { name: "valid, nil service ipFamilies", tweakSvc: func(s *core.Service) { s.Spec.IPFamilies = nil }, numErrs: 0, - }, - { + }, { name: "valid, service with valid ipFamilies (v4)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "valid, service with valid ipFamilies (v6)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} }, numErrs: 0, - }, - { + }, { name: "valid, service with valid ipFamilies(v4,v6)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 0, - }, - { + }, { name: "valid, service with valid ipFamilies(v6,v4)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "valid, service preferred dual stack with single family", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &preferDualStack @@ -15763,8 +14572,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"garbage-ip"} }, numErrs: 1, - }, - { + }, { name: "invalid, garbage ips", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15772,8 +14580,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"garbage-ip", "garbage-second-ip"} }, numErrs: 2, - }, - { + }, { name: "invalid, garbage first ip", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15781,8 +14588,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"garbage-ip", "2001::1"} }, numErrs: 1, - }, - { + }, { name: "invalid, garbage second ip", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15790,8 +14596,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"2001::1", "garbage-ip"} }, numErrs: 1, - }, - { + }, { name: "invalid, NONE + IP", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15799,8 +14604,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"None", "2001::1"} }, numErrs: 1, - }, - { + }, { name: "invalid, IP + NONE", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15808,8 +14612,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"2001::1", "None"} }, numErrs: 1, - }, - { + }, { name: "invalid, EMPTY STRING + IP", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15817,8 +14620,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"", "2001::1"} }, numErrs: 2, - }, - { + }, { name: "invalid, IP + EMPTY STRING", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15826,8 +14628,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"2001::1", ""} }, numErrs: 1, - }, - { + }, { name: "invalid, same ip family (v6)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15836,8 +14637,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 2, - }, - { + }, { name: "invalid, same ip family (v4)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15847,8 +14647,7 @@ func TestValidateServiceCreate(t *testing.T) { }, numErrs: 2, - }, - { + }, { name: "invalid, more than two ips", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15857,8 +14656,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 1, - }, - { + }, { name: " multi ip, dualstack not set (request for downgrade)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &singleStack @@ -15867,8 +14665,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 0, - }, - { + }, { name: "valid, headless-no-selector + multi family + gate off", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15878,8 +14675,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.Selector = nil }, numErrs: 0, - }, - { + }, { name: "valid, multi ip, single ipfamilies preferDualStack", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &preferDualStack @@ -15899,8 +14695,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "invalid, families don't match (v4=>v6)", tweakSvc: func(s *core.Service) { s.Spec.ClusterIP = "10.0.0.1" @@ -15908,8 +14703,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} }, numErrs: 1, - }, - { + }, { name: "invalid, families don't match (v6=>v4)", tweakSvc: func(s *core.Service) { s.Spec.ClusterIP = "2001::1" @@ -15917,8 +14711,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} }, numErrs: 1, - }, - { + }, { name: "valid. no field set", tweakSvc: func(s *core.Service) { }, @@ -15933,8 +14726,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"10.0.0.1"} }, numErrs: 0, - }, - { + }, { name: "valid, single family", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &singleStack @@ -15942,8 +14734,7 @@ func TestValidateServiceCreate(t *testing.T) { }, numErrs: 0, - }, - { + }, { name: "valid, single ip + single family", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &singleStack @@ -15953,8 +14744,7 @@ func TestValidateServiceCreate(t *testing.T) { }, numErrs: 0, - }, - { + }, { name: "valid, single ip + single family (dual stack requested)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &preferDualStack @@ -15964,8 +14754,7 @@ func TestValidateServiceCreate(t *testing.T) { }, numErrs: 0, - }, - { + }, { name: "valid, single ip, multi ipfamilies", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15974,8 +14763,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 0, - }, - { + }, { name: "valid, multi ips, multi ipfamilies (4,6)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15984,8 +14772,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 0, - }, - { + }, { name: "valid, ips, multi ipfamilies (6,4)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -15994,8 +14781,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "valid, multi ips (6,4)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -16003,16 +14789,14 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"} }, numErrs: 0, - }, - { + }, { name: "valid, multi ipfamilies (6,4)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "valid, multi ips (4,6)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -16020,16 +14804,14 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"} }, numErrs: 0, - }, - { + }, { name: "valid, multi ipfamilies (4,6)", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 0, - }, - { + }, { name: "valid, dual stack", tweakSvc: func(s *core.Service) { s.Spec.IPFamilyPolicy = &requireDualStack @@ -16048,8 +14830,7 @@ func TestValidateServiceCreate(t *testing.T) { }} }, numErrs: 0, - }, - { + }, { name: `valid custom appProtocol`, tweakSvc: func(s *core.Service) { s.Spec.Ports = []core.ServicePort{{ @@ -16060,8 +14841,7 @@ func TestValidateServiceCreate(t *testing.T) { }} }, numErrs: 0, - }, - { + }, { name: `invalid appProtocol`, tweakSvc: func(s *core.Service) { s.Spec.Ports = []core.ServicePort{{ @@ -16082,23 +14862,20 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"} }, numErrs: 1, - }, - { + }, { name: "invalid cluster ip != clusterIP in single ip service", tweakSvc: func(s *core.Service) { s.Spec.ClusterIP = "10.0.0.10" s.Spec.ClusterIPs = []string{"10.0.0.1"} }, numErrs: 1, - }, - { + }, { name: "Use AllocateLoadBalancerNodePorts when type is not LoadBalancer", tweakSvc: func(s *core.Service) { s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) }, numErrs: 1, - }, - { + }, { name: "valid LoadBalancerClass when type is LoadBalancer", tweakSvc: func(s *core.Service) { s.Spec.Type = core.ServiceTypeLoadBalancer @@ -16107,8 +14884,7 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") }, numErrs: 0, - }, - { + }, { name: "invalid LoadBalancerClass", tweakSvc: func(s *core.Service) { s.Spec.Type = core.ServiceTypeLoadBalancer @@ -16117,16 +14893,14 @@ func TestValidateServiceCreate(t *testing.T) { s.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerClass") }, numErrs: 1, - }, - { + }, { name: "invalid: set LoadBalancerClass when type is not LoadBalancer", tweakSvc: func(s *core.Service) { s.Spec.Type = core.ServiceTypeClusterIP s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") }, numErrs: 1, - }, - { + }, { name: "topology annotations are mismatched", tweakSvc: func(s *core.Service) { s.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original" @@ -16156,74 +14930,66 @@ func TestValidateServiceExternalTrafficPolicy(t *testing.T) { name string tweakSvc func(svc *core.Service) // Given a basic valid service, each test case can customize it. numErrs int - }{ - { - name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal - s.Spec.HealthCheckNodePort = 34567 - }, - numErrs: 0, + }{{ + name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal + s.Spec.HealthCheckNodePort = 34567 }, - { - name: "valid nodePort service with externalTrafficPolicy set", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal - }, - numErrs: 0, + numErrs: 0, + }, { + name: "valid nodePort service with externalTrafficPolicy set", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal }, - { - name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeClusterIP - }, - numErrs: 0, + numErrs: 0, + }, { + name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP }, - { - name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - s.Spec.HealthCheckNodePort = 34567 - }, - numErrs: 1, + numErrs: 0, + }, { + name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + s.Spec.HealthCheckNodePort = 34567 }, - { - name: "cannot set healthCheckNodePort field on nodePort service", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal - s.Spec.HealthCheckNodePort = 34567 - }, - numErrs: 1, + numErrs: 1, + }, { + name: "cannot set healthCheckNodePort field on nodePort service", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal + s.Spec.HealthCheckNodePort = 34567 }, - { - name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeClusterIP - s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal - s.Spec.HealthCheckNodePort = 34567 - }, - numErrs: 2, + numErrs: 1, + }, { + name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal + s.Spec.HealthCheckNodePort = 34567 }, - { - name: "externalTrafficPolicy is required on NodePort service", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeNodePort - }, - numErrs: 1, + numErrs: 2, + }, { + name: "externalTrafficPolicy is required on NodePort service", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort }, - { - name: "externalTrafficPolicy is required on LoadBalancer service", - tweakSvc: func(s *core.Service) { - s.Spec.Type = core.ServiceTypeLoadBalancer - }, - numErrs: 1, + numErrs: 1, + }, { + name: "externalTrafficPolicy is required on LoadBalancer service", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer }, + numErrs: 1, + }, } for _, tc := range testCases { @@ -16247,97 +15013,87 @@ func TestValidateReplicationControllerStatus(t *testing.T) { observedGeneration int64 expectedErr bool - }{ - { - name: "valid status", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: false, - }, - { - name: "invalid replicas", - replicas: -1, - fullyLabeledReplicas: 3, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid fullyLabeledReplicas", - replicas: 3, - fullyLabeledReplicas: -1, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid readyReplicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: -1, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid availableReplicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 3, - availableReplicas: -1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid observedGeneration", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 3, - availableReplicas: 3, - observedGeneration: -1, - expectedErr: true, - }, - { - name: "fullyLabeledReplicas greater than replicas", - replicas: 3, - fullyLabeledReplicas: 4, - readyReplicas: 3, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "readyReplicas greater than replicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 4, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "availableReplicas greater than replicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 3, - availableReplicas: 4, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "availableReplicas greater than readyReplicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 2, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, + }{{ + name: "valid status", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: false, + }, { + name: "invalid replicas", + replicas: -1, + fullyLabeledReplicas: 3, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid fullyLabeledReplicas", + replicas: 3, + fullyLabeledReplicas: -1, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid readyReplicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: -1, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid availableReplicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 3, + availableReplicas: -1, + observedGeneration: 2, + expectedErr: true, + }, { + name: "invalid observedGeneration", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 3, + availableReplicas: 3, + observedGeneration: -1, + expectedErr: true, + }, { + name: "fullyLabeledReplicas greater than replicas", + replicas: 3, + fullyLabeledReplicas: 4, + readyReplicas: 3, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, { + name: "readyReplicas greater than replicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 4, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, { + name: "availableReplicas greater than replicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 3, + availableReplicas: 4, + observedGeneration: 1, + expectedErr: true, + }, { + name: "availableReplicas greater than readyReplicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 2, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, } for _, test := range tests { @@ -16373,30 +15129,29 @@ func TestValidateReplicationControllerStatusUpdate(t *testing.T) { old core.ReplicationController update core.ReplicationController } - successCases := []rcUpdateTest{ - { - old: core.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: core.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - Status: core.ReplicationControllerStatus{ - Replicas: 2, - }, + successCases := []rcUpdateTest{{ + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, }, - update: core.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: core.ReplicationControllerSpec{ - Replicas: 3, - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - Status: core.ReplicationControllerStatus{ - Replicas: 4, - }, + Status: core.ReplicationControllerStatus{ + Replicas: 2, }, }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 3, + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + Status: core.ReplicationControllerStatus{ + Replicas: 4, + }, + }, + }, } for _, successCase := range successCases { successCase.old.ObjectMeta.ResourceVersion = "1" @@ -16481,41 +15236,39 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { old core.ReplicationController update core.ReplicationController } - successCases := []rcUpdateTest{ - { - old: core.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: core.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - update: core.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: core.ReplicationControllerSpec{ - Replicas: 3, - Selector: validSelector, - Template: &validPodTemplate.Template, - }, + successCases := []rcUpdateTest{{ + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, }, }, - { - old: core.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: core.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - update: core.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: core.ReplicationControllerSpec{ - Replicas: 1, - Selector: validSelector, - Template: &readWriteVolumePodTemplate.Template, - }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 3, + Selector: validSelector, + Template: &validPodTemplate.Template, }, }, + }, { + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 1, + Selector: validSelector, + Template: &readWriteVolumePodTemplate.Template, + }, + }, + }, } for _, successCase := range successCases { successCase.old.ObjectMeta.ResourceVersion = "1" @@ -16640,29 +15393,26 @@ func TestValidateReplicationController(t *testing.T) { }, }, } - successCases := []core.ReplicationController{ - { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: core.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, + successCases := []core.ReplicationController{{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, }, - { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: core.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, }, - { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: core.ReplicationControllerSpec{ - Replicas: 1, - Selector: validSelector, - Template: &readWriteVolumePodTemplate.Template, - }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 1, + Selector: validSelector, + Template: &readWriteVolumePodTemplate.Template, }, + }, } for _, successCase := range successCases { if errs := ValidateReplicationController(&successCase, PodValidationOptions{}); len(errs) != 0 { @@ -16841,80 +15591,75 @@ func TestValidateReplicationController(t *testing.T) { func TestValidateNode(t *testing.T) { validSelector := map[string]string{"a": "b"} invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} - successCases := []core.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Labels: validSelector, + successCases := []core.Node{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Labels: validSelector, + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, }, - Status: core.NodeStatus{ - Addresses: []core.NodeAddress{ - {Type: core.NodeExternalIP, Address: "something"}, - }, - Capacity: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - core.ResourceName("my.org/gpu"): resource.MustParse("10"), - core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"), - core.ResourceName("hugepages-1Gi"): resource.MustParse("0"), - }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("my.org/gpu"): resource.MustParse("10"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"), + core.ResourceName("hugepages-1Gi"): resource.MustParse("0"), }, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, }, - Status: core.NodeStatus{ - Addresses: []core.NodeAddress{ - {Type: core.NodeExternalIP, Address: "something"}, - }, - Capacity: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("0"), - }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), }, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Labels: validSelector, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Labels: validSelector, + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, }, - Status: core.NodeStatus{ - Addresses: []core.NodeAddress{ - {Type: core.NodeExternalIP, Address: "something"}, - }, - Capacity: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - core.ResourceName("my.org/gpu"): resource.MustParse("10"), - core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"), - core.ResourceName("hugepages-1Gi"): resource.MustParse("10Gi"), - }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("my.org/gpu"): resource.MustParse("10"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"), + core.ResourceName("hugepages-1Gi"): resource.MustParse("10Gi"), }, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dedicated-node1", + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-node1", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, }, - Status: core.NodeStatus{ - Addresses: []core.NodeAddress{ - {Type: core.NodeExternalIP, Address: "something"}, - }, - Capacity: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("0"), - }, - }, - Spec: core.NodeSpec{ - // Add a valid taint to a node - Taints: []core.Taint{{Key: "GPU", Value: "true", Effect: "NoSchedule"}}, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), }, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Annotations: map[string]string{ - core.PreferAvoidPodsAnnotationKey: ` + Spec: core.NodeSpec{ + // Add a valid taint to a node + Taints: []core.Taint{{Key: "GPU", Value: "true", Effect: "NoSchedule"}}, + }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Annotations: map[string]string{ + core.PreferAvoidPodsAnnotationKey: ` { "preferAvoidPods": [ { @@ -16932,35 +15677,34 @@ func TestValidateNode(t *testing.T) { } ] }`, - }, - }, - Status: core.NodeStatus{ - Addresses: []core.NodeAddress{ - {Type: core.NodeExternalIP, Address: "something"}, - }, - Capacity: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("0"), - }, }, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, }, - Status: core.NodeStatus{ - Addresses: []core.NodeAddress{ - {Type: core.NodeExternalIP, Address: "something"}, - }, - Capacity: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("0"), - }, - }, - Spec: core.NodeSpec{ - PodCIDRs: []string{"192.168.0.0/16"}, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), }, }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + PodCIDRs: []string{"192.168.0.0/16"}, + }, + }, } for _, successCase := range successCases { if errs := ValidateNode(&successCase); len(errs) != 0 { @@ -17639,179 +16383,161 @@ func TestValidateServiceUpdate(t *testing.T) { name string tweakSvc func(oldSvc, newSvc *core.Service) // given basic valid services, each test case can customize them numErrs int - }{ - { - name: "no change", - tweakSvc: func(oldSvc, newSvc *core.Service) { - // do nothing - }, - numErrs: 0, + }{{ + name: "no change", + tweakSvc: func(oldSvc, newSvc *core.Service) { + // do nothing }, - { - name: "change name", - tweakSvc: func(oldSvc, newSvc *core.Service) { - newSvc.Name += "2" - }, - numErrs: 1, + numErrs: 0, + }, { + name: "change name", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Name += "2" }, - { - name: "change namespace", - tweakSvc: func(oldSvc, newSvc *core.Service) { - newSvc.Namespace += "2" - }, - numErrs: 1, + numErrs: 1, + }, { + name: "change namespace", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Namespace += "2" }, - { - name: "change label valid", - tweakSvc: func(oldSvc, newSvc *core.Service) { - newSvc.Labels["key"] = "other-value" - }, - numErrs: 0, + numErrs: 1, + }, { + name: "change label valid", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Labels["key"] = "other-value" }, - { - name: "add label", - tweakSvc: func(oldSvc, newSvc *core.Service) { - newSvc.Labels["key2"] = "value2" - }, - numErrs: 0, + numErrs: 0, + }, { + name: "add label", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Labels["key2"] = "value2" }, - { - name: "change cluster IP", - tweakSvc: func(oldSvc, newSvc *core.Service) { - oldSvc.Spec.ClusterIP = "1.2.3.4" - oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} + numErrs: 0, + }, { + name: "change cluster IP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.ClusterIP = "1.2.3.4" + oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} - newSvc.Spec.ClusterIP = "8.6.7.5" - newSvc.Spec.ClusterIPs = []string{"8.6.7.5"} - }, - numErrs: 1, + newSvc.Spec.ClusterIP = "8.6.7.5" + newSvc.Spec.ClusterIPs = []string{"8.6.7.5"} }, - { - name: "remove cluster IP", - tweakSvc: func(oldSvc, newSvc *core.Service) { - oldSvc.Spec.ClusterIP = "1.2.3.4" - oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} + numErrs: 1, + }, { + name: "remove cluster IP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.ClusterIP = "1.2.3.4" + oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} - newSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIPs = nil - }, - numErrs: 1, + newSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIPs = nil }, - { - name: "change affinity", - tweakSvc: func(oldSvc, newSvc *core.Service) { - newSvc.Spec.SessionAffinity = "ClientIP" - newSvc.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{ - ClientIP: &core.ClientIPConfig{ - TimeoutSeconds: utilpointer.Int32(90), - }, - } - }, - numErrs: 0, + numErrs: 1, + }, { + name: "change affinity", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.SessionAffinity = "ClientIP" + newSvc.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{ + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: utilpointer.Int32(90), + }, + } }, - { - name: "remove affinity", - tweakSvc: func(oldSvc, newSvc *core.Service) { - newSvc.Spec.SessionAffinity = "" - }, - numErrs: 1, + numErrs: 0, + }, { + name: "remove affinity", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.SessionAffinity = "" }, - { - name: "change type", - tweakSvc: func(oldSvc, newSvc *core.Service) { - newSvc.Spec.Type = core.ServiceTypeLoadBalancer - newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - }, - numErrs: 0, + numErrs: 1, + }, { + name: "change type", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) }, - { - name: "remove type", - tweakSvc: func(oldSvc, newSvc *core.Service) { - newSvc.Spec.Type = "" - }, - numErrs: 1, + numErrs: 0, + }, { + name: "remove type", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.Type = "" }, - { - name: "change type -> nodeport", - tweakSvc: func(oldSvc, newSvc *core.Service) { - newSvc.Spec.Type = core.ServiceTypeNodePort - newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - }, - numErrs: 0, + numErrs: 1, + }, { + name: "change type -> nodeport", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.Type = core.ServiceTypeNodePort + newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster }, - { - name: "add loadBalancerSourceRanges", - tweakSvc: func(oldSvc, newSvc *core.Service) { - oldSvc.Spec.Type = core.ServiceTypeLoadBalancer - oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - newSvc.Spec.Type = core.ServiceTypeLoadBalancer - newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - newSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"} - }, - numErrs: 0, + numErrs: 0, + }, { + name: "add loadBalancerSourceRanges", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeLoadBalancer + oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + newSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"} }, - { - name: "update loadBalancerSourceRanges", - tweakSvc: func(oldSvc, newSvc *core.Service) { - oldSvc.Spec.Type = core.ServiceTypeLoadBalancer - oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - oldSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"} - newSvc.Spec.Type = core.ServiceTypeLoadBalancer - newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - newSvc.Spec.LoadBalancerSourceRanges = []string{"10.100.0.0/16"} - }, - numErrs: 0, + numErrs: 0, + }, { + name: "update loadBalancerSourceRanges", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeLoadBalancer + oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + oldSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"} + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) + newSvc.Spec.LoadBalancerSourceRanges = []string{"10.100.0.0/16"} }, - { - name: "LoadBalancer type cannot have None ClusterIP", - tweakSvc: func(oldSvc, newSvc *core.Service) { - newSvc.Spec.ClusterIP = "None" - newSvc.Spec.ClusterIPs = []string{"None"} - newSvc.Spec.Type = core.ServiceTypeLoadBalancer - newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster - newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) - }, - numErrs: 1, + numErrs: 0, + }, { + name: "LoadBalancer type cannot have None ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.ClusterIP = "None" + newSvc.Spec.ClusterIPs = []string{"None"} + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster + newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) }, - { - name: "`None` ClusterIP can NOT be changed", - tweakSvc: func(oldSvc, newSvc *core.Service) { - oldSvc.Spec.Type = core.ServiceTypeClusterIP - newSvc.Spec.Type = core.ServiceTypeClusterIP + numErrs: 1, + }, { + name: "`None` ClusterIP can NOT be changed", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeClusterIP + newSvc.Spec.Type = core.ServiceTypeClusterIP - oldSvc.Spec.ClusterIP = "None" - oldSvc.Spec.ClusterIPs = []string{"None"} + oldSvc.Spec.ClusterIP = "None" + oldSvc.Spec.ClusterIPs = []string{"None"} - newSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIPs = []string{"1.2.3.4"} - }, - numErrs: 1, + newSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIPs = []string{"1.2.3.4"} }, - { - name: "`None` ClusterIP can NOT be removed", - tweakSvc: func(oldSvc, newSvc *core.Service) { - oldSvc.Spec.ClusterIP = "None" - oldSvc.Spec.ClusterIPs = []string{"None"} + numErrs: 1, + }, { + name: "`None` ClusterIP can NOT be removed", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.ClusterIP = "None" + oldSvc.Spec.ClusterIPs = []string{"None"} - newSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIPs = nil - }, - numErrs: 1, + newSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIPs = nil }, - { - name: "ClusterIP can NOT be changed to None", - tweakSvc: func(oldSvc, newSvc *core.Service) { - oldSvc.Spec.ClusterIP = "1.2.3.4" - oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} + numErrs: 1, + }, { + name: "ClusterIP can NOT be changed to None", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.ClusterIP = "1.2.3.4" + oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} - newSvc.Spec.ClusterIP = "None" - newSvc.Spec.ClusterIPs = []string{"None"} - }, - numErrs: 1, + newSvc.Spec.ClusterIP = "None" + newSvc.Spec.ClusterIPs = []string{"None"} }, + numErrs: 1, + }, { name: "Service with ClusterIP type cannot change its set ClusterIP", @@ -17826,8 +16552,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 1, - }, - { + }, { name: "Service with ClusterIP type can change its empty ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -17839,8 +16564,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "Service with ClusterIP type cannot change its set ClusterIP when changing type to NodePort", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -17854,8 +16578,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 1, - }, - { + }, { name: "Service with ClusterIP type can change its empty ClusterIP when changing type to NodePort", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -17869,8 +16592,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "Service with ClusterIP type cannot change its ClusterIP when changing type to LoadBalancer", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -17885,8 +16607,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 1, - }, - { + }, { name: "Service with ClusterIP type can change its empty ClusterIP when changing type to LoadBalancer", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -17901,8 +16622,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from true to false", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -17912,8 +16632,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) }, numErrs: 0, - }, - { + }, { name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from false to true", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -17923,8 +16642,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) }, numErrs: 0, - }, - { + }, { name: "Service with NodePort type cannot change its set ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeNodePort @@ -17938,8 +16656,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 1, - }, - { + }, { name: "Service with NodePort type can change its empty ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeNodePort @@ -17953,8 +16670,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "Service with NodePort type cannot change its set ClusterIP when changing type to ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeNodePort @@ -17967,8 +16683,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 1, - }, - { + }, { name: "Service with NodePort type can change its empty ClusterIP when changing type to ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeNodePort @@ -17981,8 +16696,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "Service with NodePort type cannot change its set ClusterIP when changing type to LoadBalancer", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeNodePort @@ -17997,8 +16711,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 1, - }, - { + }, { name: "Service with NodePort type can change its empty ClusterIP when changing type to LoadBalancer", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeNodePort @@ -18013,8 +16726,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "Service with LoadBalancer type cannot change its set ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18030,8 +16742,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 1, - }, - { + }, { name: "Service with LoadBalancer type can change its empty ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18047,8 +16758,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18062,8 +16772,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 1, - }, - { + }, { name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18077,8 +16786,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to NodePort", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18093,8 +16801,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 1, - }, - { + }, { name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to NodePort", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18109,8 +16816,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "Service with ExternalName type can change its empty ClusterIP when changing type to ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeExternalName @@ -18123,8 +16829,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "Service with ExternalName type can change its set ClusterIP when changing type to ClusterIP", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeExternalName @@ -18137,8 +16842,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} }, numErrs: 0, - }, - { + }, { name: "invalid node port with clusterIP None", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeNodePort @@ -18164,8 +16868,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.Type = core.ServiceTypeClusterIP }, numErrs: 0, - }, - { + }, { name: "invalid: convert to ExternalName", tweakSvc: func(oldSvc, newSvc *core.Service) { singleStack := core.IPFamilyPolicySingleStack @@ -18190,8 +16893,7 @@ func TestValidateServiceUpdate(t *testing.T) { }, numErrs: 3, - }, - { + }, { name: "valid: convert to ExternalName", tweakSvc: func(oldSvc, newSvc *core.Service) { singleStack := core.IPFamilyPolicySingleStack @@ -18217,8 +16919,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "same ServiceIPFamily, change IPFamilyPolicy to singleStack", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -18230,8 +16931,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "same ServiceIPFamily, change IPFamilyPolicy singleStack => requireDualStack", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -18269,8 +16969,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.Type = core.ServiceTypeExternalName }, numErrs: 0, - }, - { + }, { name: "setting ipfamily from nil to v4", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.IPFamilies = nil @@ -18279,8 +16978,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "setting ipfamily from nil to v6", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.IPFamilies = nil @@ -18289,8 +16987,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} }, numErrs: 0, - }, - { + }, { name: "change primary ServiceIPFamily", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" @@ -18322,8 +17019,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "valid: upgrade to dual stack with preferDualStack", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" @@ -18358,8 +17054,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 0, - }, - { + }, { name: "valid: upgrade to dual stack, with specific secondary ip", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" @@ -18375,8 +17070,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 0, - }, - { + }, { name: "valid: downgrade from dual to single", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" @@ -18392,8 +17086,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "valid: change families for a headless service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "None" @@ -18409,8 +17102,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "valid: upgrade a headless service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "None" @@ -18426,8 +17118,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} }, numErrs: 0, - }, - { + }, { name: "valid: downgrade a headless service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "None" @@ -18461,8 +17152,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} }, numErrs: 4, - }, - { + }, { name: "invalid change first ip, in dualstack service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"} @@ -18478,8 +17168,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 1, - }, - { + }, { name: "invalid, change second ip in dualstack service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" @@ -18495,8 +17184,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 1, - }, - { + }, { name: "downgrade keeping the families", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" @@ -18512,8 +17200,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 0, // families and ips are trimmed in strategy - }, - { + }, { name: "invalid, downgrade without changing to singleStack", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" @@ -18529,8 +17216,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} }, numErrs: 2, - }, - { + }, { name: "invalid, downgrade and change primary ip", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" @@ -18546,8 +17232,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} }, numErrs: 1, - }, - { + }, { name: "invalid: upgrade to dual stack and change primary", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" @@ -18564,31 +17249,27 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} }, numErrs: 1, - }, - { + }, { name: "update to valid app protocol", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}} newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("https")}} }, numErrs: 0, - }, - { + }, { name: "update to invalid app protocol", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}} newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("~https")}} }, numErrs: 1, - }, - { + }, { name: "Set AllocateLoadBalancerNodePorts when type is not LoadBalancer", tweakSvc: func(oldSvc, newSvc *core.Service) { newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) }, numErrs: 1, - }, - { + }, { name: "update LoadBalancer type of service without change LoadBalancerClass", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18601,8 +17282,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old") }, numErrs: 0, - }, - { + }, { name: "invalid: change LoadBalancerClass when update service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18615,8 +17295,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new") }, numErrs: 1, - }, - { + }, { name: "invalid: unset LoadBalancerClass when update service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18629,8 +17308,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = nil }, numErrs: 1, - }, - { + }, { name: "invalid: set LoadBalancerClass when update service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18643,8 +17321,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new") }, numErrs: 1, - }, - { + }, { name: "update to LoadBalancer type of service with valid LoadBalancerClass", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -18655,8 +17332,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") }, numErrs: 0, - }, - { + }, { name: "update to LoadBalancer type of service without LoadBalancerClass", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -18667,8 +17343,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = nil }, numErrs: 0, - }, - { + }, { name: "invalid: set invalid LoadBalancerClass when update service to LoadBalancer", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -18679,8 +17354,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerclass") }, numErrs: 2, - }, - { + }, { name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeClusterIP @@ -18689,8 +17363,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") }, numErrs: 2, - }, - { + }, { name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeExternalName @@ -18699,8 +17372,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") }, numErrs: 3, - }, - { + }, { name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeNodePort @@ -18710,8 +17382,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") }, numErrs: 2, - }, - { + }, { name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18722,8 +17393,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") }, numErrs: 2, - }, - { + }, { name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18734,8 +17404,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") }, numErrs: 3, - }, - { + }, { name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service", tweakSvc: func(oldSvc, newSvc *core.Service) { oldSvc.Spec.Type = core.ServiceTypeLoadBalancer @@ -18747,8 +17416,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") }, numErrs: 2, - }, - { + }, { name: "update internalTrafficPolicy from Cluster to Local", tweakSvc: func(oldSvc, newSvc *core.Service) { cluster := core.ServiceInternalTrafficPolicyCluster @@ -18758,8 +17426,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.InternalTrafficPolicy = &local }, numErrs: 0, - }, - { + }, { name: "update internalTrafficPolicy from Local to Cluster", tweakSvc: func(oldSvc, newSvc *core.Service) { local := core.ServiceInternalTrafficPolicyLocal @@ -18769,8 +17436,7 @@ func TestValidateServiceUpdate(t *testing.T) { newSvc.Spec.InternalTrafficPolicy = &cluster }, numErrs: 0, - }, - { + }, { name: "topology annotations are mismatched", tweakSvc: func(oldSvc, newSvc *core.Service) { newSvc.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original" @@ -18869,28 +17535,24 @@ func TestValidateLimitRangeForLocalStorage(t *testing.T) { testCases := []struct { name string spec core.LimitRangeSpec - }{ - { - name: "all-fields-valid", - spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePod, - Max: getLocalStorageResourceList("10000Mi"), - Min: getLocalStorageResourceList("100Mi"), - MaxLimitRequestRatio: getLocalStorageResourceList(""), - }, - { - Type: core.LimitTypeContainer, - Max: getLocalStorageResourceList("10000Mi"), - Min: getLocalStorageResourceList("100Mi"), - Default: getLocalStorageResourceList("500Mi"), - DefaultRequest: getLocalStorageResourceList("200Mi"), - MaxLimitRequestRatio: getLocalStorageResourceList(""), - }, - }, - }, + }{{ + name: "all-fields-valid", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePod, + Max: getLocalStorageResourceList("10000Mi"), + Min: getLocalStorageResourceList("100Mi"), + MaxLimitRequestRatio: getLocalStorageResourceList(""), + }, { + Type: core.LimitTypeContainer, + Max: getLocalStorageResourceList("10000Mi"), + Min: getLocalStorageResourceList("100Mi"), + Default: getLocalStorageResourceList("500Mi"), + DefaultRequest: getLocalStorageResourceList("200Mi"), + MaxLimitRequestRatio: getLocalStorageResourceList(""), + }}, }, + }, } for _, testCase := range testCases { @@ -18905,100 +17567,80 @@ func TestValidateLimitRange(t *testing.T) { successCases := []struct { name string spec core.LimitRangeSpec - }{ - { - name: "all-fields-valid", - spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePod, - Max: getResourceList("100m", "10000Mi"), - Min: getResourceList("5m", "100Mi"), - MaxLimitRequestRatio: getResourceList("10", ""), - }, - { - Type: core.LimitTypeContainer, - Max: getResourceList("100m", "10000Mi"), - Min: getResourceList("5m", "100Mi"), - Default: getResourceList("50m", "500Mi"), - DefaultRequest: getResourceList("10m", "200Mi"), - MaxLimitRequestRatio: getResourceList("10", ""), - }, - { - Type: core.LimitTypePersistentVolumeClaim, - Max: getStorageResourceList("10Gi"), - Min: getStorageResourceList("5Gi"), - }, - }, - }, + }{{ + name: "all-fields-valid", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePod, + Max: getResourceList("100m", "10000Mi"), + Min: getResourceList("5m", "100Mi"), + MaxLimitRequestRatio: getResourceList("10", ""), + }, { + Type: core.LimitTypeContainer, + Max: getResourceList("100m", "10000Mi"), + Min: getResourceList("5m", "100Mi"), + Default: getResourceList("50m", "500Mi"), + DefaultRequest: getResourceList("10m", "200Mi"), + MaxLimitRequestRatio: getResourceList("10", ""), + }, { + Type: core.LimitTypePersistentVolumeClaim, + Max: getStorageResourceList("10Gi"), + Min: getStorageResourceList("5Gi"), + }}, }, - { - name: "pvc-min-only", - spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePersistentVolumeClaim, - Min: getStorageResourceList("5Gi"), - }, - }, - }, + }, { + name: "pvc-min-only", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePersistentVolumeClaim, + Min: getStorageResourceList("5Gi"), + }}, }, - { - name: "pvc-max-only", - spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePersistentVolumeClaim, - Max: getStorageResourceList("10Gi"), - }, - }, - }, + }, { + name: "pvc-max-only", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePersistentVolumeClaim, + Max: getStorageResourceList("10Gi"), + }}, }, - { - name: "all-fields-valid-big-numbers", - spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypeContainer, - Max: getResourceList("100m", "10000T"), - Min: getResourceList("5m", "100Mi"), - Default: getResourceList("50m", "500Mi"), - DefaultRequest: getResourceList("10m", "200Mi"), - MaxLimitRequestRatio: getResourceList("10", ""), - }, - }, - }, + }, { + name: "all-fields-valid-big-numbers", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypeContainer, + Max: getResourceList("100m", "10000T"), + Min: getResourceList("5m", "100Mi"), + Default: getResourceList("50m", "500Mi"), + DefaultRequest: getResourceList("10m", "200Mi"), + MaxLimitRequestRatio: getResourceList("10", ""), + }}, }, - { - name: "thirdparty-fields-all-valid-standard-container-resources", - spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: "thirdparty.com/foo", - Max: getResourceList("100m", "10000T"), - Min: getResourceList("5m", "100Mi"), - Default: getResourceList("50m", "500Mi"), - DefaultRequest: getResourceList("10m", "200Mi"), - MaxLimitRequestRatio: getResourceList("10", ""), - }, - }, - }, + }, { + name: "thirdparty-fields-all-valid-standard-container-resources", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{{ + Type: "thirdparty.com/foo", + Max: getResourceList("100m", "10000T"), + Min: getResourceList("5m", "100Mi"), + Default: getResourceList("50m", "500Mi"), + DefaultRequest: getResourceList("10m", "200Mi"), + MaxLimitRequestRatio: getResourceList("10", ""), + }}, }, - { - name: "thirdparty-fields-all-valid-storage-resources", - spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: "thirdparty.com/foo", - Max: getStorageResourceList("10000T"), - Min: getStorageResourceList("100Mi"), - Default: getStorageResourceList("500Mi"), - DefaultRequest: getStorageResourceList("200Mi"), - MaxLimitRequestRatio: getStorageResourceList(""), - }, - }, - }, + }, { + name: "thirdparty-fields-all-valid-storage-resources", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{{ + Type: "thirdparty.com/foo", + Max: getStorageResourceList("10000T"), + Min: getStorageResourceList("100Mi"), + Default: getStorageResourceList("500Mi"), + DefaultRequest: getStorageResourceList("200Mi"), + MaxLimitRequestRatio: getStorageResourceList(""), + }}, }, + }, } for _, successCase := range successCases { @@ -19030,156 +17672,131 @@ func TestValidateLimitRange(t *testing.T) { }, "duplicate-limit-type": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePod, - Max: getResourceList("100m", "10000m"), - Min: getResourceList("0m", "100m"), - }, - { - Type: core.LimitTypePod, - Min: getResourceList("0m", "100m"), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePod, + Max: getResourceList("100m", "10000m"), + Min: getResourceList("0m", "100m"), + }, { + Type: core.LimitTypePod, + Min: getResourceList("0m", "100m"), + }}, }}, "", }, "default-limit-type-pod": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePod, - Max: getResourceList("100m", "10000m"), - Min: getResourceList("0m", "100m"), - Default: getResourceList("10m", "100m"), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePod, + Max: getResourceList("100m", "10000m"), + Min: getResourceList("0m", "100m"), + Default: getResourceList("10m", "100m"), + }}, }}, "may not be specified when `type` is 'Pod'", }, "default-request-limit-type-pod": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePod, - Max: getResourceList("100m", "10000m"), - Min: getResourceList("0m", "100m"), - DefaultRequest: getResourceList("10m", "100m"), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePod, + Max: getResourceList("100m", "10000m"), + Min: getResourceList("0m", "100m"), + DefaultRequest: getResourceList("10m", "100m"), + }}, }}, "may not be specified when `type` is 'Pod'", }, "min value 100m is greater than max value 10m": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePod, - Max: getResourceList("10m", ""), - Min: getResourceList("100m", ""), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePod, + Max: getResourceList("10m", ""), + Min: getResourceList("100m", ""), + }}, }}, "min value 100m is greater than max value 10m", }, "invalid spec default outside range": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypeContainer, - Max: getResourceList("1", ""), - Min: getResourceList("100m", ""), - Default: getResourceList("2000m", ""), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypeContainer, + Max: getResourceList("1", ""), + Min: getResourceList("100m", ""), + Default: getResourceList("2000m", ""), + }}, }}, "default value 2 is greater than max value 1", }, "invalid spec default request outside range": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypeContainer, - Max: getResourceList("1", ""), - Min: getResourceList("100m", ""), - DefaultRequest: getResourceList("2000m", ""), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypeContainer, + Max: getResourceList("1", ""), + Min: getResourceList("100m", ""), + DefaultRequest: getResourceList("2000m", ""), + }}, }}, "default request value 2 is greater than max value 1", }, "invalid spec default request more than default": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypeContainer, - Max: getResourceList("2", ""), - Min: getResourceList("100m", ""), - Default: getResourceList("500m", ""), - DefaultRequest: getResourceList("800m", ""), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypeContainer, + Max: getResourceList("2", ""), + Min: getResourceList("100m", ""), + Default: getResourceList("500m", ""), + DefaultRequest: getResourceList("800m", ""), + }}, }}, "default request value 800m is greater than default limit value 500m", }, "invalid spec maxLimitRequestRatio less than 1": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePod, - MaxLimitRequestRatio: getResourceList("800m", ""), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePod, + MaxLimitRequestRatio: getResourceList("800m", ""), + }}, }}, "ratio 800m is less than 1", }, "invalid spec maxLimitRequestRatio greater than max/min": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypeContainer, - Max: getResourceList("", "2Gi"), - Min: getResourceList("", "512Mi"), - MaxLimitRequestRatio: getResourceList("", "10"), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypeContainer, + Max: getResourceList("", "2Gi"), + Min: getResourceList("", "512Mi"), + MaxLimitRequestRatio: getResourceList("", "10"), + }}, }}, "ratio 10 is greater than max/min = 4.000000", }, "invalid non standard limit type": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: "foo", - Max: getStorageResourceList("10000T"), - Min: getStorageResourceList("100Mi"), - Default: getStorageResourceList("500Mi"), - DefaultRequest: getStorageResourceList("200Mi"), - MaxLimitRequestRatio: getStorageResourceList(""), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: "foo", + Max: getStorageResourceList("10000T"), + Min: getStorageResourceList("100Mi"), + Default: getStorageResourceList("500Mi"), + DefaultRequest: getStorageResourceList("200Mi"), + MaxLimitRequestRatio: getStorageResourceList(""), + }}, }}, "must be a standard limit type or fully qualified", }, "min and max values missing, one required": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePersistentVolumeClaim, - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePersistentVolumeClaim, + }}, }}, "either minimum or maximum storage value is required, but neither was provided", }, "invalid min greater than max": { core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ - Limits: []core.LimitRangeItem{ - { - Type: core.LimitTypePersistentVolumeClaim, - Min: getStorageResourceList("10Gi"), - Max: getStorageResourceList("1Gi"), - }, - }, + Limits: []core.LimitRangeItem{{ + Type: core.LimitTypePersistentVolumeClaim, + Min: getStorageResourceList("10Gi"), + Max: getStorageResourceList("1Gi"), + }}, }}, "min value 10Gi is greater than max value 1Gi", }, @@ -19427,13 +18044,11 @@ func TestValidateResourceQuota(t *testing.T) { scopeSelectorSpec := core.ResourceQuotaSpec{ ScopeSelector: &core.ScopeSelector{ - MatchExpressions: []core.ScopedResourceSelectorRequirement{ - { - ScopeName: core.ResourceQuotaScopePriorityClass, - Operator: core.ScopeSelectorOpIn, - Values: []string{"cluster-services"}, - }, - }, + MatchExpressions: []core.ScopedResourceSelectorRequirement{{ + ScopeName: core.ResourceQuotaScopePriorityClass, + Operator: core.ScopeSelectorOpIn, + Values: []string{"cluster-services"}, + }}, }, } @@ -19488,13 +18103,11 @@ func TestValidateResourceQuota(t *testing.T) { invalidCrossNamespaceAffinitySpec := core.ResourceQuotaSpec{ ScopeSelector: &core.ScopeSelector{ - MatchExpressions: []core.ScopedResourceSelectorRequirement{ - { - ScopeName: core.ResourceQuotaScopeCrossNamespacePodAffinity, - Operator: core.ScopeSelectorOpIn, - Values: []string{"cluster-services"}, - }, - }, + MatchExpressions: []core.ScopedResourceSelectorRequirement{{ + ScopeName: core.ResourceQuotaScopeCrossNamespacePodAffinity, + Operator: core.ScopeSelectorOpIn, + Values: []string{"cluster-services"}, + }}, }, } @@ -19648,16 +18261,14 @@ func TestValidateResourceQuota(t *testing.T) { func TestValidateNamespace(t *testing.T) { validLabels := map[string]string{"a": "b"} invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} - successCases := []core.Namespace{ - { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: validLabels}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, - Spec: core.NamespaceSpec{ - Finalizers: []core.FinalizerName{"example.com/something", "example.com/other"}, - }, + successCases := []core.Namespace{{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: validLabels}, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, + Spec: core.NamespaceSpec{ + Finalizers: []core.FinalizerName{"example.com/something", "example.com/other"}, }, + }, } for _, successCase := range successCases { if errs := ValidateNamespace(&successCase); len(errs) != 0 { @@ -20043,49 +18654,42 @@ func TestValidateSecretUpdate(t *testing.T) { oldSecret core.Secret newSecret core.Secret valid bool - }{ - { - name: "mark secret immutable", - oldSecret: secret, - newSecret: immutableSecret, - valid: true, - }, - { - name: "revert immutable secret", - oldSecret: immutableSecret, - newSecret: secret, - valid: false, - }, - { - name: "makr immutable secret mutable", - oldSecret: immutableSecret, - newSecret: mutableSecret, - valid: false, - }, - { - name: "add data in secret", - oldSecret: secret, - newSecret: secretWithData, - valid: true, - }, - { - name: "add data in immutable secret", - oldSecret: immutableSecret, - newSecret: immutableSecretWithData, - valid: false, - }, - { - name: "change data in secret", - oldSecret: secret, - newSecret: secretWithChangedData, - valid: true, - }, - { - name: "change data in immutable secret", - oldSecret: immutableSecret, - newSecret: immutableSecretWithChangedData, - valid: false, - }, + }{{ + name: "mark secret immutable", + oldSecret: secret, + newSecret: immutableSecret, + valid: true, + }, { + name: "revert immutable secret", + oldSecret: immutableSecret, + newSecret: secret, + valid: false, + }, { + name: "makr immutable secret mutable", + oldSecret: immutableSecret, + newSecret: mutableSecret, + valid: false, + }, { + name: "add data in secret", + oldSecret: secret, + newSecret: secretWithData, + valid: true, + }, { + name: "add data in immutable secret", + oldSecret: immutableSecret, + newSecret: immutableSecretWithData, + valid: false, + }, { + name: "change data in secret", + oldSecret: secret, + newSecret: secretWithChangedData, + valid: true, + }, { + name: "change data in immutable secret", + oldSecret: immutableSecret, + newSecret: immutableSecretWithChangedData, + valid: false, + }, } for _, tc := range tests { @@ -20241,16 +18845,13 @@ func TestValidateEndpointsCreate(t *testing.T) { "simple endpoint": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}}, - Ports: []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}}, - }, - { - Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}}, - Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}}, + }, { + Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}}, + }}, }, }, "empty subsets": { @@ -20261,33 +18862,27 @@ func TestValidateEndpointsCreate(t *testing.T) { "no name required for singleton port": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}}, + }}, }, }, "valid appProtocol": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("HTTP")}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("HTTP")}}, + }}, }, }, "empty ports": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}}, + }}, }, }, } @@ -20328,23 +18923,19 @@ func TestValidateEndpointsCreate(t *testing.T) { "empty addresses": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, + }}, }, errorType: "FieldValueRequired", }, "invalid IP": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "[2001:0db8:85a3:0042:1000:8a2e:0370:7334]"}}, - Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "[2001:0db8:85a3:0042:1000:8a2e:0370:7334]"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, + }}, }, errorType: "FieldValueInvalid", errorDetail: "must be a valid IP address", @@ -20352,24 +18943,20 @@ func TestValidateEndpointsCreate(t *testing.T) { "Multiple ports, one without name": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}}, + }}, }, errorType: "FieldValueRequired", }, "Invalid port number": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []core.EndpointPort{{Name: "a", Port: 66000, Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 66000, Protocol: "TCP"}}, + }}, }, errorType: "FieldValueInvalid", errorDetail: "between", @@ -20377,24 +18964,20 @@ func TestValidateEndpointsCreate(t *testing.T) { "Invalid protocol": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "Protocol"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "Protocol"}}, + }}, }, errorType: "FieldValueNotSupported", }, "Address missing IP": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{}}, - Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{}}, + Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, + }}, }, errorType: "FieldValueInvalid", errorDetail: "must be a valid IP address", @@ -20402,12 +18985,10 @@ func TestValidateEndpointsCreate(t *testing.T) { "Port missing number": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []core.EndpointPort{{Name: "a", Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Name: "a", Protocol: "TCP"}}, + }}, }, errorType: "FieldValueInvalid", errorDetail: "between", @@ -20415,24 +18996,20 @@ func TestValidateEndpointsCreate(t *testing.T) { "Port missing protocol": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []core.EndpointPort{{Name: "a", Port: 93}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 93}}, + }}, }, errorType: "FieldValueRequired", }, "Address is loopback": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "127.0.0.1"}}, - Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, + }}, }, errorType: "FieldValueInvalid", errorDetail: "loopback", @@ -20440,12 +19017,10 @@ func TestValidateEndpointsCreate(t *testing.T) { "Address is link-local": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "169.254.169.254"}}, - Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "169.254.169.254"}}, + Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, + }}, }, errorType: "FieldValueInvalid", errorDetail: "link-local", @@ -20453,12 +19028,10 @@ func TestValidateEndpointsCreate(t *testing.T) { "Address is link-local multicast": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "224.0.0.1"}}, - Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "224.0.0.1"}}, + Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, + }}, }, errorType: "FieldValueInvalid", errorDetail: "link-local multicast", @@ -20466,12 +19039,10 @@ func TestValidateEndpointsCreate(t *testing.T) { "Invalid AppProtocol": { endpoints: core.Endpoints{ ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []core.EndpointSubset{ - { - Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP", AppProtocol: utilpointer.String("lots-of[invalid]-{chars}")}}, - }, - }, + Subsets: []core.EndpointSubset{{ + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP", AppProtocol: utilpointer.String("lots-of[invalid]-{chars}")}}, + }}, }, errorType: "FieldValueInvalid", errorDetail: "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character", @@ -20543,26 +19114,23 @@ func TestValidateWindowsSecurityContext(t *testing.T) { expectError bool errorMsg string errorType field.ErrorType - }{ - { - name: "pod with SELinux Options", - sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}}}}, - expectError: true, - errorMsg: "cannot be set for a windows pod", - errorType: "FieldValueForbidden", - }, - { - name: "pod with SeccompProfile", - sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SeccompProfile: &core.SeccompProfile{LocalhostProfile: utilpointer.String("dummy")}}}}}, - expectError: true, - errorMsg: "cannot be set for a windows pod", - errorType: "FieldValueForbidden", - }, - { - name: "pod with WindowsOptions, no error", - sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}}}}, - expectError: false, - }, + }{{ + name: "pod with SELinux Options", + sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}}}}, + expectError: true, + errorMsg: "cannot be set for a windows pod", + errorType: "FieldValueForbidden", + }, { + name: "pod with SeccompProfile", + sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SeccompProfile: &core.SeccompProfile{LocalhostProfile: utilpointer.String("dummy")}}}}}, + expectError: true, + errorMsg: "cannot be set for a windows pod", + errorType: "FieldValueForbidden", + }, { + name: "pod with WindowsOptions, no error", + sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}}}}, + expectError: false, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -20776,64 +19344,58 @@ func TestValidateSchedulingGates(t *testing.T) { name string schedulingGates []core.PodSchedulingGate wantFieldErrors field.ErrorList - }{ - { - name: "nil gates", - schedulingGates: nil, - wantFieldErrors: field.ErrorList{}, + }{{ + name: "nil gates", + schedulingGates: nil, + wantFieldErrors: field.ErrorList{}, + }, { + name: "empty string in gates", + schedulingGates: []core.PodSchedulingGate{ + {Name: "foo"}, + {Name: ""}, }, - { - name: "empty string in gates", - schedulingGates: []core.PodSchedulingGate{ - {Name: "foo"}, - {Name: ""}, - }, - wantFieldErrors: field.ErrorList{ - field.Invalid(fieldPath.Index(1), "", "name part must be non-empty"), - field.Invalid(fieldPath.Index(1), "", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')"), - }, + wantFieldErrors: field.ErrorList{ + field.Invalid(fieldPath.Index(1), "", "name part must be non-empty"), + field.Invalid(fieldPath.Index(1), "", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')"), }, - { - name: "legal gates", - schedulingGates: []core.PodSchedulingGate{ - {Name: "foo"}, - {Name: "bar"}, - }, - wantFieldErrors: field.ErrorList{}, + }, { + name: "legal gates", + schedulingGates: []core.PodSchedulingGate{ + {Name: "foo"}, + {Name: "bar"}, }, - { - name: "illegal gates", - schedulingGates: []core.PodSchedulingGate{ - {Name: "foo"}, - {Name: "\nbar"}, - }, - wantFieldErrors: []*field.Error{field.Invalid(fieldPath.Index(1), "\nbar", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")}, + wantFieldErrors: field.ErrorList{}, + }, { + name: "illegal gates", + schedulingGates: []core.PodSchedulingGate{ + {Name: "foo"}, + {Name: "\nbar"}, }, - { - name: "duplicated gates (single duplication)", - schedulingGates: []core.PodSchedulingGate{ - {Name: "foo"}, - {Name: "bar"}, - {Name: "bar"}, - }, - wantFieldErrors: []*field.Error{field.Duplicate(fieldPath.Index(2), "bar")}, + wantFieldErrors: []*field.Error{field.Invalid(fieldPath.Index(1), "\nbar", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")}, + }, { + name: "duplicated gates (single duplication)", + schedulingGates: []core.PodSchedulingGate{ + {Name: "foo"}, + {Name: "bar"}, + {Name: "bar"}, }, - { - name: "duplicated gates (multiple duplications)", - schedulingGates: []core.PodSchedulingGate{ - {Name: "foo"}, - {Name: "bar"}, - {Name: "foo"}, - {Name: "baz"}, - {Name: "foo"}, - {Name: "bar"}, - }, - wantFieldErrors: field.ErrorList{ - field.Duplicate(fieldPath.Index(2), "foo"), - field.Duplicate(fieldPath.Index(4), "foo"), - field.Duplicate(fieldPath.Index(5), "bar"), - }, + wantFieldErrors: []*field.Error{field.Duplicate(fieldPath.Index(2), "bar")}, + }, { + name: "duplicated gates (multiple duplications)", + schedulingGates: []core.PodSchedulingGate{ + {Name: "foo"}, + {Name: "bar"}, + {Name: "foo"}, + {Name: "baz"}, + {Name: "foo"}, + {Name: "bar"}, }, + wantFieldErrors: field.ErrorList{ + field.Duplicate(fieldPath.Index(2), "foo"), + field.Duplicate(fieldPath.Index(4), "foo"), + field.Duplicate(fieldPath.Index(5), "bar"), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -21224,61 +19786,52 @@ func TestValidateConfigMapUpdate(t *testing.T) { newCfg core.ConfigMap oldCfg core.ConfigMap valid bool - }{ - { - name: "valid", - newCfg: configMap, - oldCfg: configMap, - valid: true, - }, - { - name: "invalid", - newCfg: noVersion, - oldCfg: configMap, - valid: false, - }, - { - name: "mark configmap immutable", - oldCfg: configMap, - newCfg: immutableConfigMap, - valid: true, - }, - { - name: "revert immutable configmap", - oldCfg: immutableConfigMap, - newCfg: configMap, - valid: false, - }, - { - name: "mark immutable configmap mutable", - oldCfg: immutableConfigMap, - newCfg: mutableConfigMap, - valid: false, - }, - { - name: "add data in configmap", - oldCfg: configMap, - newCfg: configMapWithData, - valid: true, - }, - { - name: "add data in immutable configmap", - oldCfg: immutableConfigMap, - newCfg: immutableConfigMapWithData, - valid: false, - }, - { - name: "change data in configmap", - oldCfg: configMap, - newCfg: configMapWithChangedData, - valid: true, - }, - { - name: "change data in immutable configmap", - oldCfg: immutableConfigMap, - newCfg: immutableConfigMapWithChangedData, - valid: false, - }, + }{{ + name: "valid", + newCfg: configMap, + oldCfg: configMap, + valid: true, + }, { + name: "invalid", + newCfg: noVersion, + oldCfg: configMap, + valid: false, + }, { + name: "mark configmap immutable", + oldCfg: configMap, + newCfg: immutableConfigMap, + valid: true, + }, { + name: "revert immutable configmap", + oldCfg: immutableConfigMap, + newCfg: configMap, + valid: false, + }, { + name: "mark immutable configmap mutable", + oldCfg: immutableConfigMap, + newCfg: mutableConfigMap, + valid: false, + }, { + name: "add data in configmap", + oldCfg: configMap, + newCfg: configMapWithData, + valid: true, + }, { + name: "add data in immutable configmap", + oldCfg: immutableConfigMap, + newCfg: immutableConfigMapWithData, + valid: false, + }, { + name: "change data in configmap", + oldCfg: configMap, + newCfg: configMapWithChangedData, + valid: true, + }, { + name: "change data in immutable configmap", + oldCfg: immutableConfigMap, + newCfg: immutableConfigMapWithChangedData, + valid: false, + }, } for _, tc := range cases { @@ -21451,15 +20004,13 @@ func newNodeNameEndpoint(nodeName string) *core.Endpoints { Namespace: metav1.NamespaceDefault, ResourceVersion: "1", }, - Subsets: []core.EndpointSubset{ - { - NotReadyAddresses: []core.EndpointAddress{}, - Ports: []core.EndpointPort{{Name: "https", Port: 443, Protocol: "TCP"}}, - Addresses: []core.EndpointAddress{ - { - IP: "8.8.8.8", - Hostname: "zookeeper1", - NodeName: &nodeName}}}}} + Subsets: []core.EndpointSubset{{ + NotReadyAddresses: []core.EndpointAddress{}, + Ports: []core.EndpointPort{{Name: "https", Port: 443, Protocol: "TCP"}}, + Addresses: []core.EndpointAddress{{ + IP: "8.8.8.8", + Hostname: "zookeeper1", + NodeName: &nodeName}}}}} return ep } @@ -21644,171 +20195,146 @@ func TestValidateWindowsSecurityContextOptions(t *testing.T) { windowsOptions *core.WindowsSecurityContextOptions expectedErrorSubstring string - }{ - { - testName: "a nil pointer", + }{{ + testName: "a nil pointer", + }, { + testName: "an empty struct", + windowsOptions: &core.WindowsSecurityContextOptions{}, + }, { + testName: "a valid input", + windowsOptions: &core.WindowsSecurityContextOptions{ + GMSACredentialSpecName: toPtr("dummy-gmsa-crep-spec-name"), + GMSACredentialSpec: toPtr("dummy-gmsa-crep-spec-contents"), }, - { - testName: "an empty struct", - windowsOptions: &core.WindowsSecurityContextOptions{}, + }, { + testName: "a GMSA cred spec name that is not a valid resource name", + windowsOptions: &core.WindowsSecurityContextOptions{ + // invalid because of the underscore + GMSACredentialSpecName: toPtr("not_a-valid-gmsa-crep-spec-name"), }, - { - testName: "a valid input", - windowsOptions: &core.WindowsSecurityContextOptions{ - GMSACredentialSpecName: toPtr("dummy-gmsa-crep-spec-name"), - GMSACredentialSpec: toPtr("dummy-gmsa-crep-spec-contents"), - }, + expectedErrorSubstring: dnsSubdomainLabelErrMsg, + }, { + testName: "empty GMSA cred spec contents", + windowsOptions: &core.WindowsSecurityContextOptions{ + GMSACredentialSpec: toPtr(""), }, - { - testName: "a GMSA cred spec name that is not a valid resource name", - windowsOptions: &core.WindowsSecurityContextOptions{ - // invalid because of the underscore - GMSACredentialSpecName: toPtr("not_a-valid-gmsa-crep-spec-name"), - }, - expectedErrorSubstring: dnsSubdomainLabelErrMsg, + expectedErrorSubstring: "gmsaCredentialSpec cannot be an empty string", + }, { + testName: "GMSA cred spec contents that are too long", + windowsOptions: &core.WindowsSecurityContextOptions{ + GMSACredentialSpec: toPtr(strings.Repeat("a", maxGMSACredentialSpecLength+1)), }, - { - testName: "empty GMSA cred spec contents", - windowsOptions: &core.WindowsSecurityContextOptions{ - GMSACredentialSpec: toPtr(""), - }, - expectedErrorSubstring: "gmsaCredentialSpec cannot be an empty string", + expectedErrorSubstring: "gmsaCredentialSpec size must be under", + }, { + testName: "RunAsUserName is nil", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: nil, }, - { - testName: "GMSA cred spec contents that are too long", - windowsOptions: &core.WindowsSecurityContextOptions{ - GMSACredentialSpec: toPtr(strings.Repeat("a", maxGMSACredentialSpecLength+1)), - }, - expectedErrorSubstring: "gmsaCredentialSpec size must be under", + }, { + testName: "a valid RunAsUserName", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr("Container. User"), }, - { - testName: "RunAsUserName is nil", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: nil, - }, + }, { + testName: "a valid RunAsUserName with NetBios Domain", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr("Network Service\\Container. User"), }, - { - testName: "a valid RunAsUserName", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr("Container. User"), - }, + }, { + testName: "a valid RunAsUserName with DNS Domain", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".liSH\\Container. User"), }, - { - testName: "a valid RunAsUserName with NetBios Domain", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr("Network Service\\Container. User"), - }, + }, { + testName: "a valid RunAsUserName with DNS Domain with a single character segment", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".l\\Container. User"), }, - { - testName: "a valid RunAsUserName with DNS Domain", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".liSH\\Container. User"), - }, + }, { + testName: "a valid RunAsUserName with a long single segment DNS Domain", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr(strings.Repeat("a", 42) + "\\Container. User"), }, - { - testName: "a valid RunAsUserName with DNS Domain with a single character segment", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".l\\Container. User"), - }, + }, { + testName: "an empty RunAsUserName", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr(""), }, - { - testName: "a valid RunAsUserName with a long single segment DNS Domain", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr(strings.Repeat("a", 42) + "\\Container. User"), - }, + expectedErrorSubstring: "runAsUserName cannot be an empty string", + }, { + testName: "RunAsUserName containing a control character", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr("Container\tUser"), }, - { - testName: "an empty RunAsUserName", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr(""), - }, - expectedErrorSubstring: "runAsUserName cannot be an empty string", + expectedErrorSubstring: "runAsUserName cannot contain control characters", + }, { + testName: "RunAsUserName containing too many backslashes", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr("Container\\Foo\\Lish"), }, - { - testName: "RunAsUserName containing a control character", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr("Container\tUser"), - }, - expectedErrorSubstring: "runAsUserName cannot contain control characters", + expectedErrorSubstring: "runAsUserName cannot contain more than one backslash", + }, { + testName: "RunAsUserName containing backslash but empty Domain", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr("\\User"), }, - { - testName: "RunAsUserName containing too many backslashes", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr("Container\\Foo\\Lish"), - }, - expectedErrorSubstring: "runAsUserName cannot contain more than one backslash", + expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format", + }, { + testName: "RunAsUserName containing backslash but empty User", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr("Container\\"), }, - { - testName: "RunAsUserName containing backslash but empty Domain", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr("\\User"), - }, - expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format", + expectedErrorSubstring: "runAsUserName's User cannot be empty", + }, { + testName: "RunAsUserName's NetBios Domain is too long", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr("NetBios " + strings.Repeat("a", 8) + "\\user"), }, - { - testName: "RunAsUserName containing backslash but empty User", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr("Container\\"), - }, - expectedErrorSubstring: "runAsUserName's User cannot be empty", + expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios", + }, { + testName: "RunAsUserName's DNS Domain is too long", + windowsOptions: &core.WindowsSecurityContextOptions{ + // even if this tests the max Domain length, the Domain should still be "valid". + RunAsUserName: toPtr(strings.Repeat(strings.Repeat("a", 63)+".", 4)[:253] + ".com\\user"), }, - { - testName: "RunAsUserName's NetBios Domain is too long", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr("NetBios " + strings.Repeat("a", 8) + "\\user"), - }, - expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios", + expectedErrorSubstring: "runAsUserName's Domain length must be under", + }, { + testName: "RunAsUserName's User is too long", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr(strings.Repeat("a", maxRunAsUserNameUserLength+1)), }, - { - testName: "RunAsUserName's DNS Domain is too long", - windowsOptions: &core.WindowsSecurityContextOptions{ - // even if this tests the max Domain length, the Domain should still be "valid". - RunAsUserName: toPtr(strings.Repeat(strings.Repeat("a", 63)+".", 4)[:253] + ".com\\user"), - }, - expectedErrorSubstring: "runAsUserName's Domain length must be under", + expectedErrorSubstring: "runAsUserName's User length must not be longer than", + }, { + testName: "RunAsUserName's User cannot contain only spaces or periods", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr("... ..."), }, - { - testName: "RunAsUserName's User is too long", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr(strings.Repeat("a", maxRunAsUserNameUserLength+1)), - }, - expectedErrorSubstring: "runAsUserName's User length must not be longer than", + expectedErrorSubstring: "runAsUserName's User cannot contain only periods or spaces", + }, { + testName: "RunAsUserName's NetBios Domain cannot start with a dot", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr(".FooLish\\User"), }, - { - testName: "RunAsUserName's User cannot contain only spaces or periods", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr("... ..."), - }, - expectedErrorSubstring: "runAsUserName's User cannot contain only periods or spaces", + expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios", + }, { + testName: "RunAsUserName's NetBios Domain cannot contain invalid characters", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr("Foo? Lish?\\User"), }, - { - testName: "RunAsUserName's NetBios Domain cannot start with a dot", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr(".FooLish\\User"), - }, - expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios", + expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios", + }, { + testName: "RunAsUserName's DNS Domain cannot contain invalid characters", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr(strings.Repeat("a", 32) + ".com-\\user"), }, - { - testName: "RunAsUserName's NetBios Domain cannot contain invalid characters", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr("Foo? Lish?\\User"), - }, - expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios", - }, - { - testName: "RunAsUserName's DNS Domain cannot contain invalid characters", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr(strings.Repeat("a", 32) + ".com-\\user"), - }, - expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format", - }, - { - testName: "RunAsUserName's User cannot contain invalid characters", - windowsOptions: &core.WindowsSecurityContextOptions{ - RunAsUserName: toPtr("Container/User"), - }, - expectedErrorSubstring: "runAsUserName's User cannot contain the following characters", + expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format", + }, { + testName: "RunAsUserName's User cannot contain invalid characters", + windowsOptions: &core.WindowsSecurityContextOptions{ + RunAsUserName: toPtr("Container/User"), }, + expectedErrorSubstring: "runAsUserName's User cannot contain the following characters", + }, } for _, testCase := range testCases { @@ -21863,34 +20389,28 @@ func TestAlphaVolumePVCDataSource(t *testing.T) { testName string claimSpec core.PersistentVolumeClaimSpec expectedFail bool - }{ - { - testName: "test create from valid snapshot source", - claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"), - }, - { - testName: "test create from valid pvc source", - claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""), - }, - { - testName: "test missing name in snapshot datasource should fail", - claimSpec: *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"), - expectedFail: true, - }, - { - testName: "test missing kind in snapshot datasource should fail", - claimSpec: *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"), - expectedFail: true, - }, - { - testName: "test create from valid generic custom resource source", - claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"), - }, - { - testName: "test invalid datasource should fail", - claimSpec: *testDataSourceInSpec("test_pod", "Pod", ""), - expectedFail: true, - }, + }{{ + testName: "test create from valid snapshot source", + claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"), + }, { + testName: "test create from valid pvc source", + claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""), + }, { + testName: "test missing name in snapshot datasource should fail", + claimSpec: *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"), + expectedFail: true, + }, { + testName: "test missing kind in snapshot datasource should fail", + claimSpec: *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"), + expectedFail: true, + }, { + testName: "test create from valid generic custom resource source", + claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"), + }, { + testName: "test invalid datasource should fail", + claimSpec: *testDataSourceInSpec("test_pod", "Pod", ""), + expectedFail: true, + }, } for _, tc := range testCases { @@ -21913,34 +20433,28 @@ func testAnyDataSource(t *testing.T, ds, dsRef bool) { testName string claimSpec core.PersistentVolumeClaimSpec expectedFail bool - }{ - { - testName: "test create from valid snapshot source", - claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"), - }, - { - testName: "test create from valid pvc source", - claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""), - }, - { - testName: "test missing name in snapshot datasource should fail", - claimSpec: *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"), - expectedFail: true, - }, - { - testName: "test missing kind in snapshot datasource should fail", - claimSpec: *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"), - expectedFail: true, - }, - { - testName: "test create from valid generic custom resource source", - claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"), - }, - { - testName: "test invalid datasource should fail", - claimSpec: *testDataSourceInSpec("test_pod", "Pod", ""), - expectedFail: true, - }, + }{{ + testName: "test create from valid snapshot source", + claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"), + }, { + testName: "test create from valid pvc source", + claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""), + }, { + testName: "test missing name in snapshot datasource should fail", + claimSpec: *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"), + expectedFail: true, + }, { + testName: "test missing kind in snapshot datasource should fail", + claimSpec: *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"), + expectedFail: true, + }, { + testName: "test create from valid generic custom resource source", + claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"), + }, { + testName: "test invalid datasource should fail", + claimSpec: *testDataSourceInSpec("test_pod", "Pod", ""), + expectedFail: true, + }, } for _, tc := range testCases { @@ -22018,47 +20532,39 @@ func TestCrossNamespaceSource(t *testing.T) { testName string expectedFail bool claimSpec *core.PersistentVolumeClaimSpec - }{ - { - testName: "Feature gate enabled and valid xns DataSourceRef specified", - expectedFail: false, - claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, false), - }, - { - testName: "Feature gate enabled and xns DataSourceRef with PVC source specified", - expectedFail: false, - claimSpec: pvcSpecWithCrossNamespaceSource(&coreAPIGroup, pvcKind, &goodNS, goodName, false), - }, - { - testName: "Feature gate enabled and xns DataSourceRef with unsupported source specified", - expectedFail: false, - claimSpec: pvcSpecWithCrossNamespaceSource(&unsupportedAPIGroup, "UnsupportedKind", &goodNS, goodName, false), - }, - { - testName: "Feature gate enabled and xns DataSourceRef with nil apiGroup", - expectedFail: true, - claimSpec: pvcSpecWithCrossNamespaceSource(nil, "UnsupportedKind", &goodNS, goodName, false), - }, - { - testName: "Feature gate enabled and xns DataSourceRef with invalid namspace specified", - expectedFail: true, - claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &badNS, goodName, false), - }, - { - testName: "Feature gate enabled and xns DataSourceRef with nil namspace specified", - expectedFail: false, - claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, nil, goodName, false), - }, - { - testName: "Feature gate enabled and xns DataSourceRef with empty namspace specified", - expectedFail: false, - claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &emptyNS, goodName, false), - }, - { - testName: "Feature gate enabled and both xns DataSourceRef and DataSource specified", - expectedFail: true, - claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, true), - }, + }{{ + testName: "Feature gate enabled and valid xns DataSourceRef specified", + expectedFail: false, + claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, false), + }, { + testName: "Feature gate enabled and xns DataSourceRef with PVC source specified", + expectedFail: false, + claimSpec: pvcSpecWithCrossNamespaceSource(&coreAPIGroup, pvcKind, &goodNS, goodName, false), + }, { + testName: "Feature gate enabled and xns DataSourceRef with unsupported source specified", + expectedFail: false, + claimSpec: pvcSpecWithCrossNamespaceSource(&unsupportedAPIGroup, "UnsupportedKind", &goodNS, goodName, false), + }, { + testName: "Feature gate enabled and xns DataSourceRef with nil apiGroup", + expectedFail: true, + claimSpec: pvcSpecWithCrossNamespaceSource(nil, "UnsupportedKind", &goodNS, goodName, false), + }, { + testName: "Feature gate enabled and xns DataSourceRef with invalid namspace specified", + expectedFail: true, + claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &badNS, goodName, false), + }, { + testName: "Feature gate enabled and xns DataSourceRef with nil namspace specified", + expectedFail: false, + claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, nil, goodName, false), + }, { + testName: "Feature gate enabled and xns DataSourceRef with empty namspace specified", + expectedFail: false, + claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &emptyNS, goodName, false), + }, { + testName: "Feature gate enabled and both xns DataSourceRef and DataSource specified", + expectedFail: true, + claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, true), + }, } for _, tc := range testCases { @@ -22098,262 +20604,210 @@ func TestValidateTopologySpreadConstraints(t *testing.T) { constraints []core.TopologySpreadConstraint wantFieldErrors field.ErrorList opts PodValidationOptions - }{ - { - name: "all required fields ok", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.DoNotSchedule, - MinDomains: utilpointer.Int32(3), - }, - }, - wantFieldErrors: field.ErrorList{}, + }{{ + name: "all required fields ok", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.DoNotSchedule, + MinDomains: utilpointer.Int32(3), + }}, + wantFieldErrors: field.ErrorList{}, + }, { + name: "missing MaxSkew", + constraints: []core.TopologySpreadConstraint{ + {TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, }, - { - name: "missing MaxSkew", - constraints: []core.TopologySpreadConstraint{ - {TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, - }, - wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(0), isNotPositiveErrorMsg)}, + wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(0), isNotPositiveErrorMsg)}, + }, { + name: "negative MaxSkew", + constraints: []core.TopologySpreadConstraint{ + {MaxSkew: -1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, }, - { - name: "negative MaxSkew", - constraints: []core.TopologySpreadConstraint{ - {MaxSkew: -1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, - }, - wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(-1), isNotPositiveErrorMsg)}, + wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(-1), isNotPositiveErrorMsg)}, + }, { + name: "can use MinDomains with ScheduleAnyway, when MinDomains = nil", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.ScheduleAnyway, + MinDomains: nil, + }}, + wantFieldErrors: field.ErrorList{}, + }, { + name: "negative minDomains is invalid", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.DoNotSchedule, + MinDomains: utilpointer.Int32(-1), + }}, + wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg)}, + }, { + name: "cannot use non-nil MinDomains with ScheduleAnyway", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.ScheduleAnyway, + MinDomains: utilpointer.Int32(10), + }}, + wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(10), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway)))}, + }, { + name: "use negative MinDomains with ScheduleAnyway(invalid)", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.ScheduleAnyway, + MinDomains: utilpointer.Int32(-1), + }}, + wantFieldErrors: []*field.Error{ + field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg), + field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway))), }, - { - name: "can use MinDomains with ScheduleAnyway, when MinDomains = nil", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.ScheduleAnyway, - MinDomains: nil, - }, - }, - wantFieldErrors: field.ErrorList{}, + }, { + name: "missing TopologyKey", + constraints: []core.TopologySpreadConstraint{ + {MaxSkew: 1, WhenUnsatisfiable: core.DoNotSchedule}, }, - { - name: "negative minDomains is invalid", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.DoNotSchedule, - MinDomains: utilpointer.Int32(-1), - }, - }, - wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg)}, + wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")}, + }, { + name: "missing scheduling mode", + constraints: []core.TopologySpreadConstraint{ + {MaxSkew: 1, TopologyKey: "k8s.io/zone"}, }, - { - name: "cannot use non-nil MinDomains with ScheduleAnyway", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.ScheduleAnyway, - MinDomains: utilpointer.Int32(10), - }, - }, - wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(10), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway)))}, + wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction(""), supportedScheduleActions.List())}, + }, { + name: "unsupported scheduling mode", + constraints: []core.TopologySpreadConstraint{ + {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.UnsatisfiableConstraintAction("N/A")}, }, - { - name: "use negative MinDomains with ScheduleAnyway(invalid)", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.ScheduleAnyway, - MinDomains: utilpointer.Int32(-1), - }, - }, - wantFieldErrors: []*field.Error{ - field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg), - field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway))), - }, + wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction("N/A"), supportedScheduleActions.List())}, + }, { + name: "multiple constraints ok with all required fields", + constraints: []core.TopologySpreadConstraint{ + {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, + {MaxSkew: 2, TopologyKey: "k8s.io/node", WhenUnsatisfiable: core.ScheduleAnyway}, }, - { - name: "missing TopologyKey", - constraints: []core.TopologySpreadConstraint{ - {MaxSkew: 1, WhenUnsatisfiable: core.DoNotSchedule}, - }, - wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")}, + wantFieldErrors: field.ErrorList{}, + }, { + name: "multiple constraints missing TopologyKey on partial ones", + constraints: []core.TopologySpreadConstraint{ + {MaxSkew: 1, WhenUnsatisfiable: core.ScheduleAnyway}, + {MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, }, - { - name: "missing scheduling mode", - constraints: []core.TopologySpreadConstraint{ - {MaxSkew: 1, TopologyKey: "k8s.io/zone"}, - }, - wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction(""), supportedScheduleActions.List())}, + wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")}, + }, { + name: "duplicate constraints", + constraints: []core.TopologySpreadConstraint{ + {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, + {MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, }, - { - name: "unsupported scheduling mode", - constraints: []core.TopologySpreadConstraint{ - {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.UnsatisfiableConstraintAction("N/A")}, - }, - wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction("N/A"), supportedScheduleActions.List())}, + wantFieldErrors: []*field.Error{ + field.Duplicate(fieldPathTopologyKeyAndWhenUnsatisfiable, fmt.Sprintf("{%v, %v}", "k8s.io/zone", core.DoNotSchedule)), }, - { - name: "multiple constraints ok with all required fields", - constraints: []core.TopologySpreadConstraint{ - {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, - {MaxSkew: 2, TopologyKey: "k8s.io/node", WhenUnsatisfiable: core.ScheduleAnyway}, - }, - wantFieldErrors: field.ErrorList{}, + }, { + name: "supported policy name set on NodeAffinityPolicy and NodeTaintsPolicy", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.DoNotSchedule, + NodeAffinityPolicy: &honor, + NodeTaintsPolicy: &ignore, + }}, + wantFieldErrors: []*field.Error{}, + }, { + name: "unsupported policy name set on NodeAffinityPolicy", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.DoNotSchedule, + NodeAffinityPolicy: &unknown, + NodeTaintsPolicy: &ignore, + }}, + wantFieldErrors: []*field.Error{ + field.NotSupported(nodeAffinityField, &unknown, supportedPodTopologySpreadNodePolicies.List()), }, - { - name: "multiple constraints missing TopologyKey on partial ones", - constraints: []core.TopologySpreadConstraint{ - {MaxSkew: 1, WhenUnsatisfiable: core.ScheduleAnyway}, - {MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, - }, - wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")}, + }, { + name: "unsupported policy name set on NodeTaintsPolicy", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.DoNotSchedule, + NodeAffinityPolicy: &honor, + NodeTaintsPolicy: &unknown, + }}, + wantFieldErrors: []*field.Error{ + field.NotSupported(nodeTaintsField, &unknown, supportedPodTopologySpreadNodePolicies.List()), }, - { - name: "duplicate constraints", - constraints: []core.TopologySpreadConstraint{ - {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, - {MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, + }, { + name: "key in MatchLabelKeys isn't correctly defined", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + LabelSelector: &metav1.LabelSelector{}, + WhenUnsatisfiable: core.DoNotSchedule, + MatchLabelKeys: []string{"/simple"}, + }}, + wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "/simple", "prefix part must be non-empty")}, + }, { + name: "key exists in both matchLabelKeys and labelSelector", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.DoNotSchedule, + MatchLabelKeys: []string{"foo"}, + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "foo", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value1", "value2"}, + }}, }, - wantFieldErrors: []*field.Error{ - field.Duplicate(fieldPathTopologyKeyAndWhenUnsatisfiable, fmt.Sprintf("{%v, %v}", "k8s.io/zone", core.DoNotSchedule)), - }, - }, - { - name: "supported policy name set on NodeAffinityPolicy and NodeTaintsPolicy", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.DoNotSchedule, - NodeAffinityPolicy: &honor, - NodeTaintsPolicy: &ignore, - }, - }, - wantFieldErrors: []*field.Error{}, - }, - { - name: "unsupported policy name set on NodeAffinityPolicy", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.DoNotSchedule, - NodeAffinityPolicy: &unknown, - NodeTaintsPolicy: &ignore, - }, - }, - wantFieldErrors: []*field.Error{ - field.NotSupported(nodeAffinityField, &unknown, supportedPodTopologySpreadNodePolicies.List()), - }, - }, - { - name: "unsupported policy name set on NodeTaintsPolicy", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.DoNotSchedule, - NodeAffinityPolicy: &honor, - NodeTaintsPolicy: &unknown, - }, - }, - wantFieldErrors: []*field.Error{ - field.NotSupported(nodeTaintsField, &unknown, supportedPodTopologySpreadNodePolicies.List()), - }, - }, - { - name: "key in MatchLabelKeys isn't correctly defined", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - LabelSelector: &metav1.LabelSelector{}, - WhenUnsatisfiable: core.DoNotSchedule, - MatchLabelKeys: []string{"/simple"}, - }, - }, - wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "/simple", "prefix part must be non-empty")}, - }, - { - name: "key exists in both matchLabelKeys and labelSelector", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.DoNotSchedule, - MatchLabelKeys: []string{"foo"}, - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - }, - }, - wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "foo", "exists in both matchLabelKeys and labelSelector")}, - }, - { - name: "key in MatchLabelKeys is forbidden to be specified when labelSelector is not set", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.DoNotSchedule, - MatchLabelKeys: []string{"foo"}, - }, - }, - wantFieldErrors: field.ErrorList{field.Forbidden(fieldPathMatchLabelKeys, "must not be specified when labelSelector is not set")}, - }, - { - name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.DoNotSchedule, - MinDomains: nil, - LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}}, - }, - }, - wantFieldErrors: []*field.Error{field.Invalid(labelSelectorField.Child("matchLabels"), "NoUppercaseOrSpecialCharsLike=Equals", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")}, - opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false}, - }, - { - name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is true", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.DoNotSchedule, - MinDomains: nil, - LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}}, - }, - }, - wantFieldErrors: []*field.Error{}, - opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: true}, - }, - { - name: "valid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false", - constraints: []core.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "k8s.io/zone", - WhenUnsatisfiable: core.DoNotSchedule, - MinDomains: nil, - LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "foo"}}, - }, - }, - wantFieldErrors: []*field.Error{}, - opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false}, - }, + }}, + wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "foo", "exists in both matchLabelKeys and labelSelector")}, + }, { + name: "key in MatchLabelKeys is forbidden to be specified when labelSelector is not set", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.DoNotSchedule, + MatchLabelKeys: []string{"foo"}, + }}, + wantFieldErrors: field.ErrorList{field.Forbidden(fieldPathMatchLabelKeys, "must not be specified when labelSelector is not set")}, + }, { + name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.DoNotSchedule, + MinDomains: nil, + LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}}, + }}, + wantFieldErrors: []*field.Error{field.Invalid(labelSelectorField.Child("matchLabels"), "NoUppercaseOrSpecialCharsLike=Equals", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")}, + opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false}, + }, { + name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is true", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.DoNotSchedule, + MinDomains: nil, + LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}}, + }}, + wantFieldErrors: []*field.Error{}, + opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: true}, + }, { + name: "valid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false", + constraints: []core.TopologySpreadConstraint{{ + MaxSkew: 1, + TopologyKey: "k8s.io/zone", + WhenUnsatisfiable: core.DoNotSchedule, + MinDomains: nil, + LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "foo"}}, + }}, + wantFieldErrors: []*field.Error{}, + opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false}, + }, } for _, tc := range testCases { @@ -22370,14 +20824,13 @@ func TestValidateOverhead(t *testing.T) { successCase := []struct { Name string overhead core.ResourceList - }{ - { - Name: "Valid Overhead for CPU + Memory", - overhead: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - }, + }{{ + Name: "Valid Overhead for CPU + Memory", + overhead: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), }, + }, } for _, tc := range successCase { if errs := validateOverhead(tc.overhead, field.NewPath("overheads"), PodValidationOptions{}); len(errs) != 0 { @@ -22388,13 +20841,12 @@ func TestValidateOverhead(t *testing.T) { errorCase := []struct { Name string overhead core.ResourceList - }{ - { - Name: "Invalid Overhead Resources", - overhead: core.ResourceList{ - core.ResourceName("my.org"): resource.MustParse("10m"), - }, + }{{ + Name: "Invalid Overhead Resources", + overhead: core.ResourceList{ + core.ResourceName("my.org"): resource.MustParse("10m"), }, + }, } for _, tc := range errorCase { if errs := validateOverhead(tc.overhead, field.NewPath("resources"), PodValidationOptions{}); len(errs) == 0 { @@ -22408,11 +20860,9 @@ func makePod(podName string, podNamespace string, podIPs []core.PodIP) core.Pod return core.Pod{ ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace}, Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - }, - }, + Containers: []core.Container{{ + Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + }}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, }, @@ -22425,49 +20875,39 @@ func TestPodIPsValidation(t *testing.T) { testCases := []struct { pod core.Pod expectError bool - }{ - { - expectError: false, - pod: makePod("nil-ips", "ns", nil), - }, - { - expectError: false, - pod: makePod("empty-podips-list", "ns", []core.PodIP{}), - }, - { - expectError: false, - pod: makePod("single-ip-family-6", "ns", []core.PodIP{{IP: "::1"}}), - }, - { - expectError: false, - pod: makePod("single-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}}), - }, - { - expectError: false, - pod: makePod("dual-stack-4-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}), - }, - { - expectError: false, - pod: makePod("dual-stack-6-4", "ns", []core.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}), - }, + }{{ + expectError: false, + pod: makePod("nil-ips", "ns", nil), + }, { + expectError: false, + pod: makePod("empty-podips-list", "ns", []core.PodIP{}), + }, { + expectError: false, + pod: makePod("single-ip-family-6", "ns", []core.PodIP{{IP: "::1"}}), + }, { + expectError: false, + pod: makePod("single-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}}), + }, { + expectError: false, + pod: makePod("dual-stack-4-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}), + }, { + expectError: false, + pod: makePod("dual-stack-6-4", "ns", []core.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}), + }, /* failure cases start here */ { expectError: true, pod: makePod("invalid-pod-ip", "ns", []core.PodIP{{IP: "this-is-not-an-ip"}}), - }, - { + }, { expectError: true, pod: makePod("dualstack-same-ip-family-6", "ns", []core.PodIP{{IP: "::1"}, {IP: "::2"}}), - }, - { + }, { expectError: true, pod: makePod("dualstack-same-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}), - }, - { + }, { expectError: true, pod: makePod("dualstack-repeated-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}), - }, - { + }, { expectError: true, pod: makePod("dualstack-repeated-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}), }, @@ -22475,8 +20915,7 @@ func TestPodIPsValidation(t *testing.T) { { expectError: true, pod: makePod("dualstack-duplicate-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}), - }, - { + }, { expectError: true, pod: makePod("dualstack-duplicate-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}), }, @@ -22529,29 +20968,24 @@ func TestValidateNodeCIDRs(t *testing.T) { testCases := []struct { expectError bool node core.Node - }{ - { - expectError: false, - node: makeNode("nil-pod-cidr", nil), - }, - { - expectError: false, - node: makeNode("empty-pod-cidr", []string{}), - }, - { - expectError: false, - node: makeNode("single-pod-cidr-4", []string{"192.168.0.0/16"}), - }, - { - expectError: false, - node: makeNode("single-pod-cidr-6", []string{"2000::/10"}), - }, + }{{ + expectError: false, + node: makeNode("nil-pod-cidr", nil), + }, { + expectError: false, + node: makeNode("empty-pod-cidr", []string{}), + }, { + expectError: false, + node: makeNode("single-pod-cidr-4", []string{"192.168.0.0/16"}), + }, { + expectError: false, + node: makeNode("single-pod-cidr-6", []string{"2000::/10"}), + }, { expectError: false, node: makeNode("multi-pod-cidr-6-4", []string{"2000::/10", "192.168.0.0/16"}), - }, - { + }, { expectError: false, node: makeNode("multi-pod-cidr-4-6", []string{"192.168.0.0/16", "2000::/10"}), }, @@ -22559,28 +20993,22 @@ func TestValidateNodeCIDRs(t *testing.T) { { expectError: true, node: makeNode("invalid-pod-cidr", []string{"this-is-not-a-valid-cidr"}), - }, - { + }, { expectError: true, node: makeNode("duplicate-pod-cidr-4", []string{"10.0.0.1/16", "10.0.0.1/16"}), - }, - { + }, { expectError: true, node: makeNode("duplicate-pod-cidr-6", []string{"2000::/10", "2000::/10"}), - }, - { + }, { expectError: true, node: makeNode("not-a-dualstack-no-v4", []string{"2000::/10", "3000::/10"}), - }, - { + }, { expectError: true, node: makeNode("not-a-dualstack-no-v6", []string{"10.0.0.0/16", "10.1.0.0/16"}), - }, - { + }, { expectError: true, node: makeNode("not-a-dualstack-repeated-v6", []string{"2000::/10", "10.0.0.0/16", "3000::/10"}), - }, - { + }, { expectError: true, node: makeNode("not-a-dualstack-repeated-v4", []string{"10.0.0.0/16", "3000::/10", "10.1.0.0/16"}), }, @@ -22606,229 +21034,219 @@ func TestValidateSeccompAnnotationAndField(t *testing.T) { description string pod *core.Pod validation func(*testing.T, string, field.ErrorList, *v1.Pod) - }{ - { - description: "Field type unconfined and annotation does not match", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.SeccompPodAnnotationKey: "not-matching", + }{{ + description: "Field type unconfined and annotation does not match", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.SeccompPodAnnotationKey: "not-matching", + }, + }, + Spec: core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + SeccompProfile: &core.SeccompProfile{ + Type: core.SeccompProfileTypeUnconfined, }, }, - Spec: core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ + }, + }, + validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { + require.NotNil(t, allErrs, desc) + }, + }, { + description: "Field type default and annotation does not match", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.SeccompPodAnnotationKey: "not-matching", + }, + }, + Spec: core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + SeccompProfile: &core.SeccompProfile{ + Type: core.SeccompProfileTypeRuntimeDefault, + }, + }, + }, + }, + validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { + require.NotNil(t, allErrs, desc) + }, + }, { + description: "Field type localhost and annotation does not match", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.SeccompPodAnnotationKey: "not-matching", + }, + }, + Spec: core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + SeccompProfile: &core.SeccompProfile{ + Type: core.SeccompProfileTypeLocalhost, + LocalhostProfile: &testProfile, + }, + }, + }, + }, + validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { + require.NotNil(t, allErrs, desc) + }, + }, { + description: "Field type localhost and localhost/ prefixed annotation does not match", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.SeccompPodAnnotationKey: "localhost/not-matching", + }, + }, + Spec: core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + SeccompProfile: &core.SeccompProfile{ + Type: core.SeccompProfileTypeLocalhost, + LocalhostProfile: &testProfile, + }, + }, + }, + }, + validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { + require.NotNil(t, allErrs, desc) + }, + }, { + description: "Field type unconfined and annotation does not match (container)", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching", + }, + }, + Spec: core.PodSpec{ + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ SeccompProfile: &core.SeccompProfile{ Type: core.SeccompProfileTypeUnconfined, }, }, - }, - }, - validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { - require.NotNil(t, allErrs, desc) + }}, }, }, - { - description: "Field type default and annotation does not match", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.SeccompPodAnnotationKey: "not-matching", - }, + validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { + require.NotNil(t, allErrs, desc) + }, + }, { + description: "Field type default and annotation does not match (container)", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching", }, - Spec: core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ + }, + Spec: core.PodSpec{ + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ SeccompProfile: &core.SeccompProfile{ Type: core.SeccompProfileTypeRuntimeDefault, }, }, - }, - }, - validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { - require.NotNil(t, allErrs, desc) + }}, }, }, - { - description: "Field type localhost and annotation does not match", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.SeccompPodAnnotationKey: "not-matching", - }, + validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { + require.NotNil(t, allErrs, desc) + }, + }, { + description: "Field type localhost and annotation does not match (container)", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching", }, - Spec: core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ + }, + Spec: core.PodSpec{ + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ SeccompProfile: &core.SeccompProfile{ Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: &testProfile, }, }, - }, - }, - validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { - require.NotNil(t, allErrs, desc) + }}, }, }, - { - description: "Field type localhost and localhost/ prefixed annotation does not match", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.SeccompPodAnnotationKey: "localhost/not-matching", - }, + validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { + require.NotNil(t, allErrs, desc) + }, + }, { + description: "Field type localhost and localhost/ prefixed annotation does not match (container)", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching", }, - Spec: core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ + }, + Spec: core.PodSpec{ + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ SeccompProfile: &core.SeccompProfile{ Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: &testProfile, }, }, - }, - }, - validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { - require.NotNil(t, allErrs, desc) + }}, }, }, - { - description: "Field type unconfined and annotation does not match (container)", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching", - }, - }, - Spec: core.PodSpec{ - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - SeccompProfile: &core.SeccompProfile{ - Type: core.SeccompProfileTypeUnconfined, - }, - }, - }}, + validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { + require.NotNil(t, allErrs, desc) + }, + }, { + description: "Nil errors must not be appended (pod)", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.SeccompPodAnnotationKey: "localhost/anyprofile", }, }, - validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { - require.NotNil(t, allErrs, desc) + Spec: core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + SeccompProfile: &core.SeccompProfile{ + Type: "Abc", + }, + }, + Containers: []core.Container{{ + Name: containerName, + }}, }, }, - { - description: "Field type default and annotation does not match (container)", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching", - }, - }, - Spec: core.PodSpec{ - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - SeccompProfile: &core.SeccompProfile{ - Type: core.SeccompProfileTypeRuntimeDefault, - }, - }, - }}, - }, - }, - validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { - require.NotNil(t, allErrs, desc) - }, + validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { + require.Empty(t, allErrs, desc) }, - { - description: "Field type localhost and annotation does not match (container)", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching", - }, - }, - Spec: core.PodSpec{ - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - SeccompProfile: &core.SeccompProfile{ - Type: core.SeccompProfileTypeLocalhost, - LocalhostProfile: &testProfile, - }, - }, - }}, + }, { + description: "Nil errors must not be appended (container)", + pod: &core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching", }, }, - validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { - require.NotNil(t, allErrs, desc) - }, - }, - { - description: "Field type localhost and localhost/ prefixed annotation does not match (container)", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching", - }, - }, - Spec: core.PodSpec{ - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - SeccompProfile: &core.SeccompProfile{ - Type: core.SeccompProfileTypeLocalhost, - LocalhostProfile: &testProfile, - }, - }, - }}, - }, - }, - validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { - require.NotNil(t, allErrs, desc) - }, - }, - { - description: "Nil errors must not be appended (pod)", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.SeccompPodAnnotationKey: "localhost/anyprofile", - }, - }, - Spec: core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ + Spec: core.PodSpec{ + Containers: []core.Container{{ + SecurityContext: &core.SecurityContext{ SeccompProfile: &core.SeccompProfile{ Type: "Abc", }, }, - Containers: []core.Container{{ - Name: containerName, - }}, - }, - }, - validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { - require.Empty(t, allErrs, desc) + Name: containerName, + }}, }, }, - { - description: "Nil errors must not be appended (container)", - pod: &core.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching", - }, - }, - Spec: core.PodSpec{ - Containers: []core.Container{{ - SecurityContext: &core.SecurityContext{ - SeccompProfile: &core.SeccompProfile{ - Type: "Abc", - }, - }, - Name: containerName, - }}, - }, - }, - validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { - require.Empty(t, allErrs, desc) - }, + validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { + require.Empty(t, allErrs, desc) }, + }, } { output := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}, @@ -22857,63 +21275,54 @@ func TestValidateSeccompAnnotationsAndFieldsMatch(t *testing.T) { seccompField *core.SeccompProfile fldPath *field.Path expectedErr *field.Error - }{ - { - description: "seccompField nil should return empty", - expectedErr: nil, - }, - { - description: "unconfined annotation and SeccompProfileTypeUnconfined should return empty", - annotationValue: "unconfined", - seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined}, - expectedErr: nil, - }, - { - description: "runtime/default annotation and SeccompProfileTypeRuntimeDefault should return empty", - annotationValue: "runtime/default", - seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault}, - expectedErr: nil, - }, - { - description: "docker/default annotation and SeccompProfileTypeRuntimeDefault should return empty", - annotationValue: "docker/default", - seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault}, - expectedErr: nil, - }, - { - description: "localhost/test.json annotation and SeccompProfileTypeLocalhost with correct profile should return empty", - annotationValue: "localhost/test.json", - seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("test.json")}, - expectedErr: nil, - }, - { - description: "localhost/test.json annotation and SeccompProfileTypeLocalhost without profile should error", - annotationValue: "localhost/test.json", - seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost}, - fldPath: rootFld, - expectedErr: field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"), - }, - { - description: "localhost/test.json annotation and SeccompProfileTypeLocalhost with different profile should error", - annotationValue: "localhost/test.json", - seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("different.json")}, - fldPath: rootFld, - expectedErr: field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"), - }, - { - description: "localhost/test.json annotation and SeccompProfileTypeUnconfined with different profile should error", - annotationValue: "localhost/test.json", - seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined}, - fldPath: rootFld, - expectedErr: field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"), - }, - { - description: "localhost/test.json annotation and SeccompProfileTypeRuntimeDefault with different profile should error", - annotationValue: "localhost/test.json", - seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault}, - fldPath: rootFld, - expectedErr: field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"), - }, + }{{ + description: "seccompField nil should return empty", + expectedErr: nil, + }, { + description: "unconfined annotation and SeccompProfileTypeUnconfined should return empty", + annotationValue: "unconfined", + seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined}, + expectedErr: nil, + }, { + description: "runtime/default annotation and SeccompProfileTypeRuntimeDefault should return empty", + annotationValue: "runtime/default", + seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault}, + expectedErr: nil, + }, { + description: "docker/default annotation and SeccompProfileTypeRuntimeDefault should return empty", + annotationValue: "docker/default", + seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault}, + expectedErr: nil, + }, { + description: "localhost/test.json annotation and SeccompProfileTypeLocalhost with correct profile should return empty", + annotationValue: "localhost/test.json", + seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("test.json")}, + expectedErr: nil, + }, { + description: "localhost/test.json annotation and SeccompProfileTypeLocalhost without profile should error", + annotationValue: "localhost/test.json", + seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost}, + fldPath: rootFld, + expectedErr: field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"), + }, { + description: "localhost/test.json annotation and SeccompProfileTypeLocalhost with different profile should error", + annotationValue: "localhost/test.json", + seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("different.json")}, + fldPath: rootFld, + expectedErr: field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"), + }, { + description: "localhost/test.json annotation and SeccompProfileTypeUnconfined with different profile should error", + annotationValue: "localhost/test.json", + seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined}, + fldPath: rootFld, + expectedErr: field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"), + }, { + description: "localhost/test.json annotation and SeccompProfileTypeRuntimeDefault with different profile should error", + annotationValue: "localhost/test.json", + seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault}, + fldPath: rootFld, + expectedErr: field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"), + }, } for i, test := range tests { @@ -22929,120 +21338,108 @@ func TestValidatePodTemplateSpecSeccomp(t *testing.T) { spec *core.PodTemplateSpec fldPath *field.Path expectedErr field.ErrorList - }{ - { - description: "seccomp field and container annotation must match", - fldPath: rootFld, - expectedErr: field.ErrorList{ - field.Forbidden( - rootFld.Child("spec").Child("containers").Index(1).Child("securityContext").Child("seccompProfile").Child("type"), - "seccomp type in annotation and field must match"), - }, - spec: &core.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - "container.seccomp.security.alpha.kubernetes.io/test2": "unconfined", - }, - }, - Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "test1", - Image: "alpine", - ImagePullPolicy: core.PullAlways, - TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, - }, - { - SecurityContext: &core.SecurityContext{ - SeccompProfile: &core.SeccompProfile{ - Type: core.SeccompProfileTypeRuntimeDefault, - }, - }, - Name: "test2", - Image: "alpine", - ImagePullPolicy: core.PullAlways, - TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, - }, - }, - RestartPolicy: core.RestartPolicyAlways, - DNSPolicy: core.DNSDefault, - }, - }, + }{{ + description: "seccomp field and container annotation must match", + fldPath: rootFld, + expectedErr: field.ErrorList{ + field.Forbidden( + rootFld.Child("spec").Child("containers").Index(1).Child("securityContext").Child("seccompProfile").Child("type"), + "seccomp type in annotation and field must match"), }, - { - description: "seccomp field and pod annotation must match", - fldPath: rootFld, - expectedErr: field.ErrorList{ - field.Forbidden( - rootFld.Child("spec").Child("securityContext").Child("seccompProfile").Child("type"), - "seccomp type in annotation and field must match"), - }, - spec: &core.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - "seccomp.security.alpha.kubernetes.io/pod": "runtime/default", - }, + spec: &core.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "container.seccomp.security.alpha.kubernetes.io/test2": "unconfined", }, - Spec: core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ + }, + Spec: core.PodSpec{ + Containers: []core.Container{{ + Name: "test1", + Image: "alpine", + ImagePullPolicy: core.PullAlways, + TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, + }, { + SecurityContext: &core.SecurityContext{ SeccompProfile: &core.SeccompProfile{ - Type: core.SeccompProfileTypeUnconfined, + Type: core.SeccompProfileTypeRuntimeDefault, }, }, - Containers: []core.Container{ - { - Name: "test", - Image: "alpine", - ImagePullPolicy: core.PullAlways, - TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, - }, - }, - RestartPolicy: core.RestartPolicyAlways, - DNSPolicy: core.DNSDefault, - }, + Name: "test2", + Image: "alpine", + ImagePullPolicy: core.PullAlways, + TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, + }}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSDefault, }, }, - { - description: "init seccomp field and container annotation must match", - fldPath: rootFld, - expectedErr: field.ErrorList{ - field.Forbidden( - rootFld.Child("spec").Child("initContainers").Index(0).Child("securityContext").Child("seccompProfile").Child("type"), - "seccomp type in annotation and field must match"), + }, { + description: "seccomp field and pod annotation must match", + fldPath: rootFld, + expectedErr: field.ErrorList{ + field.Forbidden( + rootFld.Child("spec").Child("securityContext").Child("seccompProfile").Child("type"), + "seccomp type in annotation and field must match"), + }, + spec: &core.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "seccomp.security.alpha.kubernetes.io/pod": "runtime/default", + }, }, - spec: &core.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - "container.seccomp.security.alpha.kubernetes.io/init-test": "unconfined", + Spec: core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + SeccompProfile: &core.SeccompProfile{ + Type: core.SeccompProfileTypeUnconfined, }, }, - Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "test", - Image: "alpine", - ImagePullPolicy: core.PullAlways, - TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, - }, - }, - InitContainers: []core.Container{ - { - Name: "init-test", - SecurityContext: &core.SecurityContext{ - SeccompProfile: &core.SeccompProfile{ - Type: core.SeccompProfileTypeRuntimeDefault, - }, - }, - Image: "alpine", - ImagePullPolicy: core.PullAlways, - TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, - }, - }, - RestartPolicy: core.RestartPolicyAlways, - DNSPolicy: core.DNSDefault, - }, + Containers: []core.Container{{ + Name: "test", + Image: "alpine", + ImagePullPolicy: core.PullAlways, + TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, + }}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSDefault, }, }, + }, { + description: "init seccomp field and container annotation must match", + fldPath: rootFld, + expectedErr: field.ErrorList{ + field.Forbidden( + rootFld.Child("spec").Child("initContainers").Index(0).Child("securityContext").Child("seccompProfile").Child("type"), + "seccomp type in annotation and field must match"), + }, + spec: &core.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "container.seccomp.security.alpha.kubernetes.io/init-test": "unconfined", + }, + }, + Spec: core.PodSpec{ + Containers: []core.Container{{ + Name: "test", + Image: "alpine", + ImagePullPolicy: core.PullAlways, + TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, + }}, + InitContainers: []core.Container{{ + Name: "init-test", + SecurityContext: &core.SecurityContext{ + SeccompProfile: &core.SeccompProfile{ + Type: core.SeccompProfileTypeRuntimeDefault, + }, + }, + Image: "alpine", + ImagePullPolicy: core.PullAlways, + TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, + }}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSDefault, + }, + }, + }, } for i, test := range tests { @@ -23057,45 +21454,42 @@ func TestValidateResourceRequirements(t *testing.T) { name string requirements core.ResourceRequirements opts PodValidationOptions - }{ - { - name: "limits and requests of hugepage resource are equal", - requirements: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceCPU: resource.MustParse("10"), - core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), - }, - Requests: core.ResourceList{ - core.ResourceCPU: resource.MustParse("10"), - core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), - }, + }{{ + name: "limits and requests of hugepage resource are equal", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceCPU: resource.MustParse("10"), + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), }, - opts: PodValidationOptions{}, - }, - { - name: "limits and requests of memory resource are equal", - requirements: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceMemory: resource.MustParse("2Mi"), - }, - Requests: core.ResourceList{ - core.ResourceMemory: resource.MustParse("2Mi"), - }, + Requests: core.ResourceList{ + core.ResourceCPU: resource.MustParse("10"), + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), }, - opts: PodValidationOptions{}, }, - { - name: "limits and requests of cpu resource are equal", - requirements: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceCPU: resource.MustParse("10"), - }, - Requests: core.ResourceList{ - core.ResourceCPU: resource.MustParse("10"), - }, + opts: PodValidationOptions{}, + }, { + name: "limits and requests of memory resource are equal", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceMemory: resource.MustParse("2Mi"), + }, + Requests: core.ResourceList{ + core.ResourceMemory: resource.MustParse("2Mi"), }, - opts: PodValidationOptions{}, }, + opts: PodValidationOptions{}, + }, { + name: "limits and requests of cpu resource are equal", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceCPU: resource.MustParse("10"), + }, + Requests: core.ResourceList{ + core.ResourceCPU: resource.MustParse("10"), + }, + }, + opts: PodValidationOptions{}, + }, } for _, tc := range tests { @@ -23110,19 +21504,18 @@ func TestValidateResourceRequirements(t *testing.T) { name string requirements core.ResourceRequirements opts PodValidationOptions - }{ - { - name: "hugepage resource without cpu or memory", - requirements: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), - }, - Requests: core.ResourceList{ - core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), - }, + }{{ + name: "hugepage resource without cpu or memory", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + Requests: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), }, - opts: PodValidationOptions{}, }, + opts: PodValidationOptions{}, + }, } for _, tc := range errTests { @@ -23183,147 +21576,127 @@ func TestValidateHostUsers(t *testing.T) { name string success bool spec *core.PodSpec - }{ - { - name: "empty", - success: true, - spec: &core.PodSpec{}, + }{{ + name: "empty", + success: true, + spec: &core.PodSpec{}, + }, { + name: "hostUsers unset", + success: true, + spec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{}, }, - { - name: "hostUsers unset", - success: true, - spec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{}, + }, { + name: "hostUsers=false", + success: true, + spec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostUsers: &falseVar, }, }, - { - name: "hostUsers=false", - success: true, - spec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostUsers: &falseVar, - }, + }, { + name: "hostUsers=true", + success: true, + spec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostUsers: &trueVar, }, }, - { - name: "hostUsers=true", - success: true, - spec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostUsers: &trueVar, - }, + }, { + name: "hostUsers=false & volumes", + success: true, + spec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostUsers: &falseVar, }, - }, - { - name: "hostUsers=false & volumes", - success: true, - spec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostUsers: &falseVar, - }, - Volumes: []core.Volume{ - { - Name: "configmap", - VolumeSource: core.VolumeSource{ - ConfigMap: &core.ConfigMapVolumeSource{ - LocalObjectReference: core.LocalObjectReference{Name: "configmap"}, - }, - }, - }, - { - Name: "secret", - VolumeSource: core.VolumeSource{ - Secret: &core.SecretVolumeSource{ - SecretName: "secret", - }, - }, - }, - { - Name: "downward-api", - VolumeSource: core.VolumeSource{ - DownwardAPI: &core.DownwardAPIVolumeSource{}, - }, - }, - { - Name: "proj", - VolumeSource: core.VolumeSource{ - Projected: &core.ProjectedVolumeSource{}, - }, - }, - { - Name: "empty-dir", - VolumeSource: core.VolumeSource{ - EmptyDir: &core.EmptyDirVolumeSource{}, - }, + Volumes: []core.Volume{{ + Name: "configmap", + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{Name: "configmap"}, }, }, - }, - }, - { - name: "hostUsers=false - unsupported volume", - success: false, - spec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostUsers: &falseVar, - }, - Volumes: []core.Volume{ - { - Name: "host-path", - VolumeSource: core.VolumeSource{ - HostPath: &core.HostPathVolumeSource{}, - }, + }, { + Name: "secret", + VolumeSource: core.VolumeSource{ + Secret: &core.SecretVolumeSource{ + SecretName: "secret", }, }, + }, { + Name: "downward-api", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{}, + }, + }, { + Name: "proj", + VolumeSource: core.VolumeSource{ + Projected: &core.ProjectedVolumeSource{}, + }, + }, { + Name: "empty-dir", + VolumeSource: core.VolumeSource{ + EmptyDir: &core.EmptyDirVolumeSource{}, + }, + }}, + }, + }, { + name: "hostUsers=false - unsupported volume", + success: false, + spec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostUsers: &falseVar, + }, + Volumes: []core.Volume{{ + Name: "host-path", + VolumeSource: core.VolumeSource{ + HostPath: &core.HostPathVolumeSource{}, + }, + }}, + }, + }, { + // It should ignore unsupported volumes with hostUsers=true. + name: "hostUsers=true - unsupported volume", + success: true, + spec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostUsers: &trueVar, + }, + Volumes: []core.Volume{{ + Name: "host-path", + VolumeSource: core.VolumeSource{ + HostPath: &core.HostPathVolumeSource{}, + }, + }}, + }, + }, { + name: "hostUsers=false & HostNetwork", + success: false, + spec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostUsers: &falseVar, + HostNetwork: true, }, }, - { - // It should ignore unsupported volumes with hostUsers=true. - name: "hostUsers=true - unsupported volume", - success: true, - spec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostUsers: &trueVar, - }, - Volumes: []core.Volume{ - { - Name: "host-path", - VolumeSource: core.VolumeSource{ - HostPath: &core.HostPathVolumeSource{}, - }, - }, - }, + }, { + name: "hostUsers=false & HostPID", + success: false, + spec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostUsers: &falseVar, + HostPID: true, }, }, - { - name: "hostUsers=false & HostNetwork", - success: false, - spec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostUsers: &falseVar, - HostNetwork: true, - }, - }, - }, - { - name: "hostUsers=false & HostPID", - success: false, - spec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostUsers: &falseVar, - HostPID: true, - }, - }, - }, - { - name: "hostUsers=false & HostIPC", - success: false, - spec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostUsers: &falseVar, - HostIPC: true, - }, + }, { + name: "hostUsers=false & HostIPC", + success: false, + spec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostUsers: &falseVar, + HostIPC: true, }, }, + }, } for _, tc := range cases { @@ -23351,368 +21724,350 @@ func TestValidateWindowsHostProcessPod(t *testing.T) { expectError bool allowPrivileged bool podSpec *core.PodSpec - }{ - { - name: "Spec with feature enabled, pod-wide HostProcess=true, and HostNetwork unset should not validate", - expectError: true, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ + }{{ + name: "Spec with feature enabled, pod-wide HostProcess=true, and HostNetwork unset should not validate", + expectError: true, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + Containers: []core.Container{{ + Name: containerName, + }}, + }, + }, { + name: "Spec with feature enabled, pod-wide HostProcess=ture, and HostNetwork set should validate", + expectError: false, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + Containers: []core.Container{{ + Name: containerName, + }}, + }, + }, { + name: "Spec with feature enabled, pod-wide HostProcess=ture, HostNetwork set, and containers setting HostProcess=true should validate", + expectError: false, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ WindowsOptions: &core.WindowsSecurityContextOptions{ HostProcess: &trueVar, }, }, - Containers: []core.Container{{ - Name: containerName, - }}, - }, - }, - { - name: "Spec with feature enabled, pod-wide HostProcess=ture, and HostNetwork set should validate", - expectError: false, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, + }}, + InitContainers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ WindowsOptions: &core.WindowsSecurityContextOptions{ HostProcess: &trueVar, }, }, - Containers: []core.Container{{ - Name: containerName, - }}, - }, + }}, }, - { - name: "Spec with feature enabled, pod-wide HostProcess=ture, HostNetwork set, and containers setting HostProcess=true should validate", - expectError: false, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, + }, { + name: "Spec with feature enabled, pod-wide HostProcess=nil, HostNetwork set, and all containers setting HostProcess=true should validate", + expectError: false, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + }, + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ WindowsOptions: &core.WindowsSecurityContextOptions{ HostProcess: &trueVar, }, }, - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }}, - InitContainers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }}, - }, - }, - { - name: "Spec with feature enabled, pod-wide HostProcess=nil, HostNetwork set, and all containers setting HostProcess=true should validate", - expectError: false, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, - }, - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }}, - InitContainers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }}, - }, - }, - { - name: "Pods with feature enabled, some containers setting HostProcess=true, and others setting HostProcess=false should not validate", - expectError: true, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, - }, - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }}, - InitContainers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &falseVar, - }, - }, - }}, - }, - }, - { - name: "Spec with feature enabled, some containers setting HostProcess=true, and other leaving HostProcess unset should not validate", - expectError: true, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, - }, - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }}, - InitContainers: []core.Container{{ - Name: containerName, - }}, - }, - }, - { - name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and init containers setting HostProcess=false should not validate", - expectError: true, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, + }}, + InitContainers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ WindowsOptions: &core.WindowsSecurityContextOptions{ HostProcess: &trueVar, }, }, - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }}, - InitContainers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &falseVar, - }, - }, - }}, - }, + }}, }, - { - name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others setting HostProcess=false should not validate", - expectError: true, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, + }, { + name: "Pods with feature enabled, some containers setting HostProcess=true, and others setting HostProcess=false should not validate", + expectError: true, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + }, + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ WindowsOptions: &core.WindowsSecurityContextOptions{ HostProcess: &trueVar, }, }, - Containers: []core.Container{ - { - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }, { - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &falseVar, - }, - }, - }, - }, - }, - }, - { - name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others leaving HostProcess=nil should validate", - expectError: false, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }}, - InitContainers: []core.Container{{ - Name: containerName, - }}, - }, - }, - { - name: "Spec with feature enabled, pod-wide HostProcess=false, some contaienrs setting HostProccess=true should not validate", - expectError: true, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, + }}, + InitContainers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ WindowsOptions: &core.WindowsSecurityContextOptions{ HostProcess: &falseVar, }, }, - Containers: []core.Container{{ - Name: containerName, - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }}, - InitContainers: []core.Container{{ - Name: containerName, - }}, - }, + }}, }, - { - name: "Pod's HostProcess set to true but all containers override to false should not validate", - expectError: true, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, + }, { + name: "Spec with feature enabled, some containers setting HostProcess=true, and other leaving HostProcess unset should not validate", + expectError: true, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + }, + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ WindowsOptions: &core.WindowsSecurityContextOptions{ HostProcess: &trueVar, }, }, - Containers: []core.Container{{ - Name: containerName, + }}, + InitContainers: []core.Container{{ + Name: containerName, + }}, + }, + }, { + name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and init containers setting HostProcess=false should not validate", + expectError: true, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + }}, + InitContainers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &falseVar, + }, + }, + }}, + }, + }, { + name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others setting HostProcess=false should not validate", + expectError: true, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + }, { + Name: containerName, + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &falseVar, + }, + }, + }}, + }, + }, { + name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others leaving HostProcess=nil should validate", + expectError: false, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + }}, + InitContainers: []core.Container{{ + Name: containerName, + }}, + }, + }, { + name: "Spec with feature enabled, pod-wide HostProcess=false, some contaienrs setting HostProccess=true should not validate", + expectError: true, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &falseVar, + }, + }, + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + }}, + InitContainers: []core.Container{{ + Name: containerName, + }}, + }, + }, { + name: "Pod's HostProcess set to true but all containers override to false should not validate", + expectError: true, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &falseVar, + }, + }, + }}, + }, + }, { + name: "Valid HostProcess pod should spec should not validate if allowPrivileged is not set", + expectError: true, + allowPrivileged: false, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + }, + Containers: []core.Container{{ + Name: containerName, + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + }}, + }, + }, { + name: "Non-HostProcess ephemeral container in HostProcess pod should not validate", + expectError: true, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + Containers: []core.Container{{ + Name: containerName, + }}, + EphemeralContainers: []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ SecurityContext: &core.SecurityContext{ WindowsOptions: &core.WindowsSecurityContextOptions{ HostProcess: &falseVar, }, }, - }}, - }, - }, - { - name: "Valid HostProcess pod should spec should not validate if allowPrivileged is not set", - expectError: true, - allowPrivileged: false, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, }, - Containers: []core.Container{{ - Name: containerName, + }}, + }, + }, { + name: "HostProcess ephemeral container in HostProcess pod should validate", + expectError: false, + allowPrivileged: true, + podSpec: &core.PodSpec{ + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &trueVar, + }, + }, + Containers: []core.Container{{ + Name: containerName, + }}, + EphemeralContainers: []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{}, + }}, + }, + }, { + name: "Non-HostProcess ephemeral container in Non-HostProcess pod should validate", + expectError: false, + allowPrivileged: true, + podSpec: &core.PodSpec{ + Containers: []core.Container{{ + Name: containerName, + }}, + EphemeralContainers: []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ + SecurityContext: &core.SecurityContext{ + WindowsOptions: &core.WindowsSecurityContextOptions{ + HostProcess: &falseVar, + }, + }, + }, + }}, + }, + }, { + name: "HostProcess ephemeral container in Non-HostProcess pod should not validate", + expectError: true, + allowPrivileged: true, + podSpec: &core.PodSpec{ + Containers: []core.Container{{ + Name: containerName, + }}, + EphemeralContainers: []core.EphemeralContainer{{ + EphemeralContainerCommon: core.EphemeralContainerCommon{ SecurityContext: &core.SecurityContext{ WindowsOptions: &core.WindowsSecurityContextOptions{ HostProcess: &trueVar, }, }, - }}, - }, - }, - { - name: "Non-HostProcess ephemeral container in HostProcess pod should not validate", - expectError: true, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, }, - Containers: []core.Container{{ - Name: containerName, - }}, - EphemeralContainers: []core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &falseVar, - }, - }, - }, - }}, - }, - }, - { - name: "HostProcess ephemeral container in HostProcess pod should validate", - expectError: false, - allowPrivileged: true, - podSpec: &core.PodSpec{ - SecurityContext: &core.PodSecurityContext{ - HostNetwork: true, - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - Containers: []core.Container{{ - Name: containerName, - }}, - EphemeralContainers: []core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{}, - }}, - }, - }, - { - name: "Non-HostProcess ephemeral container in Non-HostProcess pod should validate", - expectError: false, - allowPrivileged: true, - podSpec: &core.PodSpec{ - Containers: []core.Container{{ - Name: containerName, - }}, - EphemeralContainers: []core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &falseVar, - }, - }, - }, - }}, - }, - }, - { - name: "HostProcess ephemeral container in Non-HostProcess pod should not validate", - expectError: true, - allowPrivileged: true, - podSpec: &core.PodSpec{ - Containers: []core.Container{{ - Name: containerName, - }}, - EphemeralContainers: []core.EphemeralContainer{{ - EphemeralContainerCommon: core.EphemeralContainerCommon{ - SecurityContext: &core.SecurityContext{ - WindowsOptions: &core.WindowsSecurityContextOptions{ - HostProcess: &trueVar, - }, - }, - }, - }}, - }, + }}, }, + }, } for _, testCase := range testCases { @@ -23738,37 +22093,31 @@ func TestValidateOS(t *testing.T) { name string expectError bool podSpec *core.PodSpec - }{ - { - name: "no OS field, featuregate", - expectError: false, - podSpec: &core.PodSpec{OS: nil}, - }, - { - name: "empty OS field, featuregate", - expectError: true, - podSpec: &core.PodSpec{OS: &core.PodOS{}}, - }, - { - name: "OS field, featuregate, valid OS", - expectError: false, - podSpec: &core.PodSpec{OS: &core.PodOS{Name: core.Linux}}, - }, - { - name: "OS field, featuregate, valid OS", - expectError: false, - podSpec: &core.PodSpec{OS: &core.PodOS{Name: core.Windows}}, - }, - { - name: "OS field, featuregate, empty OS", - expectError: true, - podSpec: &core.PodSpec{OS: &core.PodOS{Name: ""}}, - }, - { - name: "OS field, featuregate, invalid OS", - expectError: true, - podSpec: &core.PodSpec{OS: &core.PodOS{Name: "dummyOS"}}, - }, + }{{ + name: "no OS field, featuregate", + expectError: false, + podSpec: &core.PodSpec{OS: nil}, + }, { + name: "empty OS field, featuregate", + expectError: true, + podSpec: &core.PodSpec{OS: &core.PodOS{}}, + }, { + name: "OS field, featuregate, valid OS", + expectError: false, + podSpec: &core.PodSpec{OS: &core.PodOS{Name: core.Linux}}, + }, { + name: "OS field, featuregate, valid OS", + expectError: false, + podSpec: &core.PodSpec{OS: &core.PodOS{Name: core.Windows}}, + }, { + name: "OS field, featuregate, empty OS", + expectError: true, + podSpec: &core.PodSpec{OS: &core.PodOS{Name: ""}}, + }, { + name: "OS field, featuregate, invalid OS", + expectError: true, + podSpec: &core.PodSpec{OS: &core.PodOS{Name: "dummyOS"}}, + }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -23817,37 +22166,32 @@ func TestValidatePVSecretReference(t *testing.T) { args args expectError bool expectedError string - }{ - { - name: "invalid secret ref name", - args: args{&core.SecretReference{Name: "$%^&*#", Namespace: "default"}, rootFld}, - expectError: true, - expectedError: "name.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, - }, - { - name: "invalid secret ref namespace", - args: args{&core.SecretReference{Name: "valid", Namespace: "$%^&*#"}, rootFld}, - expectError: true, - expectedError: "name.namespace: Invalid value: \"$%^&*#\": " + dnsLabelErrMsg, - }, - { - name: "invalid secret: missing namespace", - args: args{&core.SecretReference{Name: "valid"}, rootFld}, - expectError: true, - expectedError: "name.namespace: Required value", - }, - { - name: "invalid secret : missing name", - args: args{&core.SecretReference{Namespace: "default"}, rootFld}, - expectError: true, - expectedError: "name.name: Required value", - }, - { - name: "valid secret", - args: args{&core.SecretReference{Name: "valid", Namespace: "default"}, rootFld}, - expectError: false, - expectedError: "", - }, + }{{ + name: "invalid secret ref name", + args: args{&core.SecretReference{Name: "$%^&*#", Namespace: "default"}, rootFld}, + expectError: true, + expectedError: "name.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, + }, { + name: "invalid secret ref namespace", + args: args{&core.SecretReference{Name: "valid", Namespace: "$%^&*#"}, rootFld}, + expectError: true, + expectedError: "name.namespace: Invalid value: \"$%^&*#\": " + dnsLabelErrMsg, + }, { + name: "invalid secret: missing namespace", + args: args{&core.SecretReference{Name: "valid"}, rootFld}, + expectError: true, + expectedError: "name.namespace: Required value", + }, { + name: "invalid secret : missing name", + args: args{&core.SecretReference{Namespace: "default"}, rootFld}, + expectError: true, + expectedError: "name.name: Required value", + }, { + name: "valid secret", + args: args{&core.SecretReference{Name: "valid", Namespace: "default"}, rootFld}, + expectError: false, + expectedError: "", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -23884,27 +22228,23 @@ func TestValidateDynamicResourceAllocation(t *testing.T) { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-template"}}}}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim-template", - Source: core.ClaimSource{ - ResourceClaimTemplateName: &externalClaimTemplateName, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim-template", + Source: core.ClaimSource{ + ResourceClaimTemplateName: &externalClaimTemplateName, }, - }, + }}, } goodClaimReference := core.PodSpec{ Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-reference"}}}}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim-reference", - Source: core.ClaimSource{ - ResourceClaimName: &externalClaimName, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim-reference", + Source: core.ClaimSource{ + ResourceClaimName: &externalClaimName, }, - }, + }}, } successCases := map[string]core.PodSpec{ @@ -23914,28 +22254,23 @@ func TestValidateDynamicResourceAllocation(t *testing.T) { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "another-claim"}}}}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim", - Source: goodClaimSource, - }, - { - Name: "another-claim", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim", + Source: goodClaimSource, + }, { + Name: "another-claim", + Source: goodClaimSource, + }}, }, "init container": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, InitContainers: []core.Container{{Name: "ctr-init", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim", + Source: goodClaimSource, + }}, }, } for k, v := range successCases { @@ -23951,107 +22286,88 @@ func TestValidateDynamicResourceAllocation(t *testing.T) { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "../my-claim", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "../my-claim", + Source: goodClaimSource, + }}, }, "pod claim name with path": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my/claim", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my/claim", + Source: goodClaimSource, + }}, }, "pod claim name empty": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "", + Source: goodClaimSource, + }}, }, "duplicate pod claim entries": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim", - Source: goodClaimSource, - }, - { - Name: "my-claim", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim", + Source: goodClaimSource, + }, { + Name: "my-claim", + Source: goodClaimSource, + }}, }, "resource claim source empty": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim", - Source: core.ClaimSource{}, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim", + Source: core.ClaimSource{}, + }}, }, "resource claim reference and template": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim", - Source: core.ClaimSource{ - ResourceClaimName: &externalClaimName, - ResourceClaimTemplateName: &externalClaimTemplateName, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim", + Source: core.ClaimSource{ + ResourceClaimName: &externalClaimName, + ResourceClaimTemplateName: &externalClaimTemplateName, }, - }, + }}, }, "claim not found": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "no-such-claim"}}}}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim", + Source: goodClaimSource, + }}, }, "claim name empty": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: ""}}}}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim", + Source: goodClaimSource, + }}, }, "pod claim name duplicates": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "my-claim"}}}}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim", + Source: goodClaimSource, + }}, }, "no claims defined": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, @@ -24062,28 +22378,23 @@ func TestValidateDynamicResourceAllocation(t *testing.T) { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim", - Source: goodClaimSource, - }, - { - Name: "my-claim", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim", + Source: goodClaimSource, + }, { + Name: "my-claim", + Source: goodClaimSource, + }}, }, "ephemeral container don't support resource requirements": { Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr-ephemeral", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}, TargetContainerName: "ctr"}}, RestartPolicy: core.RestartPolicyAlways, DNSPolicy: core.DNSClusterFirst, - ResourceClaims: []core.PodResourceClaim{ - { - Name: "my-claim", - Source: goodClaimSource, - }, - }, + ResourceClaims: []core.PodResourceClaim{{ + Name: "my-claim", + Source: goodClaimSource, + }}, }, "invalid claim template name": func() core.PodSpec { spec := goodClaimTemplate.DeepCopy() diff --git a/pkg/apis/flowcontrol/validation/validation_test.go b/pkg/apis/flowcontrol/validation/validation_test.go index 11825564988..7b5f11b69c8 100644 --- a/pkg/apis/flowcontrol/validation/validation_test.go +++ b/pkg/apis/flowcontrol/validation/validation_test.go @@ -41,31 +41,23 @@ func TestFlowSchemaValidation(t *testing.T) { PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ Name: flowcontrol.PriorityLevelConfigurationNameExempt, }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindGroup, - Group: &flowcontrol.GroupSubject{Name: "system:masters"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - ClusterScope: true, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - NonResourceURLs: []string{"/"}, - }, - }, - }, - }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "system:masters"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + ClusterScope: true, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + NonResourceURLs: []string{"/"}, + }}, + }}, } badCatchAll := flowcontrol.FlowSchemaSpec{ MatchingPrecedence: flowcontrol.FlowSchemaMaxMatchingPrecedence, @@ -73,836 +65,665 @@ func TestFlowSchemaValidation(t *testing.T) { Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, }, DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByUserType}, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindGroup, - Group: &flowcontrol.GroupSubject{Name: user.AllUnauthenticated}, - }, - { - Kind: flowcontrol.SubjectKindGroup, - Group: &flowcontrol.GroupSubject{Name: user.AllAuthenticated}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - ClusterScope: true, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - NonResourceURLs: []string{"/"}, - }, - }, - }, - }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: user.AllUnauthenticated}, + }, { + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: user.AllAuthenticated}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + ClusterScope: true, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + NonResourceURLs: []string{"/"}, + }}, + }}, } testCases := []struct { name string flowSchema *flowcontrol.FlowSchema expectedErrors field.ErrorList - }{ - { - name: "missing both resource and non-resource policy-rule should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", + }{{ + name: "missing both resource and non-resource policy-rule should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("spec").Child("rules").Index(0), "at least one of resourceRules and nonResourceRules has to be non-empty"), + }, + }, { + name: "normal flow-schema w/ * verbs/apiGroups/resources should work", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{}, + }, { + name: "malformed Subject union in ServiceAccount case", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindServiceAccount, + User: &flowcontrol.UserSubject{Name: "fred"}, + Group: &flowcontrol.GroupSubject{Name: "fred"}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + NonResourceURLs: []string{"*"}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount"), "serviceAccount is required when subject kind is 'ServiceAccount'"), + field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("user"), "user is forbidden when subject kind is not 'User'"), + field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("group"), "group is forbidden when subject kind is not 'Group'"), + }, + }, { + name: "Subject union malformed in User case", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + Group: &flowcontrol.GroupSubject{Name: "fred"}, + ServiceAccount: &flowcontrol.ServiceAccountSubject{Namespace: "s", Name: "n"}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + NonResourceURLs: []string{"*"}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{ + field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount"), "serviceAccount is forbidden when subject kind is not 'ServiceAccount'"), + field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("user"), "user is required when subject kind is 'User'"), + field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("group"), "group is forbidden when subject kind is not 'Group'"), + }, + }, { + name: "malformed Subject union in Group case", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + User: &flowcontrol.UserSubject{Name: "fred"}, + ServiceAccount: &flowcontrol.ServiceAccountSubject{Namespace: "s", Name: "n"}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + NonResourceURLs: []string{"*"}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{ + field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount"), "serviceAccount is forbidden when subject kind is not 'ServiceAccount'"), + field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("user"), "user is forbidden when subject kind is not 'User'"), + field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("group"), "group is required when subject kind is 'Group'"), + }, + }, { + name: "exempt flow-schema should work", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: flowcontrol.FlowSchemaNameExempt, + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 1, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: flowcontrol.PriorityLevelConfigurationNameExempt, + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "system:masters"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + ClusterScope: true, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + NonResourceURLs: []string{"*"}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{}, + }, { + name: "bad exempt flow-schema should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: flowcontrol.FlowSchemaNameExempt, + }, + Spec: badExempt, + }, + expectedErrors: field.ErrorList{field.Invalid(field.NewPath("spec"), badExempt, "spec of 'exempt' must equal the fixed value")}, + }, { + name: "bad catch-all flow-schema should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: flowcontrol.FlowSchemaNameCatchAll, + }, + Spec: badCatchAll, + }, + expectedErrors: field.ErrorList{field.Invalid(field.NewPath("spec"), badCatchAll, "spec of 'catch-all' must equal the fixed value")}, + }, { + name: "catch-all flow-schema should work", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: flowcontrol.FlowSchemaNameCatchAll, + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 10000, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, + }, + DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByUserType}, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: user.AllUnauthenticated}, + }, { + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: user.AllAuthenticated}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + ClusterScope: true, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + NonResourceURLs: []string{"*"}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{}, + }, { + name: "non-exempt flow-schema with matchingPrecedence==1 should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fred", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 1, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "exempt", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindGroup, + Group: &flowcontrol.GroupSubject{Name: "gorp"}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + NonResourceURLs: []string{"*"}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("matchingPrecedence"), int32(1), "only the schema named 'exempt' may have matchingPrecedence 1")}, + }, { + name: "flow-schema mixes * verbs/apiGroups/resources should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll, "create"}, + APIGroups: []string{flowcontrol.APIGroupAll, "tak"}, + Resources: []string{flowcontrol.ResourceAll, "tok"}, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("verbs"), []string{"*", "create"}, "if '*' is present, must not specify other verbs"), + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("apiGroups"), []string{"*", "tak"}, "if '*' is present, must not specify other api groups"), + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("resources"), []string{"*", "tok"}, "if '*' is present, must not specify other resources"), + }, + }, { + name: "flow-schema has both resource rules and non-resource rules should work", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + NonResourceURLs: []string{"/apis/*"}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{}, + }, { + name: "flow-schema mixes * non-resource URLs should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{"*"}, + NonResourceURLs: []string{flowcontrol.NonResourceAll, "tik"}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("nonResourceRules").Index(0).Child("nonResourceURLs"), []string{"*", "tik"}, "if '*' is present, must not specify other non-resource URLs"), + }, + }, { + name: "invalid subject kind should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: "FooKind", + }}, + NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ + Verbs: []string{"*"}, + NonResourceURLs: []string{flowcontrol.NonResourceAll}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{ + field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("kind"), flowcontrol.SubjectKind("FooKind"), supportedSubjectKinds.List()), + }, + }, { + name: "flow-schema w/ invalid verb should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{"feed"}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{ + field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("verbs"), []string{"feed"}, supportedVerbs.List()), + }, + }, { + name: "flow-schema w/ invalid priority level configuration name should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system+++$$", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + }}, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("priorityLevelConfiguration").Child("name"), "system+++$$", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), + }, + }, { + name: "flow-schema w/ service-account kind missing namespace should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindServiceAccount, + ServiceAccount: &flowcontrol.ServiceAccountSubject{ + Name: "noxu", }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Required(field.NewPath("spec").Child("rules").Index(0), "at least one of resourceRules and nonResourceRules has to be non-empty"), + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + }}, }, }, - { - name: "normal flow-schema w/ * verbs/apiGroups/resources should work", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindGroup, - Group: &flowcontrol.GroupSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{}, + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount").Child("namespace"), "must specify namespace for service account"), }, - { - name: "malformed Subject union in ServiceAccount case", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindServiceAccount, - User: &flowcontrol.UserSubject{Name: "fred"}, - Group: &flowcontrol.GroupSubject{Name: "fred"}, - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - NonResourceURLs: []string{"*"}, - }, - }, - }, - }, - }, + }, { + name: "flow-schema missing kind should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", }, - expectedErrors: field.ErrorList{ - field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount"), "serviceAccount is required when subject kind is 'ServiceAccount'"), - field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("user"), "user is forbidden when subject kind is not 'User'"), - field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("group"), "group is forbidden when subject kind is not 'Group'"), + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: "", + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + }}, }, }, - { - name: "Subject union malformed in User case", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - Group: &flowcontrol.GroupSubject{Name: "fred"}, - ServiceAccount: &flowcontrol.ServiceAccountSubject{Namespace: "s", Name: "n"}, - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - NonResourceURLs: []string{"*"}, - }, - }, - }, - }, - }, + expectedErrors: field.ErrorList{ + field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("kind"), flowcontrol.SubjectKind(""), supportedSubjectKinds.List()), + }, + }, { + name: "Omitted ResourceRule.Namespaces should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", }, - expectedErrors: field.ErrorList{ - field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount"), "serviceAccount is forbidden when subject kind is not 'ServiceAccount'"), - field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("user"), "user is required when subject kind is 'User'"), - field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("group"), "group is forbidden when subject kind is not 'Group'"), + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + Namespaces: nil, + }}, + }}, }, }, - { - name: "malformed Subject union in Group case", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindGroup, - User: &flowcontrol.UserSubject{Name: "fred"}, - ServiceAccount: &flowcontrol.ServiceAccountSubject{Namespace: "s", Name: "n"}, - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - NonResourceURLs: []string{"*"}, - }, - }, - }, - }, - }, + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("namespaces"), "resource rules that are not cluster scoped must supply at least one namespace"), + }, + }, { + name: "ClusterScope is allowed, with no Namespaces", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", }, - expectedErrors: field.ErrorList{ - field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount"), "serviceAccount is forbidden when subject kind is not 'ServiceAccount'"), - field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("user"), "user is forbidden when subject kind is not 'User'"), - field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("group"), "group is required when subject kind is 'Group'"), + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + ClusterScope: true, + }}, + }}, }, }, - { - name: "exempt flow-schema should work", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: flowcontrol.FlowSchemaNameExempt, - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 1, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: flowcontrol.PriorityLevelConfigurationNameExempt, - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindGroup, - Group: &flowcontrol.GroupSubject{Name: "system:masters"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - ClusterScope: true, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - NonResourceURLs: []string{"*"}, - }, - }, - }, - }, - }, + expectedErrors: field.ErrorList{}, + }, { + name: "ClusterScope is allowed with NamespaceEvery", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", }, - expectedErrors: field.ErrorList{}, - }, - { - name: "bad exempt flow-schema should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: flowcontrol.FlowSchemaNameExempt, + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", }, - Spec: badExempt, - }, - expectedErrors: field.ErrorList{field.Invalid(field.NewPath("spec"), badExempt, "spec of 'exempt' must equal the fixed value")}, - }, - { - name: "bad catch-all flow-schema should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: flowcontrol.FlowSchemaNameCatchAll, - }, - Spec: badCatchAll, - }, - expectedErrors: field.ErrorList{field.Invalid(field.NewPath("spec"), badCatchAll, "spec of 'catch-all' must equal the fixed value")}, - }, - { - name: "catch-all flow-schema should work", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: flowcontrol.FlowSchemaNameCatchAll, - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 10000, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, - }, - DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByUserType}, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindGroup, - Group: &flowcontrol.GroupSubject{Name: user.AllUnauthenticated}, - }, - { - Kind: flowcontrol.SubjectKindGroup, - Group: &flowcontrol.GroupSubject{Name: user.AllAuthenticated}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - ClusterScope: true, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - NonResourceURLs: []string{"*"}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{}, - }, - { - name: "non-exempt flow-schema with matchingPrecedence==1 should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "fred", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 1, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "exempt", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindGroup, - Group: &flowcontrol.GroupSubject{Name: "gorp"}, - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - NonResourceURLs: []string{"*"}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("matchingPrecedence"), int32(1), "only the schema named 'exempt' may have matchingPrecedence 1")}, - }, - { - name: "flow-schema mixes * verbs/apiGroups/resources should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll, "create"}, - APIGroups: []string{flowcontrol.APIGroupAll, "tak"}, - Resources: []string{flowcontrol.ResourceAll, "tok"}, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("verbs"), []string{"*", "create"}, "if '*' is present, must not specify other verbs"), - field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("apiGroups"), []string{"*", "tak"}, "if '*' is present, must not specify other api groups"), - field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("resources"), []string{"*", "tok"}, "if '*' is present, must not specify other resources"), + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + ClusterScope: true, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + }}, }, }, - { - name: "flow-schema has both resource rules and non-resource rules should work", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - NonResourceURLs: []string{"/apis/*"}, - }, - }, - }, - }, - }, + expectedErrors: field.ErrorList{}, + }, { + name: "NamespaceEvery may not be combined with particulars", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", }, - expectedErrors: field.ErrorList{}, - }, - { - name: "flow-schema mixes * non-resource URLs should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{"*"}, - NonResourceURLs: []string{flowcontrol.NonResourceAll, "tik"}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("nonResourceRules").Index(0).Child("nonResourceURLs"), []string{"*", "tik"}, "if '*' is present, must not specify other non-resource URLs"), + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + Namespaces: []string{"foo", flowcontrol.NamespaceEvery}, + }}, + }}, }, }, - { - name: "invalid subject kind should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: "FooKind", - }, - }, - NonResourceRules: []flowcontrol.NonResourcePolicyRule{ - { - Verbs: []string{"*"}, - NonResourceURLs: []string{flowcontrol.NonResourceAll}, - }, - }, - }, - }, - }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("namespaces"), []string{"foo", flowcontrol.NamespaceEvery}, "if '*' is present, must not specify other namespaces"), + }, + }, { + name: "ResourceRule.Namespaces must be well formed", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", }, - expectedErrors: field.ErrorList{ - field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("kind"), flowcontrol.SubjectKind("FooKind"), supportedSubjectKinds.List()), + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 50, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + Namespaces: []string{"-foo"}, + }}, + }}, }, }, - { - name: "flow-schema w/ invalid verb should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{"feed"}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - }, - }, - }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("namespaces").Index(0), "-foo", nsErrIntro+`a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')`), + }, + }, { + name: "MatchingPrecedence must not be greater than 10000", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", }, - expectedErrors: field.ErrorList{ - field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("verbs"), []string{"feed"}, supportedVerbs.List()), + Spec: flowcontrol.FlowSchemaSpec{ + MatchingPrecedence: 10001, + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRulesWithSubjects{{ + Subjects: []flowcontrol.Subject{{ + Kind: flowcontrol.SubjectKindUser, + User: &flowcontrol.UserSubject{Name: "noxu"}, + }}, + ResourceRules: []flowcontrol.ResourcePolicyRule{{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + Namespaces: []string{flowcontrol.NamespaceEvery}, + }}, + }}, }, }, - { - name: "flow-schema w/ invalid priority level configuration name should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system+++$$", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("priorityLevelConfiguration").Child("name"), "system+++$$", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), - }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("matchingPrecedence"), int32(10001), "must not be greater than 10000"), }, - { - name: "flow-schema w/ service-account kind missing namespace should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindServiceAccount, - ServiceAccount: &flowcontrol.ServiceAccountSubject{ - Name: "noxu", - }, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount").Child("namespace"), "must specify namespace for service account"), - }, - }, - { - name: "flow-schema missing kind should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: "", - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("kind"), flowcontrol.SubjectKind(""), supportedSubjectKinds.List()), - }, - }, - { - name: "Omitted ResourceRule.Namespaces should fail", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - Namespaces: nil, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Required(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("namespaces"), "resource rules that are not cluster scoped must supply at least one namespace"), - }, - }, - { - name: "ClusterScope is allowed, with no Namespaces", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - ClusterScope: true, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{}, - }, - { - name: "ClusterScope is allowed with NamespaceEvery", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - ClusterScope: true, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{}, - }, - { - name: "NamespaceEvery may not be combined with particulars", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - Namespaces: []string{"foo", flowcontrol.NamespaceEvery}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("namespaces"), []string{"foo", flowcontrol.NamespaceEvery}, "if '*' is present, must not specify other namespaces"), - }, - }, - { - name: "ResourceRule.Namespaces must be well formed", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 50, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - Namespaces: []string{"-foo"}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("namespaces").Index(0), "-foo", nsErrIntro+`a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')`), - }, - }, - { - name: "MatchingPrecedence must not be greater than 10000", - flowSchema: &flowcontrol.FlowSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.FlowSchemaSpec{ - MatchingPrecedence: 10001, - PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ - Name: "system-bar", - }, - Rules: []flowcontrol.PolicyRulesWithSubjects{ - { - Subjects: []flowcontrol.Subject{ - { - Kind: flowcontrol.SubjectKindUser, - User: &flowcontrol.UserSubject{Name: "noxu"}, - }, - }, - ResourceRules: []flowcontrol.ResourcePolicyRule{ - { - Verbs: []string{flowcontrol.VerbAll}, - APIGroups: []string{flowcontrol.APIGroupAll}, - Resources: []string{flowcontrol.ResourceAll}, - Namespaces: []string{flowcontrol.NamespaceEvery}, - }, - }, - }, - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("matchingPrecedence"), int32(10001), "must not be greater than 10000"), - }, - }, - } + }} for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { errs := ValidateFlowSchema(testCase.flowSchema) @@ -926,254 +747,239 @@ func TestPriorityLevelConfigurationValidation(t *testing.T) { name string priorityLevelConfiguration *flowcontrol.PriorityLevelConfiguration expectedErrors field.ErrorList - }{ - { - name: "exempt should work", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: flowcontrol.PriorityLevelConfigurationNameExempt, - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementExempt, - }, + }{{ + name: "exempt should work", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: flowcontrol.PriorityLevelConfigurationNameExempt, }, - expectedErrors: field.ErrorList{}, - }, - { - name: "wrong exempt spec should fail", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: flowcontrol.PriorityLevelConfigurationNameExempt, - }, - Spec: badSpec, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("type"), flowcontrol.PriorityLevelEnablementLimited, "type must be 'Exempt' if and only if name is 'exempt'"), - field.Invalid(field.NewPath("spec"), badSpec, "spec of 'exempt' must equal the fixed value"), + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementExempt, }, }, - { - name: "limited requires more details", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "broken-limited", - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - }, + expectedErrors: field.ErrorList{}, + }, { + name: "wrong exempt spec should fail", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: flowcontrol.PriorityLevelConfigurationNameExempt, }, - expectedErrors: field.ErrorList{field.Required(field.NewPath("spec").Child("limited"), "must not be empty when type is Limited")}, + Spec: badSpec, }, - { - name: "max-in-flight should work", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "max-in-flight", - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ - NominalConcurrencyShares: 42, - LimitResponse: flowcontrol.LimitResponse{ - Type: flowcontrol.LimitResponseTypeReject}, - }, - }, - }, - expectedErrors: field.ErrorList{}, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("type"), flowcontrol.PriorityLevelEnablementLimited, "type must be 'Exempt' if and only if name is 'exempt'"), + field.Invalid(field.NewPath("spec"), badSpec, "spec of 'exempt' must equal the fixed value"), }, - { - name: "forbid queuing details when not queuing", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ - NominalConcurrencyShares: 100, - LimitResponse: flowcontrol.LimitResponse{ - Type: flowcontrol.LimitResponseTypeReject, - Queuing: &flowcontrol.QueuingConfiguration{ - Queues: 512, - HandSize: 4, - QueueLengthLimit: 100, - }}}}, + }, { + name: "limited requires more details", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "broken-limited", }, - expectedErrors: field.ErrorList{field.Forbidden(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing"), "must be nil if limited.limitResponse.type is not Limited")}, - }, - { - name: "wrong backstop spec should fail", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, - }, - Spec: badSpec, - }, - expectedErrors: field.ErrorList{field.Invalid(field.NewPath("spec"), badSpec, "spec of 'catch-all' must equal the fixed value")}, - }, - { - name: "backstop should work", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ - NominalConcurrencyShares: 5, - LendablePercent: pointer.Int32(0), - LimitResponse: flowcontrol.LimitResponse{ - Type: flowcontrol.LimitResponseTypeReject, - }}}, - }, - expectedErrors: field.ErrorList{}, - }, - { - name: "broken queuing level should fail", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ - NominalConcurrencyShares: 100, - LimitResponse: flowcontrol.LimitResponse{ - Type: flowcontrol.LimitResponseTypeQueue, - }}}, - }, - expectedErrors: field.ErrorList{field.Required(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing"), "must not be empty if limited.limitResponse.type is Limited")}, - }, - { - name: "normal customized priority level should work", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ - NominalConcurrencyShares: 100, - LimitResponse: flowcontrol.LimitResponse{ - Type: flowcontrol.LimitResponseTypeQueue, - Queuing: &flowcontrol.QueuingConfiguration{ - Queues: 512, - HandSize: 4, - QueueLengthLimit: 100, - }}}}, - }, - expectedErrors: field.ErrorList{}, - }, - { - name: "customized priority level w/ overflowing handSize/queues should fail 1", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ - NominalConcurrencyShares: 100, - LimitResponse: flowcontrol.LimitResponse{ - Type: flowcontrol.LimitResponseTypeQueue, - Queuing: &flowcontrol.QueuingConfiguration{ - QueueLengthLimit: 100, - Queues: 512, - HandSize: 8, - }}}}, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(8), "required entropy bits of deckSize 512 and handSize 8 should not be greater than 60"), + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, }, }, - { - name: "customized priority level w/ overflowing handSize/queues should fail 2", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ - NominalConcurrencyShares: 100, - LimitResponse: flowcontrol.LimitResponse{ - Type: flowcontrol.LimitResponseTypeQueue, - Queuing: &flowcontrol.QueuingConfiguration{ - QueueLengthLimit: 100, - Queues: 128, - HandSize: 10, - }}}}, + expectedErrors: field.ErrorList{field.Required(field.NewPath("spec").Child("limited"), "must not be empty when type is Limited")}, + }, { + name: "max-in-flight should work", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "max-in-flight", }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(10), "required entropy bits of deckSize 128 and handSize 10 should not be greater than 60"), + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 42, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeReject}, + }, }, }, - { - name: "customized priority level w/ overflowing handSize/queues should fail 3", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ - NominalConcurrencyShares: 100, - LimitResponse: flowcontrol.LimitResponse{ - Type: flowcontrol.LimitResponseTypeQueue, - Queuing: &flowcontrol.QueuingConfiguration{ - QueueLengthLimit: 100, - Queues: math.MaxInt32, - HandSize: 3, - }}}}, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(3), "required entropy bits of deckSize 2147483647 and handSize 3 should not be greater than 60"), - field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("queues"), int32(math.MaxInt32), "must not be greater than 10000000"), + expectedErrors: field.ErrorList{}, + }, { + name: "forbid queuing details when not queuing", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 100, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeReject, + Queuing: &flowcontrol.QueuingConfiguration{ + Queues: 512, + HandSize: 4, + QueueLengthLimit: 100, + }}}}, }, - { - name: "customized priority level w/ handSize=2 and queues=10^7 should work", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ - NominalConcurrencyShares: 100, - LimitResponse: flowcontrol.LimitResponse{ - Type: flowcontrol.LimitResponseTypeQueue, - Queuing: &flowcontrol.QueuingConfiguration{ - QueueLengthLimit: 100, - Queues: 10 * 1000 * 1000, // 10^7 - HandSize: 2, - }}}}, + expectedErrors: field.ErrorList{field.Forbidden(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing"), "must be nil if limited.limitResponse.type is not Limited")}, + }, { + name: "wrong backstop spec should fail", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, }, - expectedErrors: field.ErrorList{}, + Spec: badSpec, }, - { - name: "customized priority level w/ handSize greater than queues should fail", - priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "system-foo", - }, - Spec: flowcontrol.PriorityLevelConfigurationSpec{ - Type: flowcontrol.PriorityLevelEnablementLimited, - Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ - NominalConcurrencyShares: 100, - LimitResponse: flowcontrol.LimitResponse{ - Type: flowcontrol.LimitResponseTypeQueue, - Queuing: &flowcontrol.QueuingConfiguration{ - QueueLengthLimit: 100, - Queues: 7, - HandSize: 8, - }}}}, - }, - expectedErrors: field.ErrorList{ - field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(8), "should not be greater than queues (7)"), + expectedErrors: field.ErrorList{field.Invalid(field.NewPath("spec"), badSpec, "spec of 'catch-all' must equal the fixed value")}, + }, { + name: "backstop should work", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 5, + LendablePercent: pointer.Int32(0), + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeReject, + }}}, }, - } + expectedErrors: field.ErrorList{}, + }, { + name: "broken queuing level should fail", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 100, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeQueue, + }}}, + }, + expectedErrors: field.ErrorList{field.Required(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing"), "must not be empty if limited.limitResponse.type is Limited")}, + }, { + name: "normal customized priority level should work", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 100, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeQueue, + Queuing: &flowcontrol.QueuingConfiguration{ + Queues: 512, + HandSize: 4, + QueueLengthLimit: 100, + }}}}, + }, + expectedErrors: field.ErrorList{}, + }, { + name: "customized priority level w/ overflowing handSize/queues should fail 1", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 100, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeQueue, + Queuing: &flowcontrol.QueuingConfiguration{ + QueueLengthLimit: 100, + Queues: 512, + HandSize: 8, + }}}}, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(8), "required entropy bits of deckSize 512 and handSize 8 should not be greater than 60"), + }, + }, { + name: "customized priority level w/ overflowing handSize/queues should fail 2", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 100, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeQueue, + Queuing: &flowcontrol.QueuingConfiguration{ + QueueLengthLimit: 100, + Queues: 128, + HandSize: 10, + }}}}, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(10), "required entropy bits of deckSize 128 and handSize 10 should not be greater than 60"), + }, + }, { + name: "customized priority level w/ overflowing handSize/queues should fail 3", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 100, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeQueue, + Queuing: &flowcontrol.QueuingConfiguration{ + QueueLengthLimit: 100, + Queues: math.MaxInt32, + HandSize: 3, + }}}}, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(3), "required entropy bits of deckSize 2147483647 and handSize 3 should not be greater than 60"), + field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("queues"), int32(math.MaxInt32), "must not be greater than 10000000"), + }, + }, { + name: "customized priority level w/ handSize=2 and queues=10^7 should work", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 100, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeQueue, + Queuing: &flowcontrol.QueuingConfiguration{ + QueueLengthLimit: 100, + Queues: 10 * 1000 * 1000, // 10^7 + HandSize: 2, + }}}}, + }, + expectedErrors: field.ErrorList{}, + }, { + name: "customized priority level w/ handSize greater than queues should fail", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + Type: flowcontrol.PriorityLevelEnablementLimited, + Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ + NominalConcurrencyShares: 100, + LimitResponse: flowcontrol.LimitResponse{ + Type: flowcontrol.LimitResponseTypeQueue, + Queuing: &flowcontrol.QueuingConfiguration{ + QueueLengthLimit: 100, + Queues: 7, + HandSize: 8, + }}}}, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(8), "should not be greater than queues (7)"), + }, + }} for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { errs := ValidatePriorityLevelConfiguration(testCase.priorityLevelConfiguration, flowcontrolv1beta3.SchemeGroupVersion) @@ -1189,42 +995,33 @@ func TestValidateFlowSchemaStatus(t *testing.T) { name string status *flowcontrol.FlowSchemaStatus expectedErrors field.ErrorList - }{ - { - name: "empty status should work", - status: &flowcontrol.FlowSchemaStatus{}, - expectedErrors: field.ErrorList{}, + }{{ + name: "empty status should work", + status: &flowcontrol.FlowSchemaStatus{}, + expectedErrors: field.ErrorList{}, + }, { + name: "duplicate key should fail", + status: &flowcontrol.FlowSchemaStatus{ + Conditions: []flowcontrol.FlowSchemaCondition{{ + Type: "1", + }, { + Type: "1", + }}, }, - { - name: "duplicate key should fail", - status: &flowcontrol.FlowSchemaStatus{ - Conditions: []flowcontrol.FlowSchemaCondition{ - { - Type: "1", - }, - { - Type: "1", - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Duplicate(field.NewPath("status").Child("conditions").Index(1).Child("type"), flowcontrol.FlowSchemaConditionType("1")), - }, + expectedErrors: field.ErrorList{ + field.Duplicate(field.NewPath("status").Child("conditions").Index(1).Child("type"), flowcontrol.FlowSchemaConditionType("1")), }, - { - name: "missing key should fail", - status: &flowcontrol.FlowSchemaStatus{ - Conditions: []flowcontrol.FlowSchemaCondition{ - { - Type: "", - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Required(field.NewPath("status").Child("conditions").Index(0).Child("type"), "must not be empty"), - }, + }, { + name: "missing key should fail", + status: &flowcontrol.FlowSchemaStatus{ + Conditions: []flowcontrol.FlowSchemaCondition{{ + Type: "", + }}, }, - } + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("status").Child("conditions").Index(0).Child("type"), "must not be empty"), + }, + }} for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { errs := ValidateFlowSchemaStatus(testCase.status, field.NewPath("status")) @@ -1240,42 +1037,33 @@ func TestValidatePriorityLevelConfigurationStatus(t *testing.T) { name string status *flowcontrol.PriorityLevelConfigurationStatus expectedErrors field.ErrorList - }{ - { - name: "empty status should work", - status: &flowcontrol.PriorityLevelConfigurationStatus{}, - expectedErrors: field.ErrorList{}, + }{{ + name: "empty status should work", + status: &flowcontrol.PriorityLevelConfigurationStatus{}, + expectedErrors: field.ErrorList{}, + }, { + name: "duplicate key should fail", + status: &flowcontrol.PriorityLevelConfigurationStatus{ + Conditions: []flowcontrol.PriorityLevelConfigurationCondition{{ + Type: "1", + }, { + Type: "1", + }}, }, - { - name: "duplicate key should fail", - status: &flowcontrol.PriorityLevelConfigurationStatus{ - Conditions: []flowcontrol.PriorityLevelConfigurationCondition{ - { - Type: "1", - }, - { - Type: "1", - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Duplicate(field.NewPath("status").Child("conditions").Index(1).Child("type"), flowcontrol.PriorityLevelConfigurationConditionType("1")), - }, + expectedErrors: field.ErrorList{ + field.Duplicate(field.NewPath("status").Child("conditions").Index(1).Child("type"), flowcontrol.PriorityLevelConfigurationConditionType("1")), }, - { - name: "missing key should fail", - status: &flowcontrol.PriorityLevelConfigurationStatus{ - Conditions: []flowcontrol.PriorityLevelConfigurationCondition{ - { - Type: "", - }, - }, - }, - expectedErrors: field.ErrorList{ - field.Required(field.NewPath("status").Child("conditions").Index(0).Child("type"), "must not be empty"), - }, + }, { + name: "missing key should fail", + status: &flowcontrol.PriorityLevelConfigurationStatus{ + Conditions: []flowcontrol.PriorityLevelConfigurationCondition{{ + Type: "", + }}, }, - } + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("status").Child("conditions").Index(0).Child("type"), "must not be empty"), + }, + }} for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { errs := ValidatePriorityLevelConfigurationStatus(testCase.status, field.NewPath("status")) @@ -1291,73 +1079,59 @@ func TestValidateNonResourceURLPath(t *testing.T) { name string path string expectingError bool - }{ - { - name: "empty string should fail", - path: "", - expectingError: true, - }, - { - name: "no slash should fail", - path: "foo", - expectingError: true, - }, - { - name: "single slash should work", - path: "/", - expectingError: false, - }, - { - name: "continuous slash should fail", - path: "//", - expectingError: true, - }, - { - name: "/foo slash should work", - path: "/foo", - expectingError: false, - }, - { - name: "multiple continuous slashes should fail", - path: "/////", - expectingError: true, - }, - { - name: "ending up with slash should work", - path: "/apis/", - expectingError: false, - }, - { - name: "ending up with wildcard should work", - path: "/healthz/*", - expectingError: false, - }, - { - name: "single wildcard inside the path should fail", - path: "/healthz/*/foo", - expectingError: true, - }, - { - name: "white-space in the path should fail", - path: "/healthz/foo bar", - expectingError: true, - }, - { - name: "wildcard plus plain path should fail", - path: "/health*", - expectingError: true, - }, - { - name: "wildcard plus plain path should fail 2", - path: "/health*/foo", - expectingError: true, - }, - { - name: "multiple wildcard internal and suffix should fail", - path: "/*/*", - expectingError: true, - }, - } + }{{ + name: "empty string should fail", + path: "", + expectingError: true, + }, { + name: "no slash should fail", + path: "foo", + expectingError: true, + }, { + name: "single slash should work", + path: "/", + expectingError: false, + }, { + name: "continuous slash should fail", + path: "//", + expectingError: true, + }, { + name: "/foo slash should work", + path: "/foo", + expectingError: false, + }, { + name: "multiple continuous slashes should fail", + path: "/////", + expectingError: true, + }, { + name: "ending up with slash should work", + path: "/apis/", + expectingError: false, + }, { + name: "ending up with wildcard should work", + path: "/healthz/*", + expectingError: false, + }, { + name: "single wildcard inside the path should fail", + path: "/healthz/*/foo", + expectingError: true, + }, { + name: "white-space in the path should fail", + path: "/healthz/foo bar", + expectingError: true, + }, { + name: "wildcard plus plain path should fail", + path: "/health*", + expectingError: true, + }, { + name: "wildcard plus plain path should fail 2", + path: "/health*/foo", + expectingError: true, + }, { + name: "multiple wildcard internal and suffix should fail", + path: "/*/*", + expectingError: true, + }} for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { err := ValidateNonResourceURLPath(testCase.path, field.NewPath("")) @@ -1378,47 +1152,39 @@ func TestValidateLimitedPriorityLevelConfiguration(t *testing.T) { requestVersion schema.GroupVersion concurrencyShares int32 errExpected field.ErrorList - }{ - { - requestVersion: flowcontrolv1alpha1.SchemeGroupVersion, - concurrencyShares: 0, - errExpected: errExpectedFn("assuredConcurrencyShares"), - }, - { - requestVersion: flowcontrolv1beta1.SchemeGroupVersion, - concurrencyShares: 0, - errExpected: errExpectedFn("assuredConcurrencyShares"), - }, - { - requestVersion: flowcontrolv1beta2.SchemeGroupVersion, - concurrencyShares: 0, - errExpected: errExpectedFn("assuredConcurrencyShares"), - }, - { - requestVersion: flowcontrolv1beta3.SchemeGroupVersion, - concurrencyShares: 0, - errExpected: errExpectedFn("nominalConcurrencyShares"), - }, - { - // let's simulate a post v1beta3 version, we expect the - // error to return the new field introduced in v1beta3. - requestVersion: schema.GroupVersion{Group: flowcontrolv1beta3.GroupName, Version: "v1"}, - concurrencyShares: 0, - errExpected: errExpectedFn("nominalConcurrencyShares"), - }, - { - // this should never really happen in real life, the request - // context should always contain the request {group, version} - requestVersion: schema.GroupVersion{}, - concurrencyShares: 0, - errExpected: errExpectedFn("nominalConcurrencyShares"), - }, - { - requestVersion: flowcontrolv1beta3.SchemeGroupVersion, - concurrencyShares: 100, - errExpected: nil, - }, - } + }{{ + requestVersion: flowcontrolv1alpha1.SchemeGroupVersion, + concurrencyShares: 0, + errExpected: errExpectedFn("assuredConcurrencyShares"), + }, { + requestVersion: flowcontrolv1beta1.SchemeGroupVersion, + concurrencyShares: 0, + errExpected: errExpectedFn("assuredConcurrencyShares"), + }, { + requestVersion: flowcontrolv1beta2.SchemeGroupVersion, + concurrencyShares: 0, + errExpected: errExpectedFn("assuredConcurrencyShares"), + }, { + requestVersion: flowcontrolv1beta3.SchemeGroupVersion, + concurrencyShares: 0, + errExpected: errExpectedFn("nominalConcurrencyShares"), + }, { + // let's simulate a post v1beta3 version, we expect the + // error to return the new field introduced in v1beta3. + requestVersion: schema.GroupVersion{Group: flowcontrolv1beta3.GroupName, Version: "v1"}, + concurrencyShares: 0, + errExpected: errExpectedFn("nominalConcurrencyShares"), + }, { + // this should never really happen in real life, the request + // context should always contain the request {group, version} + requestVersion: schema.GroupVersion{}, + concurrencyShares: 0, + errExpected: errExpectedFn("nominalConcurrencyShares"), + }, { + requestVersion: flowcontrolv1beta3.SchemeGroupVersion, + concurrencyShares: 100, + errExpected: nil, + }} for _, test := range tests { t.Run(test.requestVersion.String(), func(t *testing.T) { @@ -1464,48 +1230,37 @@ func TestValidateLimitedPriorityLevelConfigurationWithBorrowing(t *testing.T) { lendablePercent *int32 borrowingLimitPercent *int32 errExpected field.ErrorList - }{ - { - lendablePercent: nil, - errExpected: nil, - }, - { - lendablePercent: pointer.Int32(0), - errExpected: nil, - }, - { - lendablePercent: pointer.Int32(100), - errExpected: nil, - }, - { - lendablePercent: pointer.Int32(101), - errExpected: errLendablePercentFn(101), - }, - { - lendablePercent: pointer.Int32(-1), - errExpected: errLendablePercentFn(-1), - }, - { - borrowingLimitPercent: nil, - errExpected: nil, - }, - { - borrowingLimitPercent: pointer.Int32(1), - errExpected: nil, - }, - { - borrowingLimitPercent: pointer.Int32(100), - errExpected: nil, - }, - { - borrowingLimitPercent: pointer.Int32(0), - errExpected: nil, - }, - { - borrowingLimitPercent: pointer.Int32(-1), - errExpected: errBorrowingLimitPercentFn(-1), - }, - } + }{{ + lendablePercent: nil, + errExpected: nil, + }, { + lendablePercent: pointer.Int32(0), + errExpected: nil, + }, { + lendablePercent: pointer.Int32(100), + errExpected: nil, + }, { + lendablePercent: pointer.Int32(101), + errExpected: errLendablePercentFn(101), + }, { + lendablePercent: pointer.Int32(-1), + errExpected: errLendablePercentFn(-1), + }, { + borrowingLimitPercent: nil, + errExpected: nil, + }, { + borrowingLimitPercent: pointer.Int32(1), + errExpected: nil, + }, { + borrowingLimitPercent: pointer.Int32(100), + errExpected: nil, + }, { + borrowingLimitPercent: pointer.Int32(0), + errExpected: nil, + }, { + borrowingLimitPercent: pointer.Int32(-1), + errExpected: errBorrowingLimitPercentFn(-1), + }} for _, test := range tests { t.Run(makeTestNameFn(test.lendablePercent, test.borrowingLimitPercent), func(t *testing.T) { diff --git a/pkg/apis/networking/validation/validation_test.go b/pkg/apis/networking/validation/validation_test.go index 4a5d71d9ac1..94b5ed73879 100644 --- a/pkg/apis/networking/validation/validation_test.go +++ b/pkg/apis/networking/validation/validation_test.go @@ -477,22 +477,18 @@ func TestValidateIngress(t *testing.T) { }, Spec: networking.IngressSpec{ DefaultBackend: &defaultBackend, - Rules: []networking.IngressRule{ - { - Host: "foo.bar.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/foo", - PathType: &pathTypeImplementationSpecific, - Backend: defaultBackend, - }, - }, - }, + Rules: []networking.IngressRule{{ + Host: "foo.bar.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{{ + Path: "/foo", + PathType: &pathTypeImplementationSpecific, + Backend: defaultBackend, + }}, }, }, - }, + }}, }, Status: networking.IngressStatus{ LoadBalancer: networking.IngressLoadBalancerStatus{ @@ -595,20 +591,18 @@ func TestValidateIngress(t *testing.T) { tweakIngress: func(ing *networking.Ingress) { ing.Spec.Rules[0].IngressRuleValue = networking.IngressRuleValue{ HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/foo", - PathType: &pathTypeImplementationSpecific, - Backend: networking.IngressBackend{ - Service: serviceBackend, - Resource: &api.TypedLocalObjectReference{ - APIGroup: utilpointer.String("example.com"), - Kind: "foo", - Name: "bar", - }, + Paths: []networking.HTTPIngressPath{{ + Path: "/foo", + PathType: &pathTypeImplementationSpecific, + Backend: networking.IngressBackend{ + Service: serviceBackend, + Resource: &api.TypedLocalObjectReference{ + APIGroup: utilpointer.String("example.com"), + Kind: "foo", + Name: "bar", }, }, - }, + }}, }, } }, @@ -620,20 +614,18 @@ func TestValidateIngress(t *testing.T) { tweakIngress: func(ing *networking.Ingress) { ing.Spec.Rules[0].IngressRuleValue = networking.IngressRuleValue{ HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/foo", - PathType: &pathTypeImplementationSpecific, - Backend: networking.IngressBackend{ - Service: serviceBackend, - Resource: &api.TypedLocalObjectReference{ - APIGroup: utilpointer.String("example.com"), - Kind: "foo", - Name: "bar", - }, + Paths: []networking.HTTPIngressPath{{ + Path: "/foo", + PathType: &pathTypeImplementationSpecific, + Backend: networking.IngressBackend{ + Service: serviceBackend, + Resource: &api.TypedLocalObjectReference{ + APIGroup: utilpointer.String("example.com"), + Kind: "foo", + Name: "bar", }, }, - }, + }}, }, } }, @@ -790,15 +782,13 @@ func TestValidateIngressRuleValue(t *testing.T) { t.Run(name, func(t *testing.T) { irv := &networking.IngressRuleValue{ HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: testCase.path, - PathType: &testCase.pathType, - Backend: networking.IngressBackend{ - Service: &serviceBackend, - }, + Paths: []networking.HTTPIngressPath{{ + Path: testCase.path, + PathType: &testCase.pathType, + Backend: networking.IngressBackend{ + Service: &serviceBackend, }, - }, + }}, }, } errs := validateIngressRuleValue(irv, field.NewPath("testing"), IngressValidationOptions{}) @@ -1638,22 +1628,18 @@ func TestValidateIngressTLS(t *testing.T) { }, Spec: networking.IngressSpec{ DefaultBackend: &defaultBackend, - Rules: []networking.IngressRule{ - { - Host: "foo.bar.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/foo", - PathType: &pathTypeImplementationSpecific, - Backend: defaultBackend, - }, - }, - }, + Rules: []networking.IngressRule{{ + Host: "foo.bar.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{{ + Path: "/foo", + PathType: &pathTypeImplementationSpecific, + Backend: defaultBackend, + }}, }, }, - }, + }}, }, Status: networking.IngressStatus{ LoadBalancer: networking.IngressLoadBalancerStatus{ @@ -1670,11 +1656,9 @@ func TestValidateIngressTLS(t *testing.T) { wildcardHost := "foo.*.bar.com" badWildcardTLS := newValid() badWildcardTLS.Spec.Rules[0].Host = "*.foo.bar.com" - badWildcardTLS.Spec.TLS = []networking.IngressTLS{ - { - Hosts: []string{wildcardHost}, - }, - } + badWildcardTLS.Spec.TLS = []networking.IngressTLS{{ + Hosts: []string{wildcardHost}, + }} badWildcardTLSErr := fmt.Sprintf("spec.tls[0].hosts[0]: Invalid value: '%v'", wildcardHost) errorCases[badWildcardTLSErr] = badWildcardTLS @@ -1696,11 +1680,9 @@ func TestValidateIngressTLS(t *testing.T) { wildHost := "*.bar.com" goodWildcardTLS := newValid() goodWildcardTLS.Spec.Rules[0].Host = "*.bar.com" - goodWildcardTLS.Spec.TLS = []networking.IngressTLS{ - { - Hosts: []string{wildHost}, - }, - } + goodWildcardTLS.Spec.TLS = []networking.IngressTLS{{ + Hosts: []string{wildHost}, + }} validCases[fmt.Sprintf("spec.tls[0].hosts: Valid value: '%v'", wildHost)] = goodWildcardTLS for k, v := range validCases { errs := validateIngress(&v, IngressValidationOptions{}) @@ -1731,21 +1713,17 @@ func TestValidateEmptyIngressTLS(t *testing.T) { Namespace: metav1.NamespaceDefault, }, Spec: networking.IngressSpec{ - Rules: []networking.IngressRule{ - { - Host: "foo.bar.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - PathType: &pathTypeImplementationSpecific, - Backend: defaultBackend, - }, - }, - }, + Rules: []networking.IngressRule{{ + Host: "foo.bar.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{{ + PathType: &pathTypeImplementationSpecific, + Backend: defaultBackend, + }}, }, }, - }, + }}, }, } } @@ -1757,11 +1735,9 @@ func TestValidateEmptyIngressTLS(t *testing.T) { } validCases[fmt.Sprintf("spec.tls[0]: Valid value: %v", goodEmptyTLS.Spec.TLS[0])] = goodEmptyTLS goodEmptyHosts := newValid() - goodEmptyHosts.Spec.TLS = []networking.IngressTLS{ - { - Hosts: []string{}, - }, - } + goodEmptyHosts.Spec.TLS = []networking.IngressTLS{{ + Hosts: []string{}, + }} validCases[fmt.Sprintf("spec.tls[0]: Valid value: %v", goodEmptyHosts.Spec.TLS[0])] = goodEmptyHosts for k, v := range validCases { errs := validateIngress(&v, IngressValidationOptions{}) @@ -1791,21 +1767,17 @@ func TestValidateIngressStatusUpdate(t *testing.T) { }, Spec: networking.IngressSpec{ DefaultBackend: &defaultBackend, - Rules: []networking.IngressRule{ - { - Host: "foo.bar.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/foo", - Backend: defaultBackend, - }, - }, - }, + Rules: []networking.IngressRule{{ + Host: "foo.bar.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{{ + Path: "/foo", + Backend: defaultBackend, + }}, }, }, - }, + }}, }, Status: networking.IngressStatus{ LoadBalancer: networking.IngressLoadBalancerStatus{ @@ -1867,17 +1839,13 @@ func TestValidateIngressStatusUpdate(t *testing.T) { func makeNodeSelector(key string, op api.NodeSelectorOperator, values []string) *api.NodeSelector { return &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: key, - Operator: op, - Values: values, - }, - }, - }, - }, + NodeSelectorTerms: []api.NodeSelectorTerm{{ + MatchExpressions: []api.NodeSelectorRequirement{{ + Key: key, + Operator: op, + Values: values, + }}, + }}, } } @@ -1901,59 +1869,49 @@ func TestValidateClusterCIDR(t *testing.T) { name string cc *networking.ClusterCIDR expectErr bool - }{ - { - name: "valid SingleStack IPv4 ClusterCIDR", - cc: makeClusterCIDR(8, "10.1.0.0/16", "", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: false, - }, - { - name: "valid SingleStack IPv4 ClusterCIDR, perNodeHostBits = maxPerNodeHostBits", - cc: makeClusterCIDR(16, "10.1.0.0/16", "", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: false, - }, - { - name: "valid SingleStack IPv4 ClusterCIDR, perNodeHostBits > minPerNodeHostBits", - cc: makeClusterCIDR(4, "10.1.0.0/16", "", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: false, - }, - { - name: "valid SingleStack IPv6 ClusterCIDR", - cc: makeClusterCIDR(8, "", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: false, - }, - { - name: "valid SingleStack IPv6 ClusterCIDR, perNodeHostBits = maxPerNodeHostBit", - cc: makeClusterCIDR(64, "", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: false, - }, - { - name: "valid SingleStack IPv6 ClusterCIDR, perNodeHostBits > minPerNodeHostBit", - cc: makeClusterCIDR(4, "", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: false, - }, - { - name: "valid SingleStack IPv6 ClusterCIDR perNodeHostBits=100", - cc: makeClusterCIDR(100, "", "fd00:1:1::/16", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: false, - }, - { - name: "valid DualStack ClusterCIDR", - cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: false, - }, - { - name: "valid DualStack ClusterCIDR, no NodeSelector", - cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:1:1::/64", nil), - expectErr: false, - }, + }{{ + name: "valid SingleStack IPv4 ClusterCIDR", + cc: makeClusterCIDR(8, "10.1.0.0/16", "", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: false, + }, { + name: "valid SingleStack IPv4 ClusterCIDR, perNodeHostBits = maxPerNodeHostBits", + cc: makeClusterCIDR(16, "10.1.0.0/16", "", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: false, + }, { + name: "valid SingleStack IPv4 ClusterCIDR, perNodeHostBits > minPerNodeHostBits", + cc: makeClusterCIDR(4, "10.1.0.0/16", "", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: false, + }, { + name: "valid SingleStack IPv6 ClusterCIDR", + cc: makeClusterCIDR(8, "", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: false, + }, { + name: "valid SingleStack IPv6 ClusterCIDR, perNodeHostBits = maxPerNodeHostBit", + cc: makeClusterCIDR(64, "", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: false, + }, { + name: "valid SingleStack IPv6 ClusterCIDR, perNodeHostBits > minPerNodeHostBit", + cc: makeClusterCIDR(4, "", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: false, + }, { + name: "valid SingleStack IPv6 ClusterCIDR perNodeHostBits=100", + cc: makeClusterCIDR(100, "", "fd00:1:1::/16", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: false, + }, { + name: "valid DualStack ClusterCIDR", + cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: false, + }, { + name: "valid DualStack ClusterCIDR, no NodeSelector", + cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:1:1::/64", nil), + expectErr: false, + }, // Failure cases. { name: "invalid ClusterCIDR, no IPv4 or IPv6 CIDR", cc: makeClusterCIDR(8, "", "", nil), expectErr: true, - }, - { + }, { name: "invalid ClusterCIDR, invalid nodeSelector", cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:1:1::/64", makeNodeSelector("NoUppercaseOrSpecialCharsLike=Equals", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, @@ -1963,13 +1921,11 @@ func TestValidateClusterCIDR(t *testing.T) { name: "invalid SingleStack IPv4 ClusterCIDR, invalid spec.IPv4", cc: makeClusterCIDR(8, "test", "", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, - }, - { + }, { name: "invalid Singlestack IPv4 ClusterCIDR, perNodeHostBits > maxPerNodeHostBits", cc: makeClusterCIDR(100, "10.1.0.0/16", "", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, - }, - { + }, { name: "invalid SingleStack IPv4 ClusterCIDR, perNodeHostBits < minPerNodeHostBits", cc: makeClusterCIDR(2, "10.1.0.0/16", "", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, @@ -1979,18 +1935,15 @@ func TestValidateClusterCIDR(t *testing.T) { name: "invalid SingleStack IPv6 ClusterCIDR, invalid spec.IPv6", cc: makeClusterCIDR(8, "", "testv6", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, - }, - { + }, { name: "invalid SingleStack IPv6 ClusterCIDR, valid IPv4 CIDR in spec.IPv6", cc: makeClusterCIDR(8, "", "10.2.0.0/16", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, - }, - { + }, { name: "invalid SingleStack IPv6 ClusterCIDR, invalid perNodeHostBits > maxPerNodeHostBits", cc: makeClusterCIDR(12, "", "fd00::/120", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, - }, - { + }, { name: "invalid SingleStack IPv6 ClusterCIDR, invalid perNodeHostBits < minPerNodeHostBits", cc: makeClusterCIDR(3, "", "fd00::/120", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, @@ -2000,18 +1953,15 @@ func TestValidateClusterCIDR(t *testing.T) { name: "invalid DualStack ClusterCIDR, valid spec.IPv4, invalid spec.IPv6", cc: makeClusterCIDR(8, "10.1.0.0/16", "testv6", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, - }, - { + }, { name: "invalid DualStack ClusterCIDR, valid spec.IPv6, invalid spec.IPv4", cc: makeClusterCIDR(8, "testv4", "fd00::/120", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, - }, - { + }, { name: "invalid DualStack ClusterCIDR, invalid perNodeHostBits > maxPerNodeHostBits", cc: makeClusterCIDR(24, "10.1.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, - }, - { + }, { name: "invalid DualStack ClusterCIDR, valid IPv6 CIDR in spec.IPv4", cc: makeClusterCIDR(8, "fd00::/120", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), expectErr: true, @@ -2038,33 +1988,27 @@ func TestValidateClusterConfigUpdate(t *testing.T) { name string cc *networking.ClusterCIDR expectErr bool - }{ - { - name: "Successful update, no changes to ClusterCIDR.Spec", - cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: false, - }, - { - name: "Failed update, update spec.PerNodeHostBits", - cc: makeClusterCIDR(12, "10.1.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: true, - }, - { - name: "Failed update, update spec.IPv4", - cc: makeClusterCIDR(8, "10.2.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: true, - }, - { - name: "Failed update, update spec.IPv6", - cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:2:/112", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), - expectErr: true, - }, - { - name: "Failed update, update spec.NodeSelector", - cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar2"})), - expectErr: true, - }, - } + }{{ + name: "Successful update, no changes to ClusterCIDR.Spec", + cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: false, + }, { + name: "Failed update, update spec.PerNodeHostBits", + cc: makeClusterCIDR(12, "10.1.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: true, + }, { + name: "Failed update, update spec.IPv4", + cc: makeClusterCIDR(8, "10.2.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: true, + }, { + name: "Failed update, update spec.IPv6", + cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:2:/112", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"})), + expectErr: true, + }, { + name: "Failed update, update spec.NodeSelector", + cc: makeClusterCIDR(8, "10.1.0.0/16", "fd00:1:1::/64", makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar2"})), + expectErr: true, + }} for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { err := ValidateClusterCIDRUpdate(testCase.cc, oldCCC) @@ -2244,15 +2188,14 @@ func TestValidateIPAddressUpdate(t *testing.T) { name string new func(svc *networking.IPAddress) *networking.IPAddress expectErr bool - }{ - { - name: "Successful update, no changes", - new: func(old *networking.IPAddress) *networking.IPAddress { - out := old.DeepCopy() - return out - }, - expectErr: false, + }{{ + name: "Successful update, no changes", + new: func(old *networking.IPAddress) *networking.IPAddress { + out := old.DeepCopy() + return out }, + expectErr: false, + }, { name: "Failed update, update spec.ParentRef", @@ -2267,8 +2210,7 @@ func TestValidateIPAddressUpdate(t *testing.T) { return out }, expectErr: true, - }, - { + }, { name: "Failed update, delete spec.ParentRef", new: func(svc *networking.IPAddress) *networking.IPAddress { out := svc.DeepCopy() diff --git a/pkg/apis/node/validation/validation_test.go b/pkg/apis/node/validation/validation_test.go index da55f458407..51d62d8d698 100644 --- a/pkg/apis/node/validation/validation_test.go +++ b/pkg/apis/node/validation/validation_test.go @@ -134,17 +134,15 @@ func TestValidateOverhead(t *testing.T) { successCase := []struct { Name string overhead *node.Overhead - }{ - { - Name: "Overhead with valid cpu and memory resources", - overhead: &node.Overhead{ - PodFixed: core.ResourceList{ - core.ResourceName(core.ResourceCPU): resource.MustParse("10"), - core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), - }, + }{{ + Name: "Overhead with valid cpu and memory resources", + overhead: &node.Overhead{ + PodFixed: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), }, }, - } + }} for _, tc := range successCase { rc := &node.RuntimeClass{ @@ -160,16 +158,14 @@ func TestValidateOverhead(t *testing.T) { errorCase := []struct { Name string overhead *node.Overhead - }{ - { - Name: "Invalid Resources", - overhead: &node.Overhead{ - PodFixed: core.ResourceList{ - core.ResourceName("my.org"): resource.MustParse("10m"), - }, + }{{ + Name: "Invalid Resources", + overhead: &node.Overhead{ + PodFixed: core.ResourceList{ + core.ResourceName("my.org"): resource.MustParse("10m"), }, }, - } + }} for _, tc := range errorCase { rc := &node.RuntimeClass{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, diff --git a/pkg/apis/policy/validation/validation_test.go b/pkg/apis/policy/validation/validation_test.go index f7e7334d003..b28b239a64c 100644 --- a/pkg/apis/policy/validation/validation_test.go +++ b/pkg/apis/policy/validation/validation_test.go @@ -107,40 +107,35 @@ func TestValidateUnhealthyPodEvictionPolicyDisruptionBudgetSpec(t *testing.T) { name string pdbSpec policy.PodDisruptionBudgetSpec expectErr bool - }{ - { - name: "valid nil UnhealthyPodEvictionPolicy", - pdbSpec: policy.PodDisruptionBudgetSpec{ - MinAvailable: &c1, - UnhealthyPodEvictionPolicy: nil, - }, - expectErr: false, + }{{ + name: "valid nil UnhealthyPodEvictionPolicy", + pdbSpec: policy.PodDisruptionBudgetSpec{ + MinAvailable: &c1, + UnhealthyPodEvictionPolicy: nil, }, - { - name: "valid UnhealthyPodEvictionPolicy", - pdbSpec: policy.PodDisruptionBudgetSpec{ - MinAvailable: &c1, - UnhealthyPodEvictionPolicy: &alwaysAllowPolicy, - }, - expectErr: false, + expectErr: false, + }, { + name: "valid UnhealthyPodEvictionPolicy", + pdbSpec: policy.PodDisruptionBudgetSpec{ + MinAvailable: &c1, + UnhealthyPodEvictionPolicy: &alwaysAllowPolicy, }, - { - name: "empty UnhealthyPodEvictionPolicy", - pdbSpec: policy.PodDisruptionBudgetSpec{ - MinAvailable: &c1, - UnhealthyPodEvictionPolicy: new(policy.UnhealthyPodEvictionPolicyType), - }, - expectErr: true, + expectErr: false, + }, { + name: "empty UnhealthyPodEvictionPolicy", + pdbSpec: policy.PodDisruptionBudgetSpec{ + MinAvailable: &c1, + UnhealthyPodEvictionPolicy: new(policy.UnhealthyPodEvictionPolicyType), }, - { - name: "invalid UnhealthyPodEvictionPolicy", - pdbSpec: policy.PodDisruptionBudgetSpec{ - MinAvailable: &c1, - UnhealthyPodEvictionPolicy: &invalidPolicy, - }, - expectErr: true, + expectErr: true, + }, { + name: "invalid UnhealthyPodEvictionPolicy", + pdbSpec: policy.PodDisruptionBudgetSpec{ + MinAvailable: &c1, + UnhealthyPodEvictionPolicy: &invalidPolicy, }, - } + expectErr: true, + }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -162,128 +157,112 @@ func TestValidatePodDisruptionBudgetStatus(t *testing.T) { name string pdbStatus policy.PodDisruptionBudgetStatus expectErrForVersion map[schema.GroupVersion]bool - }{ - { - name: "DisruptionsAllowed: 10", - pdbStatus: policy.PodDisruptionBudgetStatus{ - DisruptionsAllowed: 10, - }, - expectErrForVersion: map[schema.GroupVersion]bool{ - policy.SchemeGroupVersion: expectNoErrors, - policyv1beta1.SchemeGroupVersion: expectNoErrors, - }, + }{{ + name: "DisruptionsAllowed: 10", + pdbStatus: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: 10, }, - { - name: "CurrentHealthy: 5", - pdbStatus: policy.PodDisruptionBudgetStatus{ - CurrentHealthy: 5, - }, - expectErrForVersion: map[schema.GroupVersion]bool{ - policy.SchemeGroupVersion: expectNoErrors, - policyv1beta1.SchemeGroupVersion: expectNoErrors, - }, + expectErrForVersion: map[schema.GroupVersion]bool{ + policy.SchemeGroupVersion: expectNoErrors, + policyv1beta1.SchemeGroupVersion: expectNoErrors, }, - { - name: "DesiredHealthy: 3", - pdbStatus: policy.PodDisruptionBudgetStatus{ - DesiredHealthy: 3, - }, - expectErrForVersion: map[schema.GroupVersion]bool{ - policy.SchemeGroupVersion: expectNoErrors, - policyv1beta1.SchemeGroupVersion: expectNoErrors, - }, + }, { + name: "CurrentHealthy: 5", + pdbStatus: policy.PodDisruptionBudgetStatus{ + CurrentHealthy: 5, }, - { - name: "ExpectedPods: 2", - pdbStatus: policy.PodDisruptionBudgetStatus{ - ExpectedPods: 2, - }, - expectErrForVersion: map[schema.GroupVersion]bool{ - policy.SchemeGroupVersion: expectNoErrors, - policyv1beta1.SchemeGroupVersion: expectNoErrors, - }, + expectErrForVersion: map[schema.GroupVersion]bool{ + policy.SchemeGroupVersion: expectNoErrors, + policyv1beta1.SchemeGroupVersion: expectNoErrors, }, - { - name: "DisruptionsAllowed: -10", - pdbStatus: policy.PodDisruptionBudgetStatus{ - DisruptionsAllowed: -10, - }, - expectErrForVersion: map[schema.GroupVersion]bool{ - policy.SchemeGroupVersion: expectErrors, - policyv1beta1.SchemeGroupVersion: expectNoErrors, - }, + }, { + name: "DesiredHealthy: 3", + pdbStatus: policy.PodDisruptionBudgetStatus{ + DesiredHealthy: 3, }, - { - name: "CurrentHealthy: -5", - pdbStatus: policy.PodDisruptionBudgetStatus{ - CurrentHealthy: -5, - }, - expectErrForVersion: map[schema.GroupVersion]bool{ - policy.SchemeGroupVersion: expectErrors, - policyv1beta1.SchemeGroupVersion: expectNoErrors, - }, + expectErrForVersion: map[schema.GroupVersion]bool{ + policy.SchemeGroupVersion: expectNoErrors, + policyv1beta1.SchemeGroupVersion: expectNoErrors, }, - { - name: "DesiredHealthy: -3", - pdbStatus: policy.PodDisruptionBudgetStatus{ - DesiredHealthy: -3, - }, - expectErrForVersion: map[schema.GroupVersion]bool{ - policy.SchemeGroupVersion: expectErrors, - policyv1beta1.SchemeGroupVersion: expectNoErrors, - }, + }, { + name: "ExpectedPods: 2", + pdbStatus: policy.PodDisruptionBudgetStatus{ + ExpectedPods: 2, }, - { - name: "ExpectedPods: -2", - pdbStatus: policy.PodDisruptionBudgetStatus{ - ExpectedPods: -2, - }, - expectErrForVersion: map[schema.GroupVersion]bool{ - policy.SchemeGroupVersion: expectErrors, - policyv1beta1.SchemeGroupVersion: expectNoErrors, - }, + expectErrForVersion: map[schema.GroupVersion]bool{ + policy.SchemeGroupVersion: expectNoErrors, + policyv1beta1.SchemeGroupVersion: expectNoErrors, }, - { - name: "Conditions valid", - pdbStatus: policy.PodDisruptionBudgetStatus{ - Conditions: []metav1.Condition{ - { - Type: policyv1beta1.DisruptionAllowedCondition, - Status: metav1.ConditionTrue, - LastTransitionTime: metav1.Time{ - Time: time.Now().Add(-5 * time.Minute), - }, - Reason: policyv1beta1.SufficientPodsReason, - Message: "message", - ObservedGeneration: 3, - }, + }, { + name: "DisruptionsAllowed: -10", + pdbStatus: policy.PodDisruptionBudgetStatus{ + DisruptionsAllowed: -10, + }, + expectErrForVersion: map[schema.GroupVersion]bool{ + policy.SchemeGroupVersion: expectErrors, + policyv1beta1.SchemeGroupVersion: expectNoErrors, + }, + }, { + name: "CurrentHealthy: -5", + pdbStatus: policy.PodDisruptionBudgetStatus{ + CurrentHealthy: -5, + }, + expectErrForVersion: map[schema.GroupVersion]bool{ + policy.SchemeGroupVersion: expectErrors, + policyv1beta1.SchemeGroupVersion: expectNoErrors, + }, + }, { + name: "DesiredHealthy: -3", + pdbStatus: policy.PodDisruptionBudgetStatus{ + DesiredHealthy: -3, + }, + expectErrForVersion: map[schema.GroupVersion]bool{ + policy.SchemeGroupVersion: expectErrors, + policyv1beta1.SchemeGroupVersion: expectNoErrors, + }, + }, { + name: "ExpectedPods: -2", + pdbStatus: policy.PodDisruptionBudgetStatus{ + ExpectedPods: -2, + }, + expectErrForVersion: map[schema.GroupVersion]bool{ + policy.SchemeGroupVersion: expectErrors, + policyv1beta1.SchemeGroupVersion: expectNoErrors, + }, + }, { + name: "Conditions valid", + pdbStatus: policy.PodDisruptionBudgetStatus{ + Conditions: []metav1.Condition{{ + Type: policyv1beta1.DisruptionAllowedCondition, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Time{ + Time: time.Now().Add(-5 * time.Minute), }, - }, - expectErrForVersion: map[schema.GroupVersion]bool{ - policy.SchemeGroupVersion: expectNoErrors, - policyv1beta1.SchemeGroupVersion: expectNoErrors, - }, + Reason: policyv1beta1.SufficientPodsReason, + Message: "message", + ObservedGeneration: 3, + }}, }, - { - name: "Conditions not valid", - pdbStatus: policy.PodDisruptionBudgetStatus{ - Conditions: []metav1.Condition{ - { - Type: policyv1beta1.DisruptionAllowedCondition, - Status: metav1.ConditionTrue, - }, - { - Type: policyv1beta1.DisruptionAllowedCondition, - Status: metav1.ConditionFalse, - }, - }, - }, - expectErrForVersion: map[schema.GroupVersion]bool{ - policy.SchemeGroupVersion: expectErrors, - policyv1beta1.SchemeGroupVersion: expectErrors, - }, + expectErrForVersion: map[schema.GroupVersion]bool{ + policy.SchemeGroupVersion: expectNoErrors, + policyv1beta1.SchemeGroupVersion: expectNoErrors, }, - } + }, { + name: "Conditions not valid", + pdbStatus: policy.PodDisruptionBudgetStatus{ + Conditions: []metav1.Condition{{ + Type: policyv1beta1.DisruptionAllowedCondition, + Status: metav1.ConditionTrue, + }, { + Type: policyv1beta1.DisruptionAllowedCondition, + Status: metav1.ConditionFalse, + }}, + }, + expectErrForVersion: map[schema.GroupVersion]bool{ + policy.SchemeGroupVersion: expectErrors, + policyv1beta1.SchemeGroupVersion: expectErrors, + }, + }} for _, tc := range testCases { for apiVersion, expectErrors := range tc.expectErrForVersion { @@ -1165,23 +1144,19 @@ func TestAllowEphemeralVolumeType(t *testing.T) { description string hasGenericVolume bool psp func() *policy.PodSecurityPolicy - }{ - { - description: "PodSecurityPolicySpec Without GenericVolume", - hasGenericVolume: false, - psp: pspWithoutGenericVolume, - }, - { - description: "PodSecurityPolicySpec With GenericVolume", - hasGenericVolume: true, - psp: pspWithGenericVolume, - }, - { - description: "is nil", - hasGenericVolume: false, - psp: pspNil, - }, - } + }{{ + description: "PodSecurityPolicySpec Without GenericVolume", + hasGenericVolume: false, + psp: pspWithoutGenericVolume, + }, { + description: "PodSecurityPolicySpec With GenericVolume", + hasGenericVolume: true, + psp: pspWithGenericVolume, + }, { + description: "is nil", + hasGenericVolume: false, + psp: pspNil, + }} for _, oldPSPInfo := range pspInfo { for _, newPSPInfo := range pspInfo { diff --git a/pkg/apis/rbac/validation/validation_test.go b/pkg/apis/rbac/validation/validation_test.go index 76a9ece8142..f0bf6b22944 100644 --- a/pkg/apis/rbac/validation/validation_test.go +++ b/pkg/apis/rbac/validation/validation_test.go @@ -402,12 +402,10 @@ func TestValidateRoleNonResourceURL(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "default", }, - Rules: []rbac.PolicyRule{ - { - Verbs: []string{"get"}, - NonResourceURLs: []string{"/*"}, - }, - }, + Rules: []rbac.PolicyRule{{ + Verbs: []string{"get"}, + NonResourceURLs: []string{"/*"}, + }}, }, wantErr: false, }.test(t) @@ -420,13 +418,11 @@ func TestValidateRoleNamespacedNonResourceURL(t *testing.T) { Namespace: "default", Name: "default", }, - Rules: []rbac.PolicyRule{ - { - // non-resource URLs are invalid for namespaced rules - Verbs: []string{"get"}, - NonResourceURLs: []string{"/*"}, - }, - }, + Rules: []rbac.PolicyRule{{ + // non-resource URLs are invalid for namespaced rules + Verbs: []string{"get"}, + NonResourceURLs: []string{"/*"}, + }}, }, wantErr: true, errType: field.ErrorTypeInvalid, @@ -440,12 +436,10 @@ func TestValidateRoleNonResourceURLNoVerbs(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "default", }, - Rules: []rbac.PolicyRule{ - { - Verbs: []string{}, - NonResourceURLs: []string{"/*"}, - }, - }, + Rules: []rbac.PolicyRule{{ + Verbs: []string{}, + NonResourceURLs: []string{"/*"}, + }}, }, wantErr: true, errType: field.ErrorTypeRequired, @@ -460,14 +454,12 @@ func TestValidateRoleMixedNonResourceAndResource(t *testing.T) { Name: "default", Namespace: "default", }, - Rules: []rbac.PolicyRule{ - { - Verbs: []string{"get"}, - NonResourceURLs: []string{"/*"}, - APIGroups: []string{"v1"}, - Resources: []string{"pods"}, - }, - }, + Rules: []rbac.PolicyRule{{ + Verbs: []string{"get"}, + NonResourceURLs: []string{"/*"}, + APIGroups: []string{"v1"}, + Resources: []string{"pods"}, + }}, }, wantErr: true, errType: field.ErrorTypeInvalid, @@ -482,13 +474,11 @@ func TestValidateRoleValidResource(t *testing.T) { Name: "default", Namespace: "default", }, - Rules: []rbac.PolicyRule{ - { - Verbs: []string{"get"}, - APIGroups: []string{"v1"}, - Resources: []string{"pods"}, - }, - }, + Rules: []rbac.PolicyRule{{ + Verbs: []string{"get"}, + APIGroups: []string{"v1"}, + Resources: []string{"pods"}, + }}, }, wantErr: false, }.test(t) @@ -501,12 +491,10 @@ func TestValidateRoleNoAPIGroup(t *testing.T) { Name: "default", Namespace: "default", }, - Rules: []rbac.PolicyRule{ - { - Verbs: []string{"get"}, - Resources: []string{"pods"}, - }, - }, + Rules: []rbac.PolicyRule{{ + Verbs: []string{"get"}, + Resources: []string{"pods"}, + }}, }, wantErr: true, errType: field.ErrorTypeRequired, @@ -521,12 +509,10 @@ func TestValidateRoleNoResources(t *testing.T) { Name: "default", Namespace: "default", }, - Rules: []rbac.PolicyRule{ - { - Verbs: []string{"get"}, - APIGroups: []string{"v1"}, - }, - }, + Rules: []rbac.PolicyRule{{ + Verbs: []string{"get"}, + APIGroups: []string{"v1"}, + }}, }, wantErr: true, errType: field.ErrorTypeRequired, diff --git a/pkg/apis/storage/validation/validation_test.go b/pkg/apis/storage/validation/validation_test.go index a0a45450fc7..2abc1aedbca 100644 --- a/pkg/apis/storage/validation/validation_test.go +++ b/pkg/apis/storage/validation/validation_test.go @@ -58,42 +58,37 @@ func TestValidateStorageClass(t *testing.T) { deleteReclaimPolicy := api.PersistentVolumeReclaimPolicy("Delete") retainReclaimPolicy := api.PersistentVolumeReclaimPolicy("Retain") recycleReclaimPolicy := api.PersistentVolumeReclaimPolicy("Recycle") - successCases := []storage.StorageClass{ - { - // empty parameters - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Provisioner: "kubernetes.io/foo-provisioner", - Parameters: map[string]string{}, - ReclaimPolicy: &deleteReclaimPolicy, - VolumeBindingMode: &immediateMode1, + successCases := []storage.StorageClass{{ + // empty parameters + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Provisioner: "kubernetes.io/foo-provisioner", + Parameters: map[string]string{}, + ReclaimPolicy: &deleteReclaimPolicy, + VolumeBindingMode: &immediateMode1, + }, { + // nil parameters + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Provisioner: "kubernetes.io/foo-provisioner", + ReclaimPolicy: &deleteReclaimPolicy, + VolumeBindingMode: &immediateMode1, + }, { + // some parameters + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Provisioner: "kubernetes.io/foo-provisioner", + Parameters: map[string]string{ + "kubernetes.io/foo-parameter": "free/form/string", + "foo-parameter": "free-form-string", + "foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}", }, - { - // nil parameters - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Provisioner: "kubernetes.io/foo-provisioner", - ReclaimPolicy: &deleteReclaimPolicy, - VolumeBindingMode: &immediateMode1, - }, - { - // some parameters - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Provisioner: "kubernetes.io/foo-provisioner", - Parameters: map[string]string{ - "kubernetes.io/foo-parameter": "free/form/string", - "foo-parameter": "free-form-string", - "foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}", - }, - ReclaimPolicy: &deleteReclaimPolicy, - VolumeBindingMode: &immediateMode1, - }, - { - // retain reclaimPolicy - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Provisioner: "kubernetes.io/foo-provisioner", - ReclaimPolicy: &retainReclaimPolicy, - VolumeBindingMode: &immediateMode1, - }, - } + ReclaimPolicy: &deleteReclaimPolicy, + VolumeBindingMode: &immediateMode1, + }, { + // retain reclaimPolicy + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Provisioner: "kubernetes.io/foo-provisioner", + ReclaimPolicy: &retainReclaimPolicy, + VolumeBindingMode: &immediateMode1, + }} // Success cases are expected to pass validation. for k, v := range successCases { @@ -160,223 +155,208 @@ func TestValidateStorageClass(t *testing.T) { func TestVolumeAttachmentValidation(t *testing.T) { volumeName := "pv-name" empty := "" - migrationEnabledSuccessCases := []storage.VolumeAttachment{ - { - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, - NodeName: "mynode", + migrationEnabledSuccessCases := []storage.VolumeAttachment{{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "foo-with-inlinespec"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + InlineVolumeSpec: &inlineSpec, + }, + NodeName: "mynode", + }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: "foo-with-status"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": "bar", + }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", }, }, - { - ObjectMeta: metav1.ObjectMeta{Name: "foo-with-inlinespec"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{ - InlineVolumeSpec: &inlineSpec, - }, - NodeName: "mynode", + }, { + ObjectMeta: metav1.ObjectMeta{Name: "foo-with-inlinespec-and-status"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + InlineVolumeSpec: &inlineSpec, + }, + NodeName: "mynode", + }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": "bar", + }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", }, }, - { - ObjectMeta: metav1.ObjectMeta{Name: "foo-with-status"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, - NodeName: "mynode", - }, - Status: storage.VolumeAttachmentStatus{ - Attached: true, - AttachmentMetadata: map[string]string{ - "foo": "bar", - }, - AttachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: "hello world", - }, - DetachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: "hello world", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "foo-with-inlinespec-and-status"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{ - InlineVolumeSpec: &inlineSpec, - }, - NodeName: "mynode", - }, - Status: storage.VolumeAttachmentStatus{ - Attached: true, - AttachmentMetadata: map[string]string{ - "foo": "bar", - }, - AttachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: "hello world", - }, - DetachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: "hello world", - }, - }, - }, - } + }} for _, volumeAttachment := range migrationEnabledSuccessCases { if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) != 0 { t.Errorf("expected success: %v %v", volumeAttachment, errs) } } - migrationEnabledErrorCases := []storage.VolumeAttachment{ - { - // Empty attacher name - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "", - NodeName: "mynode", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, + migrationEnabledErrorCases := []storage.VolumeAttachment{{ + // Empty attacher name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "", + NodeName: "mynode", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, }, }, - { - // Empty node name - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - NodeName: "", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, + }, { + // Empty node name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, }, }, - { - // No volume name - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - NodeName: "node", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: nil, - }, + }, { + // No volume name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: nil, }, }, - { - // Empty volume name - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - NodeName: "node", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &empty, - }, + }, { + // Empty volume name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &empty, }, }, - { - // Too long error message - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - NodeName: "node", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, - }, - Status: storage.VolumeAttachmentStatus{ - Attached: true, - AttachmentMetadata: map[string]string{ - "foo": "bar", - }, - AttachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: "hello world", - }, - DetachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: strings.Repeat("a", maxVolumeErrorMessageSize+1), - }, + }, { + // Too long error message + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, }, }, - { - // Too long metadata - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - NodeName: "node", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": "bar", }, - Status: storage.VolumeAttachmentStatus{ - Attached: true, - AttachmentMetadata: map[string]string{ - "foo": strings.Repeat("a", maxAttachedVolumeMetadataSize), - }, - AttachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: "hello world", - }, - DetachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: "hello world", - }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: strings.Repeat("a", maxVolumeErrorMessageSize+1), }, }, - { - // VolumeAttachmentSource with no PersistentVolumeName nor InlineSpec - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - NodeName: "node", - Source: storage.VolumeAttachmentSource{}, + }, { + // Too long metadata + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, }, }, - { - // VolumeAttachmentSource with PersistentVolumeName and InlineSpec - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - NodeName: "node", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - InlineVolumeSpec: &inlineSpec, - }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": strings.Repeat("a", maxAttachedVolumeMetadataSize), + }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", }, }, - { - // VolumeAttachmentSource with InlineSpec without CSI PV Source - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - NodeName: "node", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - InlineVolumeSpec: &api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - FlexVolume: &api.FlexPersistentVolumeSource{ - Driver: "kubernetes.io/blue", - FSType: "ext4", - }, - }, - StorageClassName: "test-storage-class", + }, { + // VolumeAttachmentSource with no PersistentVolumeName nor InlineSpec + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{}, + }, + }, { + // VolumeAttachmentSource with PersistentVolumeName and InlineSpec + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + InlineVolumeSpec: &inlineSpec, + }, + }, + }, { + // VolumeAttachmentSource with InlineSpec without CSI PV Source + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + InlineVolumeSpec: &api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), }, + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + PersistentVolumeSource: api.PersistentVolumeSource{ + FlexVolume: &api.FlexPersistentVolumeSource{ + Driver: "kubernetes.io/blue", + FSType: "ext4", + }, + }, + StorageClassName: "test-storage-class", }, }, }, - } + }} for _, volumeAttachment := range migrationEnabledErrorCases { if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) == 0 { @@ -398,40 +378,37 @@ func TestVolumeAttachmentUpdateValidation(t *testing.T) { }, } - successCases := []storage.VolumeAttachment{ - { - // no change - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{}, - NodeName: "mynode", + successCases := []storage.VolumeAttachment{{ + // no change + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{}, + NodeName: "mynode", + }, + }, { + // modify status + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{}, + NodeName: "mynode", + }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": "bar", + }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", }, }, - { - // modify status - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{}, - NodeName: "mynode", - }, - Status: storage.VolumeAttachmentStatus{ - Attached: true, - AttachmentMetadata: map[string]string{ - "foo": "bar", - }, - AttachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: "hello world", - }, - DetachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: "hello world", - }, - }, - }, - } + }} for _, volumeAttachment := range successCases { volumeAttachment.Spec.Source = storage.VolumeAttachmentSource{} @@ -457,77 +434,71 @@ func TestVolumeAttachmentUpdateValidation(t *testing.T) { old.Spec.Source = storage.VolumeAttachmentSource{} old.Spec.Source.PersistentVolumeName = &volumeName - errorCases := []storage.VolumeAttachment{ - { - // change attacher - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "another-attacher", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, - NodeName: "mynode", + errorCases := []storage.VolumeAttachment{{ + // change attacher + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "another-attacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + }, { + // change source volume name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &newVolumeName, + }, + NodeName: "mynode", + }, + }, { + // change node + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "anothernode", + }, + }, { + // change source + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + InlineVolumeSpec: &inlineSpec, + }, + NodeName: "mynode", + }, + }, { + // add invalid status + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": "bar", + }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: strings.Repeat("a", maxAttachedVolumeMetadataSize), + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", }, }, - { - // change source volume name - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &newVolumeName, - }, - NodeName: "mynode", - }, - }, - { - // change node - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, - NodeName: "anothernode", - }, - }, - { - // change source - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{ - InlineVolumeSpec: &inlineSpec, - }, - NodeName: "mynode", - }, - }, - { - // add invalid status - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, - NodeName: "mynode", - }, - Status: storage.VolumeAttachmentStatus{ - Attached: true, - AttachmentMetadata: map[string]string{ - "foo": "bar", - }, - AttachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: strings.Repeat("a", maxAttachedVolumeMetadataSize), - }, - DetachError: &storage.VolumeError{ - Time: metav1.Time{}, - Message: "hello world", - }, - }, - }, - } + }} for _, volumeAttachment := range errorCases { if errs := ValidateVolumeAttachmentUpdate(&volumeAttachment, &old); len(errs) == 0 { @@ -539,18 +510,16 @@ func TestVolumeAttachmentUpdateValidation(t *testing.T) { func TestVolumeAttachmentValidationV1(t *testing.T) { volumeName := "pv-name" invalidVolumeName := "-invalid-@#$%^&*()-" - successCases := []storage.VolumeAttachment{ - { - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, - NodeName: "mynode", + successCases := []storage.VolumeAttachment{{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, }, + NodeName: "mynode", }, - } + }} for _, volumeAttachment := range successCases { if errs := ValidateVolumeAttachmentV1(&volumeAttachment); len(errs) != 0 { @@ -558,30 +527,27 @@ func TestVolumeAttachmentValidationV1(t *testing.T) { } } - errorCases := []storage.VolumeAttachment{ - { - // Invalid attacher name - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "invalid-@#$%^&*()", - NodeName: "mynode", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &volumeName, - }, + errorCases := []storage.VolumeAttachment{{ + // Invalid attacher name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "invalid-@#$%^&*()", + NodeName: "mynode", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, }, }, - { - // Invalid PV name - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: storage.VolumeAttachmentSpec{ - Attacher: "myattacher", - NodeName: "mynode", - Source: storage.VolumeAttachmentSource{ - PersistentVolumeName: &invalidVolumeName, - }, + }, { + // Invalid PV name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "mynode", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &invalidVolumeName, }, }, - } + }} for _, volumeAttachment := range errorCases { if errs := ValidateVolumeAttachmentV1(&volumeAttachment); len(errs) == 0 { @@ -694,235 +660,155 @@ func TestValidateUpdateVolumeBindingMode(t *testing.T) { func TestValidateAllowedTopologies(t *testing.T) { - validTopology := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"zone1"}, - }, - { - Key: "kubernetes.io/hostname", - Values: []string{"node1"}, - }, - }, - }, - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"zone2"}, - }, - { - Key: "kubernetes.io/hostname", - Values: []string{"node2"}, - }, - }, - }, - } + validTopology := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"zone1"}, + }, { + Key: "kubernetes.io/hostname", + Values: []string{"node1"}, + }}, + }, { + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"zone2"}, + }, { + Key: "kubernetes.io/hostname", + Values: []string{"node2"}, + }}, + }} - topologyInvalidKey := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "/invalidkey", - Values: []string{"zone1"}, - }, - }, - }, - } + topologyInvalidKey := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "/invalidkey", + Values: []string{"zone1"}, + }}, + }} - topologyLackOfValues := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "kubernetes.io/hostname", - Values: []string{}, - }, - }, - }, - } + topologyLackOfValues := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "kubernetes.io/hostname", + Values: []string{}, + }}, + }} - topologyDupValues := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "kubernetes.io/hostname", - Values: []string{"node1", "node1"}, - }, - }, - }, - } + topologyDupValues := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "kubernetes.io/hostname", + Values: []string{"node1", "node1"}, + }}, + }} - topologyMultiValues := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "kubernetes.io/hostname", - Values: []string{"node1", "node2"}, - }, - }, - }, - } + topologyMultiValues := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "kubernetes.io/hostname", + Values: []string{"node1", "node2"}, + }}, + }} - topologyEmptyMatchLabelExpressions := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: nil, - }, - } + topologyEmptyMatchLabelExpressions := []api.TopologySelectorTerm{{ + MatchLabelExpressions: nil, + }} - topologyDupKeys := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "kubernetes.io/hostname", - Values: []string{"node1"}, - }, - { - Key: "kubernetes.io/hostname", - Values: []string{"node2"}, - }, - }, - }, - } + topologyDupKeys := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "kubernetes.io/hostname", + Values: []string{"node1"}, + }, { + Key: "kubernetes.io/hostname", + Values: []string{"node2"}, + }}, + }} - topologyMultiTerm := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "kubernetes.io/hostname", - Values: []string{"node1"}, - }, - }, - }, - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "kubernetes.io/hostname", - Values: []string{"node2"}, - }, - }, - }, - } + topologyMultiTerm := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "kubernetes.io/hostname", + Values: []string{"node1"}, + }}, + }, { + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "kubernetes.io/hostname", + Values: []string{"node2"}, + }}, + }} - topologyDupTermsIdentical := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"zone1"}, - }, - { - Key: "kubernetes.io/hostname", - Values: []string{"node1"}, - }, - }, - }, - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"zone1"}, - }, - { - Key: "kubernetes.io/hostname", - Values: []string{"node1"}, - }, - }, - }, - } + topologyDupTermsIdentical := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"zone1"}, + }, { + Key: "kubernetes.io/hostname", + Values: []string{"node1"}, + }}, + }, { + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"zone1"}, + }, { + Key: "kubernetes.io/hostname", + Values: []string{"node1"}, + }}, + }} - topologyExprsOneSameOneDiff := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"zone1"}, - }, - { - Key: "kubernetes.io/hostname", - Values: []string{"node1"}, - }, - }, - }, - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"zone1"}, - }, - { - Key: "kubernetes.io/hostname", - Values: []string{"node2"}, - }, - }, - }, - } + topologyExprsOneSameOneDiff := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"zone1"}, + }, { + Key: "kubernetes.io/hostname", + Values: []string{"node1"}, + }}, + }, { + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"zone1"}, + }, { + Key: "kubernetes.io/hostname", + Values: []string{"node2"}, + }}, + }} - topologyValuesOneSameOneDiff := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "kubernetes.io/hostname", - Values: []string{"node1", "node2"}, - }, - }, - }, - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "kubernetes.io/hostname", - Values: []string{"node1", "node3"}, - }, - }, - }, - } + topologyValuesOneSameOneDiff := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "kubernetes.io/hostname", + Values: []string{"node1", "node2"}, + }}, + }, { + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "kubernetes.io/hostname", + Values: []string{"node1", "node3"}, + }}, + }} - topologyDupTermsDiffExprOrder := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "kubernetes.io/hostname", - Values: []string{"node1"}, - }, - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"zone1"}, - }, - }, - }, - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"zone1"}, - }, - { - Key: "kubernetes.io/hostname", - Values: []string{"node1"}, - }, - }, - }, - } + topologyDupTermsDiffExprOrder := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "kubernetes.io/hostname", + Values: []string{"node1"}, + }, { + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"zone1"}, + }}, + }, { + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"zone1"}, + }, { + Key: "kubernetes.io/hostname", + Values: []string{"node1"}, + }}, + }} - topologyDupTermsDiffValueOrder := []api.TopologySelectorTerm{ - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"zone1", "zone2"}, - }, - }, - }, - { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"zone2", "zone1"}, - }, - }, - }, - } + topologyDupTermsDiffValueOrder := []api.TopologySelectorTerm{{ + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"zone1", "zone2"}, + }}, + }, { + MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"zone2", "zone1"}, + }}, + }} cases := map[string]bindingTest{ "no topology": { @@ -1000,193 +886,150 @@ func TestCSINodeValidation(t *testing.T) { longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver" // 88 chars nodeID := "nodeA" longID := longName + longName + "abcdefghijklmnopqrstuvwxyz" // 202 chars - successCases := []storage.CSINode{ - { - // driver name: dot only - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + successCases := []storage.CSINode{{ + // driver name: dot only + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // driver name: dash only - ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io-kubernetes-storage-csi-driver", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // driver name: dash only + ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io-kubernetes-storage-csi-driver", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // driver name: numbers - ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "1io-kubernetes-storage-2-csi-driver3", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // driver name: numbers + ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "1io-kubernetes-storage-2-csi-driver3", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // driver name: dot, dash - ObjectMeta: metav1.ObjectMeta{Name: "foo4"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage-csi-driver", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // driver name: dot, dash + ObjectMeta: metav1.ObjectMeta{Name: "foo4"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage-csi-driver", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // driver name: dot, dash, and numbers - ObjectMeta: metav1.ObjectMeta{Name: "foo5"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: driverName2, - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // driver name: dot, dash, and numbers + ObjectMeta: metav1.ObjectMeta{Name: "foo5"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: driverName2, + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // Driver name length 1 - ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "a", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // Driver name length 1 + ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "a", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // multiple drivers with different node IDs, topology keys - ObjectMeta: metav1.ObjectMeta{Name: "foo6"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "driver1", - NodeID: "node1", - TopologyKeys: []string{"key1", "key2"}, - }, - { - Name: "driverB", - NodeID: "nodeA", - TopologyKeys: []string{"keyA", "keyB"}, - }, - }, - }, + }, { + // multiple drivers with different node IDs, topology keys + ObjectMeta: metav1.ObjectMeta{Name: "foo6"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "driver1", + NodeID: "node1", + TopologyKeys: []string{"key1", "key2"}, + }, { + Name: "driverB", + NodeID: "nodeA", + TopologyKeys: []string{"keyA", "keyB"}, + }}, }, - { - // multiple drivers with same node IDs, topology keys - ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "driver1", - NodeID: "node1", - TopologyKeys: []string{"key1"}, - }, - { - Name: "driver2", - NodeID: "node1", - TopologyKeys: []string{"key1"}, - }, - }, - }, + }, { + // multiple drivers with same node IDs, topology keys + ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "driver1", + NodeID: "node1", + TopologyKeys: []string{"key1"}, + }, { + Name: "driver2", + NodeID: "node1", + TopologyKeys: []string{"key1"}, + }}, }, - { - // Volume limits being zero - ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(0)}, - }, - }, - }, + }, { + // Volume limits being zero + ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(0)}, + }}, }, - { - // Volume limits with positive number - ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(1)}, - }, - }, - }, + }, { + // Volume limits with positive number + ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(1)}, + }}, }, - { - // topology key names with -, _, and dot . - ObjectMeta: metav1.ObjectMeta{Name: "foo8"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "driver1", - NodeID: "node1", - TopologyKeys: []string{"zone_1", "zone.2"}, - }, - { - Name: "driver2", - NodeID: "node1", - TopologyKeys: []string{"zone-3", "zone.4"}, - }, - }, - }, + }, { + // topology key names with -, _, and dot . + ObjectMeta: metav1.ObjectMeta{Name: "foo8"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "driver1", + NodeID: "node1", + TopologyKeys: []string{"zone_1", "zone.2"}, + }, { + Name: "driver2", + NodeID: "node1", + TopologyKeys: []string{"zone-3", "zone.4"}, + }}, }, - { - // topology prefix with - and dot. - ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "driver1", - NodeID: "node1", - TopologyKeys: []string{"company-com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // topology prefix with - and dot. + ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "driver1", + NodeID: "node1", + TopologyKeys: []string{"company-com/zone1", "company.com/zone2"}, + }}, }, - { - // No topology keys - ObjectMeta: metav1.ObjectMeta{Name: "foo10"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: driverName, - NodeID: nodeID, - }, - }, - }, + }, { + // No topology keys + ObjectMeta: metav1.ObjectMeta{Name: "foo10"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: driverName, + NodeID: nodeID, + }}, }, - } + }} for _, csiNode := range successCases { if errs := ValidateCSINode(&csiNode, shorterIDValidationOption); len(errs) != 0 { @@ -1198,13 +1041,11 @@ func TestCSINodeValidation(t *testing.T) { // node ID length > 128 but < 192 ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: driverName, - NodeID: longID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, + Drivers: []storage.CSINodeDriver{{ + Name: driverName, + NodeID: longID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, } @@ -1212,199 +1053,155 @@ func TestCSINodeValidation(t *testing.T) { t.Errorf("expected success: %v", errs) } - errorCases := []storage.CSINode{ - { - // Empty driver name - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + errorCases := []storage.CSINode{{ + // Empty driver name + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // Invalid start char in driver name - ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "_io.kubernetes.storage.csi.driver", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // Invalid start char in driver name + ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "_io.kubernetes.storage.csi.driver", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // Invalid end char in driver name - ObjectMeta: metav1.ObjectMeta{Name: "foo4"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver/", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // Invalid end char in driver name + ObjectMeta: metav1.ObjectMeta{Name: "foo4"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver/", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // Invalid separators in driver name - ObjectMeta: metav1.ObjectMeta{Name: "foo5"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io/kubernetes/storage/csi~driver", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // Invalid separators in driver name + ObjectMeta: metav1.ObjectMeta{Name: "foo5"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io/kubernetes/storage/csi~driver", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // driver name: underscore only - ObjectMeta: metav1.ObjectMeta{Name: "foo6"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io_kubernetes_storage_csi_driver", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // driver name: underscore only + ObjectMeta: metav1.ObjectMeta{Name: "foo6"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io_kubernetes_storage_csi_driver", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // Driver name length > 63 - ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: longName, - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // Driver name length > 63 + ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: longName, + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // No driver name - ObjectMeta: metav1.ObjectMeta{Name: "foo8"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // No driver name + ObjectMeta: metav1.ObjectMeta{Name: "foo8"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // Empty individual topology key - ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: driverName, - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", ""}, - }, - }, - }, + }, { + // Empty individual topology key + ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: driverName, + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", ""}, + }}, }, - { - // duplicate drivers in driver specs - ObjectMeta: metav1.ObjectMeta{Name: "foo10"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "driver1", - NodeID: "node1", - TopologyKeys: []string{"key1", "key2"}, - }, - { - Name: "driver1", - NodeID: "nodeX", - TopologyKeys: []string{"keyA", "keyB"}, - }, - }, - }, + }, { + // duplicate drivers in driver specs + ObjectMeta: metav1.ObjectMeta{Name: "foo10"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "driver1", + NodeID: "node1", + TopologyKeys: []string{"key1", "key2"}, + }, { + Name: "driver1", + NodeID: "nodeX", + TopologyKeys: []string{"keyA", "keyB"}, + }}, }, - { - // single driver with duplicate topology keys in driver specs - ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "driver1", - NodeID: "node1", - TopologyKeys: []string{"key1", "key1"}, - }, - }, - }, + }, { + // single driver with duplicate topology keys in driver specs + ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "driver1", + NodeID: "node1", + TopologyKeys: []string{"key1", "key1"}, + }}, }, - { - // multiple drivers with one set of duplicate topology keys in driver specs - ObjectMeta: metav1.ObjectMeta{Name: "foo12"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "driver1", - NodeID: "node1", - TopologyKeys: []string{"key1"}, - }, - { - Name: "driver2", - NodeID: "nodeX", - TopologyKeys: []string{"keyA", "keyA"}, - }, - }, - }, + }, { + // multiple drivers with one set of duplicate topology keys in driver specs + ObjectMeta: metav1.ObjectMeta{Name: "foo12"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "driver1", + NodeID: "node1", + TopologyKeys: []string{"key1"}, + }, { + Name: "driver2", + NodeID: "nodeX", + TopologyKeys: []string{"keyA", "keyA"}, + }}, }, - { - // Empty NodeID - ObjectMeta: metav1.ObjectMeta{Name: "foo13"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: driverName, - NodeID: "", - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // Empty NodeID + ObjectMeta: metav1.ObjectMeta{Name: "foo13"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: driverName, + NodeID: "", + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // Volume limits with negative number - ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(-1)}, - }, - }, - }, + }, { + // Volume limits with negative number + ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(-1)}, + }}, }, - { - // topology prefix should be lower case - ObjectMeta: metav1.ObjectMeta{Name: "foo14"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: driverName, - NodeID: "node1", - TopologyKeys: []string{"Company.Com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // topology prefix should be lower case + ObjectMeta: metav1.ObjectMeta{Name: "foo14"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: driverName, + NodeID: "node1", + TopologyKeys: []string{"Company.Com/zone1", "company.com/zone2"}, + }}, }, + }, nodeIDCase, } @@ -1424,100 +1221,80 @@ func TestCSINodeUpdateValidation(t *testing.T) { old := storage.CSINode{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - { - Name: "io.kubernetes.storage.csi.driver-2", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, - }, - }, + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }, { + Name: "io.kubernetes.storage.csi.driver-2", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, + }}, }, } - successCases := []storage.CSINode{ - { - // no change - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - { - Name: "io.kubernetes.storage.csi.driver-2", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, - }, - }, - }, + successCases := []storage.CSINode{{ + // no change + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }, { + Name: "io.kubernetes.storage.csi.driver-2", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, + }}, }, - { - // remove a driver - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // remove a driver + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - { - // add a driver - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - { - Name: "io.kubernetes.storage.csi.driver-2", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, - }, - { - Name: "io.kubernetes.storage.csi.driver-3", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(30)}, - }, - }, - }, + }, { + // add a driver + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }, { + Name: "io.kubernetes.storage.csi.driver-2", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, + }, { + Name: "io.kubernetes.storage.csi.driver-3", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(30)}, + }}, }, - { - // remove a driver and add a driver - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - { - Name: "io.kubernetes.storage.csi.new-driver", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(30)}, - }, - }, - }, + }, { + // remove a driver and add a driver + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }, { + Name: "io.kubernetes.storage.csi.new-driver", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(30)}, + }}, }, - } + }} for _, csiNode := range successCases { if errs := ValidateCSINodeUpdate(&csiNode, &old, shorterIDValidationOption); len(errs) != 0 { @@ -1525,122 +1302,97 @@ func TestCSINodeUpdateValidation(t *testing.T) { } } - errorCases := []storage.CSINode{ - { - // invalid change node id - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: "nodeB", - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - { - Name: "io.kubernetes.storage.csi.driver-2", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, - }, - }, - }, + errorCases := []storage.CSINode{{ + // invalid change node id + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: "nodeB", + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }, { + Name: "io.kubernetes.storage.csi.driver-2", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, + }}, }, - { - // invalid change topology keys - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - { - Name: "io.kubernetes.storage.csi.driver-2", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, - }, - }, - }, + }, { + // invalid change topology keys + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }, { + Name: "io.kubernetes.storage.csi.driver-2", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, + }}, }, - { - // invalid change trying to set a previously unset allocatable - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(10)}, - }, - { - Name: "io.kubernetes.storage.csi.driver-2", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, - }, - }, - }, + }, { + // invalid change trying to set a previously unset allocatable + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(10)}, + }, { + Name: "io.kubernetes.storage.csi.driver-2", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, + }}, }, - { - // invalid change trying to update allocatable with a different volume limit - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - { - Name: "io.kubernetes.storage.csi.driver-2", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(21)}, - }, - }, - }, + }, { + // invalid change trying to update allocatable with a different volume limit + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }, { + Name: "io.kubernetes.storage.csi.driver-2", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(21)}, + }}, }, - { - // invalid change trying to update allocatable with an empty volume limit - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - { - Name: "io.kubernetes.storage.csi.driver-2", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - Allocatable: &storage.VolumeNodeResources{Count: nil}, - }, - }, - }, + }, { + // invalid change trying to update allocatable with an empty volume limit + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }, { + Name: "io.kubernetes.storage.csi.driver-2", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + Allocatable: &storage.VolumeNodeResources{Count: nil}, + }}, }, - { - // invalid change trying to remove allocatable - ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, - Spec: storage.CSINodeSpec{ - Drivers: []storage.CSINodeDriver{ - { - Name: "io.kubernetes.storage.csi.driver-1", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - { - Name: "io.kubernetes.storage.csi.driver-2", - NodeID: nodeID, - TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, - }, - }, - }, + }, { + // invalid change trying to remove allocatable + ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, + Spec: storage.CSINodeSpec{ + Drivers: []storage.CSINodeDriver{{ + Name: "io.kubernetes.storage.csi.driver-1", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }, { + Name: "io.kubernetes.storage.csi.driver-2", + NodeID: nodeID, + TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, + }}, }, - } + }} for _, csiNode := range errorCases { if errs := ValidateCSINodeUpdate(&csiNode, &old, shorterIDValidationOption); len(errs) == 0 { @@ -1664,258 +1416,234 @@ func TestCSIDriverValidation(t *testing.T) { notSELinuxMount := false supportedFSGroupPolicy := storage.FileFSGroupPolicy invalidFSGroupPolicy := storage.FSGroupPolicy("invalid-mode") - successCases := []storage.CSIDriver{ - { - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + successCases := []storage.CSIDriver{{ + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - // driver name: dot only - ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi.driver"}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: ¬StorageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + // driver name: dot only + ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi.driver"}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: ¬StorageCapacity, + SELinuxMount: &seLinuxMount, }, - { - // driver name: dash only - ObjectMeta: metav1.ObjectMeta{Name: "io-kubernetes-storage-csi-driver"}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + // driver name: dash only + ObjectMeta: metav1.ObjectMeta{Name: "io-kubernetes-storage-csi-driver"}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - // driver name: numbers - ObjectMeta: metav1.ObjectMeta{Name: "1csi2driver3"}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + // driver name: numbers + ObjectMeta: metav1.ObjectMeta{Name: "1csi2driver3"}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - // driver name: dot and dash - ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi-driver"}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + // driver name: dot and dash + ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi-driver"}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - VolumeLifecycleModes: []storage.VolumeLifecycleMode{ - storage.VolumeLifecyclePersistent, - }, - SELinuxMount: &seLinuxMount, + }, { + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + VolumeLifecycleModes: []storage.VolumeLifecycleMode{ + storage.VolumeLifecyclePersistent, }, + SELinuxMount: &seLinuxMount, }, - { - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - VolumeLifecycleModes: []storage.VolumeLifecycleMode{ - storage.VolumeLifecycleEphemeral, - }, - SELinuxMount: &seLinuxMount, + }, { + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + VolumeLifecycleModes: []storage.VolumeLifecycleMode{ + storage.VolumeLifecycleEphemeral, }, + SELinuxMount: &seLinuxMount, }, - { - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - VolumeLifecycleModes: []storage.VolumeLifecycleMode{ - storage.VolumeLifecycleEphemeral, - storage.VolumeLifecyclePersistent, - }, - SELinuxMount: &seLinuxMount, + }, { + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + VolumeLifecycleModes: []storage.VolumeLifecycleMode{ + storage.VolumeLifecycleEphemeral, + storage.VolumeLifecyclePersistent, }, + SELinuxMount: &seLinuxMount, }, - { - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - VolumeLifecycleModes: []storage.VolumeLifecycleMode{ - storage.VolumeLifecycleEphemeral, - storage.VolumeLifecyclePersistent, - storage.VolumeLifecycleEphemeral, - }, - SELinuxMount: &seLinuxMount, + }, { + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + VolumeLifecycleModes: []storage.VolumeLifecycleMode{ + storage.VolumeLifecycleEphemeral, + storage.VolumeLifecyclePersistent, + storage.VolumeLifecycleEphemeral, }, + SELinuxMount: &seLinuxMount, }, - { - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - FSGroupPolicy: &supportedFSGroupPolicy, - SELinuxMount: &seLinuxMount, - }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + FSGroupPolicy: &supportedFSGroupPolicy, + SELinuxMount: &seLinuxMount, }, - { - // SELinuxMount: false - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - RequiresRepublish: ¬RequiresRepublish, - StorageCapacity: &storageCapacity, - SELinuxMount: ¬SELinuxMount, - }, + }, { + // SELinuxMount: false + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + RequiresRepublish: ¬RequiresRepublish, + StorageCapacity: &storageCapacity, + SELinuxMount: ¬SELinuxMount, }, - } + }} for _, csiDriver := range successCases { if errs := ValidateCSIDriver(&csiDriver); len(errs) != 0 { t.Errorf("expected success: %v", errs) } } - errorCases := []storage.CSIDriver{ - { - ObjectMeta: metav1.ObjectMeta{Name: invalidName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachRequired, - PodInfoOnMount: &podInfoOnMount, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + errorCases := []storage.CSIDriver{{ + ObjectMeta: metav1.ObjectMeta{Name: invalidName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachRequired, + PodInfoOnMount: &podInfoOnMount, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - ObjectMeta: metav1.ObjectMeta{Name: longName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + ObjectMeta: metav1.ObjectMeta{Name: longName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - // AttachRequired not set - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: nil, - PodInfoOnMount: &podInfoOnMount, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + // AttachRequired not set + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: nil, + PodInfoOnMount: &podInfoOnMount, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - // PodInfoOnMount not set - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: nil, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + // PodInfoOnMount not set + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: nil, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - // StorageCapacity not set - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: &podInfoOnMount, - StorageCapacity: nil, - SELinuxMount: &seLinuxMount, - }, + }, { + // StorageCapacity not set + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: &podInfoOnMount, + StorageCapacity: nil, + SELinuxMount: &seLinuxMount, }, - { - // invalid mode - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - StorageCapacity: &storageCapacity, - VolumeLifecycleModes: []storage.VolumeLifecycleMode{ - "no-such-mode", - }, - SELinuxMount: &seLinuxMount, + }, { + // invalid mode + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + StorageCapacity: &storageCapacity, + VolumeLifecycleModes: []storage.VolumeLifecycleMode{ + "no-such-mode", }, + SELinuxMount: &seLinuxMount, }, - { - // invalid fsGroupPolicy - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - FSGroupPolicy: &invalidFSGroupPolicy, - StorageCapacity: &storageCapacity, - SELinuxMount: &seLinuxMount, - }, + }, { + // invalid fsGroupPolicy + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + FSGroupPolicy: &invalidFSGroupPolicy, + StorageCapacity: &storageCapacity, + SELinuxMount: &seLinuxMount, }, - { - // no SELinuxMount - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - AttachRequired: &attachNotRequired, - PodInfoOnMount: ¬PodInfoOnMount, - StorageCapacity: &storageCapacity, - }, + }, { + // no SELinuxMount + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + AttachRequired: &attachNotRequired, + PodInfoOnMount: ¬PodInfoOnMount, + StorageCapacity: &storageCapacity, }, - } + }} for _, csiDriver := range errorCases { if errs := ValidateCSIDriver(&csiDriver); len(errs) == 0 { @@ -1958,36 +1686,30 @@ func TestCSIDriverValidationUpdate(t *testing.T) { successCases := []struct { name string modify func(new *storage.CSIDriver) - }{ - { - name: "no change", - modify: func(new *storage.CSIDriver) {}, + }{{ + name: "no change", + modify: func(new *storage.CSIDriver) {}, + }, { + name: "change TokenRequests", + modify: func(new *storage.CSIDriver) { + new.Spec.TokenRequests = []storage.TokenRequest{{Audience: gcp}} }, - { - name: "change TokenRequests", - modify: func(new *storage.CSIDriver) { - new.Spec.TokenRequests = []storage.TokenRequest{{Audience: gcp}} - }, + }, { + name: "change RequiresRepublish", + modify: func(new *storage.CSIDriver) { + new.Spec.RequiresRepublish = &requiresRepublish }, - { - name: "change RequiresRepublish", - modify: func(new *storage.CSIDriver) { - new.Spec.RequiresRepublish = &requiresRepublish - }, + }, { + name: "StorageCapacity changed", + modify: func(new *storage.CSIDriver) { + new.Spec.StorageCapacity = ¬StorageCapacity }, - { - name: "StorageCapacity changed", - modify: func(new *storage.CSIDriver) { - new.Spec.StorageCapacity = ¬StorageCapacity - }, + }, { + name: "SELinuxMount changed", + modify: func(new *storage.CSIDriver) { + new.Spec.SELinuxMount = ¬SELinuxMount }, - { - name: "SELinuxMount changed", - modify: func(new *storage.CSIDriver) { - new.Spec.SELinuxMount = ¬SELinuxMount - }, - }, - } + }} for _, test := range successCases { t.Run(test.name, func(t *testing.T) { new := old.DeepCopy() @@ -2002,106 +1724,90 @@ func TestCSIDriverValidationUpdate(t *testing.T) { errorCases := []struct { name string modify func(new *storage.CSIDriver) - }{ - { - name: "invalid name", - modify: func(new *storage.CSIDriver) { - new.Name = invalidName - }, + }{{ + name: "invalid name", + modify: func(new *storage.CSIDriver) { + new.Name = invalidName }, - { - name: "long name", - modify: func(new *storage.CSIDriver) { - new.Name = longName - }, + }, { + name: "long name", + modify: func(new *storage.CSIDriver) { + new.Name = longName }, - { - name: "AttachRequired not set", - modify: func(new *storage.CSIDriver) { - new.Spec.AttachRequired = nil - }, + }, { + name: "AttachRequired not set", + modify: func(new *storage.CSIDriver) { + new.Spec.AttachRequired = nil }, - { - name: "AttachRequired changed", - modify: func(new *storage.CSIDriver) { - new.Spec.AttachRequired = &attachRequired - }, + }, { + name: "AttachRequired changed", + modify: func(new *storage.CSIDriver) { + new.Spec.AttachRequired = &attachRequired }, - { - name: "PodInfoOnMount not set", - modify: func(new *storage.CSIDriver) { - new.Spec.PodInfoOnMount = nil - }, + }, { + name: "PodInfoOnMount not set", + modify: func(new *storage.CSIDriver) { + new.Spec.PodInfoOnMount = nil }, - { - name: "PodInfoOnMount changed", - modify: func(new *storage.CSIDriver) { - new.Spec.PodInfoOnMount = &podInfoOnMount - }, + }, { + name: "PodInfoOnMount changed", + modify: func(new *storage.CSIDriver) { + new.Spec.PodInfoOnMount = &podInfoOnMount }, - { - name: "invalid volume lifecycle mode", - modify: func(new *storage.CSIDriver) { - new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{ - "no-such-mode", - } - }, + }, { + name: "invalid volume lifecycle mode", + modify: func(new *storage.CSIDriver) { + new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{ + "no-such-mode", + } }, - { - name: "volume lifecycle modes not set", - modify: func(new *storage.CSIDriver) { - new.Spec.VolumeLifecycleModes = nil - }, + }, { + name: "volume lifecycle modes not set", + modify: func(new *storage.CSIDriver) { + new.Spec.VolumeLifecycleModes = nil }, - { - name: "VolumeLifecyclePersistent removed", - modify: func(new *storage.CSIDriver) { - new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{ - storage.VolumeLifecycleEphemeral, - } - }, + }, { + name: "VolumeLifecyclePersistent removed", + modify: func(new *storage.CSIDriver) { + new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{ + storage.VolumeLifecycleEphemeral, + } }, - { - name: "VolumeLifecycleEphemeral removed", - modify: func(new *storage.CSIDriver) { - new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{ - storage.VolumeLifecyclePersistent, - } - }, + }, { + name: "VolumeLifecycleEphemeral removed", + modify: func(new *storage.CSIDriver) { + new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{ + storage.VolumeLifecyclePersistent, + } }, - { - name: "FSGroupPolicy invalidated", - modify: func(new *storage.CSIDriver) { - invalidFSGroupPolicy := storage.FSGroupPolicy("invalid") - new.Spec.FSGroupPolicy = &invalidFSGroupPolicy - }, + }, { + name: "FSGroupPolicy invalidated", + modify: func(new *storage.CSIDriver) { + invalidFSGroupPolicy := storage.FSGroupPolicy("invalid") + new.Spec.FSGroupPolicy = &invalidFSGroupPolicy }, - { - name: "FSGroupPolicy changed", - modify: func(new *storage.CSIDriver) { - fileFSGroupPolicy := storage.FileFSGroupPolicy - new.Spec.FSGroupPolicy = &fileFSGroupPolicy - }, + }, { + name: "FSGroupPolicy changed", + modify: func(new *storage.CSIDriver) { + fileFSGroupPolicy := storage.FileFSGroupPolicy + new.Spec.FSGroupPolicy = &fileFSGroupPolicy }, - { - name: "TokenRequests invalidated", - modify: func(new *storage.CSIDriver) { - new.Spec.TokenRequests = []storage.TokenRequest{{Audience: gcp}, {Audience: gcp}} - }, + }, { + name: "TokenRequests invalidated", + modify: func(new *storage.CSIDriver) { + new.Spec.TokenRequests = []storage.TokenRequest{{Audience: gcp}, {Audience: gcp}} }, - { - name: "invalid nil StorageCapacity", - modify: func(new *storage.CSIDriver) { - new.Spec.StorageCapacity = nil - }, + }, { + name: "invalid nil StorageCapacity", + modify: func(new *storage.CSIDriver) { + new.Spec.StorageCapacity = nil }, - { - name: "SELinuxMount not set", - modify: func(new *storage.CSIDriver) { - new.Spec.SELinuxMount = nil - }, + }, { + name: "SELinuxMount not set", + modify: func(new *storage.CSIDriver) { + new.Spec.SELinuxMount = nil }, - } + }} for _, test := range errorCases { t.Run(test.name, func(t *testing.T) { @@ -2224,15 +1930,13 @@ func TestValidateCSIStorageCapacity(t *testing.T) { capacity: func() *storage.CSIStorageCapacity { capacity := goodCapacity capacity.NodeTopology = &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOperator("no-such-operator"), - Values: []string{ - "bar", - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "foo", + Operator: metav1.LabelSelectorOperator("no-such-operator"), + Values: []string{ + "bar", }, - }, + }}, } return &capacity }(), @@ -2262,61 +1966,55 @@ func TestCSIServiceAccountToken(t *testing.T) { desc string csiDriver *storage.CSIDriver wantErr bool - }{ - { - desc: "invalid - TokenRequests has tokens with the same audience", - csiDriver: &storage.CSIDriver{ - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - TokenRequests: []storage.TokenRequest{{Audience: gcp}, {Audience: gcp}}, - RequiresRepublish: ¬RequiresRepublish, - }, - }, - wantErr: true, - }, - { - desc: "invalid - TokenRequests has tokens with ExpirationSeconds less than 10min", - csiDriver: &storage.CSIDriver{ - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - TokenRequests: []storage.TokenRequest{{Audience: gcp, ExpirationSeconds: utilpointer.Int64(10)}}, - RequiresRepublish: ¬RequiresRepublish, - }, - }, - wantErr: true, - }, - { - desc: "invalid - TokenRequests has tokens with ExpirationSeconds longer than 1<<32 min", - csiDriver: &storage.CSIDriver{ - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - TokenRequests: []storage.TokenRequest{{Audience: gcp, ExpirationSeconds: utilpointer.Int64(1<<32 + 1)}}, - RequiresRepublish: ¬RequiresRepublish, - }, - }, - wantErr: true, - }, - { - desc: "valid - TokenRequests has at most one token with empty string audience", - csiDriver: &storage.CSIDriver{ - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - TokenRequests: []storage.TokenRequest{{Audience: ""}}, - RequiresRepublish: ¬RequiresRepublish, - }, + }{{ + desc: "invalid - TokenRequests has tokens with the same audience", + csiDriver: &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + TokenRequests: []storage.TokenRequest{{Audience: gcp}, {Audience: gcp}}, + RequiresRepublish: ¬RequiresRepublish, }, }, - { - desc: "valid - TokenRequests has tokens with different audience", - csiDriver: &storage.CSIDriver{ - ObjectMeta: metav1.ObjectMeta{Name: driverName}, - Spec: storage.CSIDriverSpec{ - TokenRequests: []storage.TokenRequest{{}, {Audience: gcp}, {Audience: aws}}, - RequiresRepublish: ¬RequiresRepublish, - }, + wantErr: true, + }, { + desc: "invalid - TokenRequests has tokens with ExpirationSeconds less than 10min", + csiDriver: &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + TokenRequests: []storage.TokenRequest{{Audience: gcp, ExpirationSeconds: utilpointer.Int64(10)}}, + RequiresRepublish: ¬RequiresRepublish, }, }, - } + wantErr: true, + }, { + desc: "invalid - TokenRequests has tokens with ExpirationSeconds longer than 1<<32 min", + csiDriver: &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + TokenRequests: []storage.TokenRequest{{Audience: gcp, ExpirationSeconds: utilpointer.Int64(1<<32 + 1)}}, + RequiresRepublish: ¬RequiresRepublish, + }, + }, + wantErr: true, + }, { + desc: "valid - TokenRequests has at most one token with empty string audience", + csiDriver: &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + TokenRequests: []storage.TokenRequest{{Audience: ""}}, + RequiresRepublish: ¬RequiresRepublish, + }, + }, + }, { + desc: "valid - TokenRequests has tokens with different audience", + csiDriver: &storage.CSIDriver{ + ObjectMeta: metav1.ObjectMeta{Name: driverName}, + Spec: storage.CSIDriverSpec{ + TokenRequests: []storage.TokenRequest{{}, {Audience: gcp}, {Audience: aws}}, + RequiresRepublish: ¬RequiresRepublish, + }, + }, + }} for _, test := range tests { test.csiDriver.Spec.AttachRequired = new(bool) @@ -2335,32 +2033,27 @@ func TestCSIDriverValidationSELinuxMountAlpha(t *testing.T) { featureEnabled bool seLinuxMountValue *bool expectError bool - }{ - { - name: "feature enabled, nil value", - featureEnabled: true, - seLinuxMountValue: nil, - expectError: true, - }, - { - name: "feature enabled, non-nil value", - featureEnabled: true, - seLinuxMountValue: utilpointer.Bool(true), - expectError: false, - }, - { - name: "feature disabled, nil value", - featureEnabled: false, - seLinuxMountValue: nil, - expectError: false, - }, - { - name: "feature disabled, non-nil value", - featureEnabled: false, - seLinuxMountValue: utilpointer.Bool(true), - expectError: false, - }, - } + }{{ + name: "feature enabled, nil value", + featureEnabled: true, + seLinuxMountValue: nil, + expectError: true, + }, { + name: "feature enabled, non-nil value", + featureEnabled: true, + seLinuxMountValue: utilpointer.Bool(true), + expectError: false, + }, { + name: "feature disabled, nil value", + featureEnabled: false, + seLinuxMountValue: nil, + expectError: false, + }, { + name: "feature disabled, non-nil value", + featureEnabled: false, + seLinuxMountValue: utilpointer.Bool(true), + expectError: false, + }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, test.featureEnabled)() diff --git a/pkg/kubelet/apis/config/validation/validation_test.go b/pkg/kubelet/apis/config/validation/validation_test.go index c1b049e059e..776a7597372 100644 --- a/pkg/kubelet/apis/config/validation/validation_test.go +++ b/pkg/kubelet/apis/config/validation/validation_test.go @@ -86,502 +86,442 @@ func TestValidateKubeletConfiguration(t *testing.T) { name string configure func(config *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration errMsg string - }{ - { - name: "Success", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - return conf - }, + }{{ + name: "Success", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + return conf }, - { - name: "invalid NodeLeaseDurationSeconds", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.NodeLeaseDurationSeconds = 0 - return conf - }, - errMsg: "invalid configuration: nodeLeaseDurationSeconds must be greater than 0", + }, { + name: "invalid NodeLeaseDurationSeconds", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.NodeLeaseDurationSeconds = 0 + return conf }, - { - name: "specify EnforceNodeAllocatable without enabling CgroupsPerQOS", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.CgroupsPerQOS = false - conf.EnforceNodeAllocatable = []string{"pods"} - return conf - }, - errMsg: "invalid configuration: enforceNodeAllocatable (--enforce-node-allocatable) is not supported unless cgroupsPerQOS (--cgroups-per-qos) is set to true", + errMsg: "invalid configuration: nodeLeaseDurationSeconds must be greater than 0", + }, { + name: "specify EnforceNodeAllocatable without enabling CgroupsPerQOS", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.CgroupsPerQOS = false + conf.EnforceNodeAllocatable = []string{"pods"} + return conf }, - { - name: "specify SystemCgroups without CgroupRoot", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.SystemCgroups = "/" - conf.CgroupRoot = "" - return conf - }, - errMsg: "invalid configuration: systemCgroups (--system-cgroups) was specified and cgroupRoot (--cgroup-root) was not specified", + errMsg: "invalid configuration: enforceNodeAllocatable (--enforce-node-allocatable) is not supported unless cgroupsPerQOS (--cgroups-per-qos) is set to true", + }, { + name: "specify SystemCgroups without CgroupRoot", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.SystemCgroups = "/" + conf.CgroupRoot = "" + return conf }, - { - name: "invalid EventBurst", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.EventBurst = -1 - return conf - }, - errMsg: "invalid configuration: eventBurst (--event-burst) -1 must not be a negative number", + errMsg: "invalid configuration: systemCgroups (--system-cgroups) was specified and cgroupRoot (--cgroup-root) was not specified", + }, { + name: "invalid EventBurst", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EventBurst = -1 + return conf }, - { - name: "invalid EventRecordQPS", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.EventRecordQPS = -1 - return conf - }, - errMsg: "invalid configuration: eventRecordQPS (--event-qps) -1 must not be a negative number", + errMsg: "invalid configuration: eventBurst (--event-burst) -1 must not be a negative number", + }, { + name: "invalid EventRecordQPS", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EventRecordQPS = -1 + return conf }, - { - name: "invalid HealthzPort", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.HealthzPort = 65536 - return conf - }, - errMsg: "invalid configuration: healthzPort (--healthz-port) 65536 must be between 1 and 65535, inclusive", + errMsg: "invalid configuration: eventRecordQPS (--event-qps) -1 must not be a negative number", + }, { + name: "invalid HealthzPort", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.HealthzPort = 65536 + return conf }, - { - name: "specify CPUCFSQuotaPeriod without enabling CPUCFSQuotaPeriod", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"CustomCPUCFSQuotaPeriod": false} - conf.CPUCFSQuotaPeriod = metav1.Duration{Duration: 200 * time.Millisecond} - return conf - }, - errMsg: "invalid configuration: cpuCFSQuotaPeriod (--cpu-cfs-quota-period) {200ms} requires feature gate CustomCPUCFSQuotaPeriod", + errMsg: "invalid configuration: healthzPort (--healthz-port) 65536 must be between 1 and 65535, inclusive", + }, { + name: "specify CPUCFSQuotaPeriod without enabling CPUCFSQuotaPeriod", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"CustomCPUCFSQuotaPeriod": false} + conf.CPUCFSQuotaPeriod = metav1.Duration{Duration: 200 * time.Millisecond} + return conf }, - { - name: "invalid CPUCFSQuotaPeriod", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"CustomCPUCFSQuotaPeriod": true} - conf.CPUCFSQuotaPeriod = metav1.Duration{Duration: 2 * time.Second} - return conf - }, - errMsg: "invalid configuration: cpuCFSQuotaPeriod (--cpu-cfs-quota-period) {2s} must be between 1ms and 1sec, inclusive", + errMsg: "invalid configuration: cpuCFSQuotaPeriod (--cpu-cfs-quota-period) {200ms} requires feature gate CustomCPUCFSQuotaPeriod", + }, { + name: "invalid CPUCFSQuotaPeriod", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"CustomCPUCFSQuotaPeriod": true} + conf.CPUCFSQuotaPeriod = metav1.Duration{Duration: 2 * time.Second} + return conf }, - { - name: "invalid ImageGCHighThresholdPercent", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.ImageGCHighThresholdPercent = 101 - return conf - }, - errMsg: "invalid configuration: imageGCHighThresholdPercent (--image-gc-high-threshold) 101 must be between 0 and 100, inclusive", + errMsg: "invalid configuration: cpuCFSQuotaPeriod (--cpu-cfs-quota-period) {2s} must be between 1ms and 1sec, inclusive", + }, { + name: "invalid ImageGCHighThresholdPercent", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ImageGCHighThresholdPercent = 101 + return conf }, - { - name: "invalid ImageGCLowThresholdPercent", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.ImageGCLowThresholdPercent = -1 - return conf - }, - errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) -1 must be between 0 and 100, inclusive", + errMsg: "invalid configuration: imageGCHighThresholdPercent (--image-gc-high-threshold) 101 must be between 0 and 100, inclusive", + }, { + name: "invalid ImageGCLowThresholdPercent", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ImageGCLowThresholdPercent = -1 + return conf }, - { - name: "ImageGCLowThresholdPercent is equal to ImageGCHighThresholdPercent", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.ImageGCHighThresholdPercent = 0 - conf.ImageGCLowThresholdPercent = 0 - return conf - }, - errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) 0 must be less than imageGCHighThresholdPercent (--image-gc-high-threshold) 0", + errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) -1 must be between 0 and 100, inclusive", + }, { + name: "ImageGCLowThresholdPercent is equal to ImageGCHighThresholdPercent", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ImageGCHighThresholdPercent = 0 + conf.ImageGCLowThresholdPercent = 0 + return conf }, - { - name: "ImageGCLowThresholdPercent is greater than ImageGCHighThresholdPercent", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.ImageGCHighThresholdPercent = 0 - conf.ImageGCLowThresholdPercent = 1 - return conf - }, - errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) 1 must be less than imageGCHighThresholdPercent (--image-gc-high-threshold) 0", + errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) 0 must be less than imageGCHighThresholdPercent (--image-gc-high-threshold) 0", + }, { + name: "ImageGCLowThresholdPercent is greater than ImageGCHighThresholdPercent", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ImageGCHighThresholdPercent = 0 + conf.ImageGCLowThresholdPercent = 1 + return conf }, - { - name: "invalid IPTablesDropBit", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.IPTablesDropBit = 32 - return conf - }, - errMsg: "invalid configuration: iptablesDropBit (--iptables-drop-bit) 32 must be between 0 and 31, inclusive", + errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) 1 must be less than imageGCHighThresholdPercent (--image-gc-high-threshold) 0", + }, { + name: "invalid IPTablesDropBit", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.IPTablesDropBit = 32 + return conf }, - { - name: "invalid IPTablesMasqueradeBit", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.IPTablesMasqueradeBit = 32 - return conf - }, - errMsg: "invalid configuration: iptablesMasqueradeBit (--iptables-masquerade-bit) 32 must be between 0 and 31, inclusive", + errMsg: "invalid configuration: iptablesDropBit (--iptables-drop-bit) 32 must be between 0 and 31, inclusive", + }, { + name: "invalid IPTablesMasqueradeBit", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.IPTablesMasqueradeBit = 32 + return conf }, - { - name: "invalid KubeAPIBurst", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.KubeAPIBurst = -1 - return conf - }, - errMsg: "invalid configuration: kubeAPIBurst (--kube-api-burst) -1 must not be a negative number", + errMsg: "invalid configuration: iptablesMasqueradeBit (--iptables-masquerade-bit) 32 must be between 0 and 31, inclusive", + }, { + name: "invalid KubeAPIBurst", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.KubeAPIBurst = -1 + return conf }, - { - name: "invalid KubeAPIQPS", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.KubeAPIQPS = -1 - return conf - }, - errMsg: "invalid configuration: kubeAPIQPS (--kube-api-qps) -1 must not be a negative number", + errMsg: "invalid configuration: kubeAPIBurst (--kube-api-burst) -1 must not be a negative number", + }, { + name: "invalid KubeAPIQPS", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.KubeAPIQPS = -1 + return conf }, - { - name: "invalid NodeStatusMaxImages", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.NodeStatusMaxImages = -2 - return conf - }, - errMsg: "invalid configuration: nodeStatusMaxImages (--node-status-max-images) -2 must be -1 or greater", + errMsg: "invalid configuration: kubeAPIQPS (--kube-api-qps) -1 must not be a negative number", + }, { + name: "invalid NodeStatusMaxImages", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.NodeStatusMaxImages = -2 + return conf }, - { - name: "invalid MaxOpenFiles", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.MaxOpenFiles = -1 - return conf - }, - errMsg: "invalid configuration: maxOpenFiles (--max-open-files) -1 must not be a negative number", + errMsg: "invalid configuration: nodeStatusMaxImages (--node-status-max-images) -2 must be -1 or greater", + }, { + name: "invalid MaxOpenFiles", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.MaxOpenFiles = -1 + return conf }, - { - name: "invalid MaxPods", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.MaxPods = -1 - return conf - }, - errMsg: "invalid configuration: maxPods (--max-pods) -1 must not be a negative number", + errMsg: "invalid configuration: maxOpenFiles (--max-open-files) -1 must not be a negative number", + }, { + name: "invalid MaxPods", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.MaxPods = -1 + return conf }, - { - name: "invalid OOMScoreAdj", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.OOMScoreAdj = 1001 - return conf - }, - errMsg: "invalid configuration: oomScoreAdj (--oom-score-adj) 1001 must be between -1000 and 1000, inclusive", + errMsg: "invalid configuration: maxPods (--max-pods) -1 must not be a negative number", + }, { + name: "invalid OOMScoreAdj", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.OOMScoreAdj = 1001 + return conf }, - { - name: "invalid PodsPerCore", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.PodsPerCore = -1 - return conf - }, - errMsg: "invalid configuration: podsPerCore (--pods-per-core) -1 must not be a negative number", + errMsg: "invalid configuration: oomScoreAdj (--oom-score-adj) 1001 must be between -1000 and 1000, inclusive", + }, { + name: "invalid PodsPerCore", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.PodsPerCore = -1 + return conf }, - { - name: "invalid Port", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.Port = 65536 - return conf - }, - errMsg: "invalid configuration: port (--port) 65536 must be between 1 and 65535, inclusive", + errMsg: "invalid configuration: podsPerCore (--pods-per-core) -1 must not be a negative number", + }, { + name: "invalid Port", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.Port = 65536 + return conf }, - { - name: "invalid ReadOnlyPort", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.ReadOnlyPort = 65536 - return conf - }, - errMsg: "invalid configuration: readOnlyPort (--read-only-port) 65536 must be between 0 and 65535, inclusive", + errMsg: "invalid configuration: port (--port) 65536 must be between 1 and 65535, inclusive", + }, { + name: "invalid ReadOnlyPort", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ReadOnlyPort = 65536 + return conf }, - { - name: "invalid RegistryBurst", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.RegistryBurst = -1 - return conf - }, - errMsg: "invalid configuration: registryBurst (--registry-burst) -1 must not be a negative number", + errMsg: "invalid configuration: readOnlyPort (--read-only-port) 65536 must be between 0 and 65535, inclusive", + }, { + name: "invalid RegistryBurst", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.RegistryBurst = -1 + return conf }, - { - name: "invalid RegistryPullQPS", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.RegistryPullQPS = -1 - return conf - }, - errMsg: "invalid configuration: registryPullQPS (--registry-qps) -1 must not be a negative number", + errMsg: "invalid configuration: registryBurst (--registry-burst) -1 must not be a negative number", + }, { + name: "invalid RegistryPullQPS", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.RegistryPullQPS = -1 + return conf }, - { - name: "invalid MaxParallelImagePulls", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.MaxParallelImagePulls = utilpointer.Int32(0) - return conf - }, - errMsg: "invalid configuration: maxParallelImagePulls 0 must be a positive number", + errMsg: "invalid configuration: registryPullQPS (--registry-qps) -1 must not be a negative number", + }, { + name: "invalid MaxParallelImagePulls", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.MaxParallelImagePulls = utilpointer.Int32(0) + return conf }, - { - name: "invalid MaxParallelImagePulls and SerializeImagePulls combination", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.MaxParallelImagePulls = utilpointer.Int32(3) - conf.SerializeImagePulls = true - return conf - }, - errMsg: "invalid configuration: maxParallelImagePulls cannot be larger than 1 unless SerializeImagePulls (--serialize-image-pulls) is set to false", + errMsg: "invalid configuration: maxParallelImagePulls 0 must be a positive number", + }, { + name: "invalid MaxParallelImagePulls and SerializeImagePulls combination", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.MaxParallelImagePulls = utilpointer.Int32(3) + conf.SerializeImagePulls = true + return conf }, - { - name: "valid MaxParallelImagePulls and SerializeImagePulls combination", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.MaxParallelImagePulls = utilpointer.Int32(1) - conf.SerializeImagePulls = true - return conf - }, + errMsg: "invalid configuration: maxParallelImagePulls cannot be larger than 1 unless SerializeImagePulls (--serialize-image-pulls) is set to false", + }, { + name: "valid MaxParallelImagePulls and SerializeImagePulls combination", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.MaxParallelImagePulls = utilpointer.Int32(1) + conf.SerializeImagePulls = true + return conf }, - { - name: "specify ServerTLSBootstrap without enabling RotateKubeletServerCertificate", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"RotateKubeletServerCertificate": false} - conf.ServerTLSBootstrap = true - return conf - }, - errMsg: "invalid configuration: serverTLSBootstrap true requires feature gate RotateKubeletServerCertificate", + }, { + name: "specify ServerTLSBootstrap without enabling RotateKubeletServerCertificate", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"RotateKubeletServerCertificate": false} + conf.ServerTLSBootstrap = true + return conf }, - { - name: "invalid TopologyManagerPolicy", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.TopologyManagerPolicy = "invalid-policy" - return conf - }, - errMsg: "invalid configuration: topologyManagerPolicy (--topology-manager-policy) \"invalid-policy\" must be one of: [\"none\" \"best-effort\" \"restricted\" \"single-numa-node\"]", + errMsg: "invalid configuration: serverTLSBootstrap true requires feature gate RotateKubeletServerCertificate", + }, { + name: "invalid TopologyManagerPolicy", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.TopologyManagerPolicy = "invalid-policy" + return conf }, - { - name: "invalid TopologyManagerScope", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.TopologyManagerScope = "invalid-scope" - return conf - }, - errMsg: "invalid configuration: topologyManagerScope (--topology-manager-scope) \"invalid-scope\" must be one of: \"container\", or \"pod\"", + errMsg: "invalid configuration: topologyManagerPolicy (--topology-manager-policy) \"invalid-policy\" must be one of: [\"none\" \"best-effort\" \"restricted\" \"single-numa-node\"]", + }, { + name: "invalid TopologyManagerScope", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.TopologyManagerScope = "invalid-scope" + return conf }, - { - name: "ShutdownGracePeriodCriticalPods is greater than ShutdownGracePeriod", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} - conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 2 * time.Second} - conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Second} - return conf - }, - errMsg: "invalid configuration: shutdownGracePeriodCriticalPods {2s} must be <= shutdownGracePeriod {1s}", + errMsg: "invalid configuration: topologyManagerScope (--topology-manager-scope) \"invalid-scope\" must be one of: \"container\", or \"pod\"", + }, { + name: "ShutdownGracePeriodCriticalPods is greater than ShutdownGracePeriod", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} + conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 2 * time.Second} + conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Second} + return conf }, - { - name: "ShutdownGracePeriod is less than 1 sec", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} - conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Millisecond} - return conf - }, - errMsg: "invalid configuration: shutdownGracePeriod {1ms} must be either zero or otherwise >= 1 sec", + errMsg: "invalid configuration: shutdownGracePeriodCriticalPods {2s} must be <= shutdownGracePeriod {1s}", + }, { + name: "ShutdownGracePeriod is less than 1 sec", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} + conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Millisecond} + return conf }, - { - name: "ShutdownGracePeriodCriticalPods is less than 1 sec", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} - conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 1 * time.Millisecond} - return conf - }, - errMsg: "invalid configuration: shutdownGracePeriodCriticalPods {1ms} must be either zero or otherwise >= 1 sec", + errMsg: "invalid configuration: shutdownGracePeriod {1ms} must be either zero or otherwise >= 1 sec", + }, { + name: "ShutdownGracePeriodCriticalPods is less than 1 sec", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} + conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 1 * time.Millisecond} + return conf }, - { - name: "specify ShutdownGracePeriod without enabling GracefulNodeShutdown", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": false} - conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Second} - return conf - }, - errMsg: "invalid configuration: specifying shutdownGracePeriod or shutdownGracePeriodCriticalPods requires feature gate GracefulNodeShutdown", + errMsg: "invalid configuration: shutdownGracePeriodCriticalPods {1ms} must be either zero or otherwise >= 1 sec", + }, { + name: "specify ShutdownGracePeriod without enabling GracefulNodeShutdown", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": false} + conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Second} + return conf }, - { - name: "specify ShutdownGracePeriodCriticalPods without enabling GracefulNodeShutdown", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": false} - conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 1 * time.Second} - return conf - }, - errMsg: "invalid configuration: specifying shutdownGracePeriod or shutdownGracePeriodCriticalPods requires feature gate GracefulNodeShutdown", + errMsg: "invalid configuration: specifying shutdownGracePeriod or shutdownGracePeriodCriticalPods requires feature gate GracefulNodeShutdown", + }, { + name: "specify ShutdownGracePeriodCriticalPods without enabling GracefulNodeShutdown", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": false} + conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 1 * time.Second} + return conf }, - { - name: "invalid MemorySwap.SwapBehavior", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"NodeSwap": true} - conf.MemorySwap.SwapBehavior = "invalid-behavior" - return conf - }, - errMsg: "invalid configuration: memorySwap.swapBehavior \"invalid-behavior\" must be one of: \"\", \"LimitedSwap\", or \"UnlimitedSwap\"", + errMsg: "invalid configuration: specifying shutdownGracePeriod or shutdownGracePeriodCriticalPods requires feature gate GracefulNodeShutdown", + }, { + name: "invalid MemorySwap.SwapBehavior", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"NodeSwap": true} + conf.MemorySwap.SwapBehavior = "invalid-behavior" + return conf }, - { - name: "specify MemorySwap.SwapBehavior without enabling NodeSwap", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"NodeSwap": false} - conf.MemorySwap.SwapBehavior = kubetypes.LimitedSwap - return conf - }, - errMsg: "invalid configuration: memorySwap.swapBehavior cannot be set when NodeSwap feature flag is disabled", + errMsg: "invalid configuration: memorySwap.swapBehavior \"invalid-behavior\" must be one of: \"\", \"LimitedSwap\", or \"UnlimitedSwap\"", + }, { + name: "specify MemorySwap.SwapBehavior without enabling NodeSwap", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"NodeSwap": false} + conf.MemorySwap.SwapBehavior = kubetypes.LimitedSwap + return conf }, - { - name: "specify SystemReservedEnforcementKey without specifying SystemReservedCgroup", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.EnforceNodeAllocatable = []string{kubetypes.SystemReservedEnforcementKey} - conf.SystemReservedCgroup = "" - return conf - }, - errMsg: "invalid configuration: systemReservedCgroup (--system-reserved-cgroup) must be specified when \"system-reserved\" contained in enforceNodeAllocatable (--enforce-node-allocatable)", + errMsg: "invalid configuration: memorySwap.swapBehavior cannot be set when NodeSwap feature flag is disabled", + }, { + name: "specify SystemReservedEnforcementKey without specifying SystemReservedCgroup", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EnforceNodeAllocatable = []string{kubetypes.SystemReservedEnforcementKey} + conf.SystemReservedCgroup = "" + return conf }, - { - name: "specify KubeReservedEnforcementKey without specifying KubeReservedCgroup", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.EnforceNodeAllocatable = []string{kubetypes.KubeReservedEnforcementKey} - conf.KubeReservedCgroup = "" - return conf - }, - errMsg: "invalid configuration: kubeReservedCgroup (--kube-reserved-cgroup) must be specified when \"kube-reserved\" contained in enforceNodeAllocatable (--enforce-node-allocatable)", + errMsg: "invalid configuration: systemReservedCgroup (--system-reserved-cgroup) must be specified when \"system-reserved\" contained in enforceNodeAllocatable (--enforce-node-allocatable)", + }, { + name: "specify KubeReservedEnforcementKey without specifying KubeReservedCgroup", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EnforceNodeAllocatable = []string{kubetypes.KubeReservedEnforcementKey} + conf.KubeReservedCgroup = "" + return conf }, - { - name: "specify NodeAllocatableNoneKey with additional enforcements", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.EnforceNodeAllocatable = []string{kubetypes.NodeAllocatableNoneKey, kubetypes.KubeReservedEnforcementKey} - return conf - }, - errMsg: "invalid configuration: enforceNodeAllocatable (--enforce-node-allocatable) may not contain additional enforcements when \"none\" is specified", + errMsg: "invalid configuration: kubeReservedCgroup (--kube-reserved-cgroup) must be specified when \"kube-reserved\" contained in enforceNodeAllocatable (--enforce-node-allocatable)", + }, { + name: "specify NodeAllocatableNoneKey with additional enforcements", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EnforceNodeAllocatable = []string{kubetypes.NodeAllocatableNoneKey, kubetypes.KubeReservedEnforcementKey} + return conf }, - { - name: "invalid EnforceNodeAllocatable", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.EnforceNodeAllocatable = []string{"invalid-enforce-node-allocatable"} - return conf - }, - errMsg: "invalid configuration: option \"invalid-enforce-node-allocatable\" specified for enforceNodeAllocatable (--enforce-node-allocatable). Valid options are \"pods\", \"system-reserved\", \"kube-reserved\", or \"none\"", + errMsg: "invalid configuration: enforceNodeAllocatable (--enforce-node-allocatable) may not contain additional enforcements when \"none\" is specified", + }, { + name: "invalid EnforceNodeAllocatable", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EnforceNodeAllocatable = []string{"invalid-enforce-node-allocatable"} + return conf }, - { - name: "invalid HairpinMode", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.HairpinMode = "invalid-hair-pin-mode" - return conf - }, - errMsg: "invalid configuration: option \"invalid-hair-pin-mode\" specified for hairpinMode (--hairpin-mode). Valid options are \"none\", \"hairpin-veth\" or \"promiscuous-bridge\"", + errMsg: "invalid configuration: option \"invalid-enforce-node-allocatable\" specified for enforceNodeAllocatable (--enforce-node-allocatable). Valid options are \"pods\", \"system-reserved\", \"kube-reserved\", or \"none\"", + }, { + name: "invalid HairpinMode", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.HairpinMode = "invalid-hair-pin-mode" + return conf }, - { - name: "specify ReservedSystemCPUs with SystemReservedCgroup", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.ReservedSystemCPUs = "0-3" - conf.SystemReservedCgroup = "/system.slice" - return conf - }, - errMsg: "invalid configuration: can't use reservedSystemCPUs (--reserved-cpus) with systemReservedCgroup (--system-reserved-cgroup) or kubeReservedCgroup (--kube-reserved-cgroup)", + errMsg: "invalid configuration: option \"invalid-hair-pin-mode\" specified for hairpinMode (--hairpin-mode). Valid options are \"none\", \"hairpin-veth\" or \"promiscuous-bridge\"", + }, { + name: "specify ReservedSystemCPUs with SystemReservedCgroup", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ReservedSystemCPUs = "0-3" + conf.SystemReservedCgroup = "/system.slice" + return conf }, - { - name: "specify ReservedSystemCPUs with KubeReservedCgroup", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.ReservedSystemCPUs = "0-3" - conf.KubeReservedCgroup = "/system.slice" - return conf - }, - errMsg: "invalid configuration: can't use reservedSystemCPUs (--reserved-cpus) with systemReservedCgroup (--system-reserved-cgroup) or kubeReservedCgroup (--kube-reserved-cgroup)", + errMsg: "invalid configuration: can't use reservedSystemCPUs (--reserved-cpus) with systemReservedCgroup (--system-reserved-cgroup) or kubeReservedCgroup (--kube-reserved-cgroup)", + }, { + name: "specify ReservedSystemCPUs with KubeReservedCgroup", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ReservedSystemCPUs = "0-3" + conf.KubeReservedCgroup = "/system.slice" + return conf }, - { - name: "invalid ReservedSystemCPUs", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.ReservedSystemCPUs = "invalid-reserved-system-cpus" - return conf - }, - errMsg: "invalid configuration: unable to parse reservedSystemCPUs (--reserved-cpus) invalid-reserved-system-cpus, error:", + errMsg: "invalid configuration: can't use reservedSystemCPUs (--reserved-cpus) with systemReservedCgroup (--system-reserved-cgroup) or kubeReservedCgroup (--kube-reserved-cgroup)", + }, { + name: "invalid ReservedSystemCPUs", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ReservedSystemCPUs = "invalid-reserved-system-cpus" + return conf }, - { - name: "enable MemoryQoS without specifying MemoryThrottlingFactor", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"MemoryQoS": true} - conf.MemoryThrottlingFactor = nil - return conf - }, - errMsg: "invalid configuration: memoryThrottlingFactor is required when MemoryQoS feature flag is enabled", + errMsg: "invalid configuration: unable to parse reservedSystemCPUs (--reserved-cpus) invalid-reserved-system-cpus, error:", + }, { + name: "enable MemoryQoS without specifying MemoryThrottlingFactor", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"MemoryQoS": true} + conf.MemoryThrottlingFactor = nil + return conf }, - { - name: "invalid MemoryThrottlingFactor", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.MemoryThrottlingFactor = utilpointer.Float64(1.1) - return conf - }, - errMsg: "invalid configuration: memoryThrottlingFactor 1.1 must be greater than 0 and less than or equal to 1.0", + errMsg: "invalid configuration: memoryThrottlingFactor is required when MemoryQoS feature flag is enabled", + }, { + name: "invalid MemoryThrottlingFactor", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.MemoryThrottlingFactor = utilpointer.Float64(1.1) + return conf }, - { - name: "invalid Taint.TimeAdded", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - now := metav1.Now() - conf.RegisterWithTaints = []v1.Taint{{TimeAdded: &now}} - return conf - }, - errMsg: "invalid configuration: taint.TimeAdded is not nil", + errMsg: "invalid configuration: memoryThrottlingFactor 1.1 must be greater than 0 and less than or equal to 1.0", + }, { + name: "invalid Taint.TimeAdded", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + now := metav1.Now() + conf.RegisterWithTaints = []v1.Taint{{TimeAdded: &now}} + return conf }, - { - name: "specify tracing with KubeletTracing disabled", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - samplingRate := int32(99999) - conf.FeatureGates = map[string]bool{"KubeletTracing": false} - conf.Tracing = &tracingapi.TracingConfiguration{SamplingRatePerMillion: &samplingRate} - return conf - }, - errMsg: "invalid configuration: tracing should not be configured if KubeletTracing feature flag is disabled.", + errMsg: "invalid configuration: taint.TimeAdded is not nil", + }, { + name: "specify tracing with KubeletTracing disabled", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + samplingRate := int32(99999) + conf.FeatureGates = map[string]bool{"KubeletTracing": false} + conf.Tracing = &tracingapi.TracingConfiguration{SamplingRatePerMillion: &samplingRate} + return conf }, - { - name: "specify tracing invalid sampling rate", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - samplingRate := int32(-1) - conf.FeatureGates = map[string]bool{"KubeletTracing": true} - conf.Tracing = &tracingapi.TracingConfiguration{SamplingRatePerMillion: &samplingRate} - return conf - }, - errMsg: "tracing.samplingRatePerMillion: Invalid value: -1: sampling rate must be positive", + errMsg: "invalid configuration: tracing should not be configured if KubeletTracing feature flag is disabled.", + }, { + name: "specify tracing invalid sampling rate", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + samplingRate := int32(-1) + conf.FeatureGates = map[string]bool{"KubeletTracing": true} + conf.Tracing = &tracingapi.TracingConfiguration{SamplingRatePerMillion: &samplingRate} + return conf }, - { - name: "specify tracing invalid endpoint", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - ep := "dn%2s://localhost:4317" - conf.FeatureGates = map[string]bool{"KubeletTracing": true} - conf.Tracing = &tracingapi.TracingConfiguration{Endpoint: &ep} - return conf - }, - errMsg: "tracing.endpoint: Invalid value: \"dn%2s://localhost:4317\": parse \"dn%2s://localhost:4317\": first path segment in URL cannot contain colon", + errMsg: "tracing.samplingRatePerMillion: Invalid value: -1: sampling rate must be positive", + }, { + name: "specify tracing invalid endpoint", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + ep := "dn%2s://localhost:4317" + conf.FeatureGates = map[string]bool{"KubeletTracing": true} + conf.Tracing = &tracingapi.TracingConfiguration{Endpoint: &ep} + return conf }, - { - name: "invalid GracefulNodeShutdownBasedOnPodPriority", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"GracefulNodeShutdownBasedOnPodPriority": true} - conf.ShutdownGracePeriodByPodPriority = []kubeletconfig.ShutdownGracePeriodByPodPriority{ - { - Priority: 0, - ShutdownGracePeriodSeconds: 0, - }} - return conf - }, - errMsg: "invalid configuration: Cannot specify both shutdownGracePeriodByPodPriority and shutdownGracePeriod at the same time", + errMsg: "tracing.endpoint: Invalid value: \"dn%2s://localhost:4317\": parse \"dn%2s://localhost:4317\": first path segment in URL cannot contain colon", + }, { + name: "invalid GracefulNodeShutdownBasedOnPodPriority", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdownBasedOnPodPriority": true} + conf.ShutdownGracePeriodByPodPriority = []kubeletconfig.ShutdownGracePeriodByPodPriority{{ + Priority: 0, + ShutdownGracePeriodSeconds: 0, + }} + return conf }, - { - name: "Specifying shutdownGracePeriodByPodPriority without enable GracefulNodeShutdownBasedOnPodPriority", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"GracefulNodeShutdownBasedOnPodPriority": false} - conf.ShutdownGracePeriodByPodPriority = []kubeletconfig.ShutdownGracePeriodByPodPriority{ - { - Priority: 0, - ShutdownGracePeriodSeconds: 0, - }} - return conf - }, - errMsg: "invalid configuration: Specifying shutdownGracePeriodByPodPriority requires feature gate GracefulNodeShutdownBasedOnPodPriority", + errMsg: "invalid configuration: Cannot specify both shutdownGracePeriodByPodPriority and shutdownGracePeriod at the same time", + }, { + name: "Specifying shutdownGracePeriodByPodPriority without enable GracefulNodeShutdownBasedOnPodPriority", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdownBasedOnPodPriority": false} + conf.ShutdownGracePeriodByPodPriority = []kubeletconfig.ShutdownGracePeriodByPodPriority{{ + Priority: 0, + ShutdownGracePeriodSeconds: 0, + }} + return conf }, - { - name: "enableSystemLogQuery is enabled without NodeLogQuery feature gate", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.EnableSystemLogQuery = true - return conf - }, - errMsg: "invalid configuration: NodeLogQuery feature gate is required for enableSystemLogHandler", + errMsg: "invalid configuration: Specifying shutdownGracePeriodByPodPriority requires feature gate GracefulNodeShutdownBasedOnPodPriority", + }, { + name: "enableSystemLogQuery is enabled without NodeLogQuery feature gate", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EnableSystemLogQuery = true + return conf }, - { - name: "enableSystemLogQuery is enabled without enableSystemLogHandler", - configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"NodeLogQuery": true} - conf.EnableSystemLogHandler = false - conf.EnableSystemLogQuery = true - return conf - }, - errMsg: "invalid configuration: enableSystemLogHandler is required for enableSystemLogQuery", + errMsg: "invalid configuration: NodeLogQuery feature gate is required for enableSystemLogHandler", + }, { + name: "enableSystemLogQuery is enabled without enableSystemLogHandler", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"NodeLogQuery": true} + conf.EnableSystemLogHandler = false + conf.EnableSystemLogQuery = true + return conf }, - } + errMsg: "invalid configuration: enableSystemLogHandler is required for enableSystemLogQuery", + }} for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/proxy/apis/config/validation/validation_test.go b/pkg/proxy/apis/config/validation/validation_test.go index eee26449a21..8810bc4f1ce 100644 --- a/pkg/proxy/apis/config/validation/validation_test.go +++ b/pkg/proxy/apis/config/validation/validation_test.go @@ -36,183 +36,173 @@ func TestValidateKubeProxyConfiguration(t *testing.T) { } else { proxyMode = kubeproxyconfig.ProxyModeIPVS } - successCases := []kubeproxyconfig.KubeProxyConfiguration{ - { - BindAddress: "192.168.59.103", - HealthzBindAddress: "0.0.0.0:10256", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Mode: proxyMode, - IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ - SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - }, - Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ - MaxPerCore: pointer.Int32(1), - Min: pointer.Int32(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, + successCases := []kubeproxyconfig.KubeProxyConfiguration{{ + BindAddress: "192.168.59.103", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, }, - { - BindAddress: "192.168.59.103", - HealthzBindAddress: "0.0.0.0:10256", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ - MaxPerCore: pointer.Int32(1), - Min: pointer.Int32(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, + Mode: proxyMode, + IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, }, - { - BindAddress: "192.168.59.103", - HealthzBindAddress: "", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ - MaxPerCore: pointer.Int32(1), - Min: pointer.Int32(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + MaxPerCore: pointer.Int32(1), + Min: pointer.Int32(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, }, - { - BindAddress: "fd00:192:168:59::103", - HealthzBindAddress: "", - MetricsBindAddress: "[::1]:10249", - ClusterCIDR: "fd00:192:168:59::/64", - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ - MaxPerCore: pointer.Int32(1), - Min: pointer.Int32(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, + }, { + BindAddress: "192.168.59.103", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, }, - { - BindAddress: "10.10.12.11", - HealthzBindAddress: "0.0.0.0:12345", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ - MaxPerCore: pointer.Int32(1), - Min: pointer.Int32(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + MaxPerCore: pointer.Int32(1), + Min: pointer.Int32(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, }, - { - BindAddress: "10.10.12.11", - HealthzBindAddress: "0.0.0.0:12345", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "fd00:192:168::/64", - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ - MaxPerCore: pointer.Int32(1), - Min: pointer.Int32(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, + }, { + BindAddress: "192.168.59.103", + HealthzBindAddress: "", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, }, - { - BindAddress: "10.10.12.11", - HealthzBindAddress: "0.0.0.0:12345", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64", - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ - MaxPerCore: pointer.Int32(1), - Min: pointer.Int32(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + MaxPerCore: pointer.Int32(1), + Min: pointer.Int32(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, }, - { - BindAddress: "10.10.12.11", - HealthzBindAddress: "0.0.0.0:12345", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ - MaxPerCore: pointer.Int32(1), - Min: pointer.Int32(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, - DetectLocalMode: kubeproxyconfig.LocalModeInterfaceNamePrefix, - DetectLocal: kubeproxyconfig.DetectLocalConfiguration{ - InterfaceNamePrefix: "vethabcde", - }, + }, { + BindAddress: "fd00:192:168:59::103", + HealthzBindAddress: "", + MetricsBindAddress: "[::1]:10249", + ClusterCIDR: "fd00:192:168:59::/64", + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, }, - { - BindAddress: "10.10.12.11", - HealthzBindAddress: "0.0.0.0:12345", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ - MaxPerCore: pointer.Int32(1), - Min: pointer.Int32(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, - DetectLocalMode: kubeproxyconfig.LocalModeBridgeInterface, - DetectLocal: kubeproxyconfig.DetectLocalConfiguration{ - BridgeInterface: "avz", - }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + MaxPerCore: pointer.Int32(1), + Min: pointer.Int32(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, }, - } + }, { + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + MaxPerCore: pointer.Int32(1), + Min: pointer.Int32(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, { + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "fd00:192:168::/64", + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + MaxPerCore: pointer.Int32(1), + Min: pointer.Int32(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, { + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64", + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + MaxPerCore: pointer.Int32(1), + Min: pointer.Int32(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, { + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + MaxPerCore: pointer.Int32(1), + Min: pointer.Int32(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + DetectLocalMode: kubeproxyconfig.LocalModeInterfaceNamePrefix, + DetectLocal: kubeproxyconfig.DetectLocalConfiguration{ + InterfaceNamePrefix: "vethabcde", + }, + }, { + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + MaxPerCore: pointer.Int32(1), + Min: pointer.Int32(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + DetectLocalMode: kubeproxyconfig.LocalModeBridgeInterface, + DetectLocal: kubeproxyconfig.DetectLocalConfiguration{ + BridgeInterface: "avz", + }, + }} for _, successCase := range successCases { if errs := Validate(&successCase); len(errs) != 0 { diff --git a/pkg/scheduler/apis/config/validation/validation_test.go b/pkg/scheduler/apis/config/validation/validation_test.go index ddb9a9ccc91..029c6985c74 100644 --- a/pkg/scheduler/apis/config/validation/validation_test.go +++ b/pkg/scheduler/apis/config/validation/validation_test.go @@ -57,42 +57,35 @@ func TestValidateKubeSchedulerConfigurationV1beta2(t *testing.T) { PodInitialBackoffSeconds: podInitialBackoffSeconds, PodMaxBackoffSeconds: podMaxBackoffSeconds, PercentageOfNodesToScore: pointer.Int32(35), - Profiles: []config.KubeSchedulerProfile{ - { - SchedulerName: "me", - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomSort"}}, - }, - Score: config.PluginSet{ - Disabled: []config.Plugin{{Name: "*"}}, - }, + Profiles: []config.KubeSchedulerProfile{{ + SchedulerName: "me", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomSort"}}, }, - PluginConfig: []config.PluginConfig{ - { - Name: "DefaultPreemption", - Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100}, - }, + Score: config.PluginSet{ + Disabled: []config.Plugin{{Name: "*"}}, }, }, - { - SchedulerName: "other", - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomSort"}}, - }, - Bind: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomBind"}}, - }, + PluginConfig: []config.PluginConfig{{ + Name: "DefaultPreemption", + Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100}, + }}, + }, { + SchedulerName: "other", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomSort"}}, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomBind"}}, }, }, - }, - Extenders: []config.Extender{ - { - PrioritizeVerb: "prioritize", - Weight: 1, - }, - }, + }}, + Extenders: []config.Extender{{ + PrioritizeVerb: "prioritize", + Weight: 1, + }}, } invalidParallelismValue := validConfig.DeepCopy() @@ -145,60 +138,46 @@ func TestValidateKubeSchedulerConfigurationV1beta2(t *testing.T) { extenderNegativeWeight.Extenders[0].Weight = -1 invalidNodePercentage := validConfig.DeepCopy() - invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{ - { - Name: "DefaultPreemption", - Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100}, - }, - } + invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{{ + Name: "DefaultPreemption", + Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100}, + }} invalidPluginArgs := validConfig.DeepCopy() - invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{ - { - Name: "DefaultPreemption", - Args: &config.InterPodAffinityArgs{}, - }, - } + invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{{ + Name: "DefaultPreemption", + Args: &config.InterPodAffinityArgs{}, + }} duplicatedPluginConfig := validConfig.DeepCopy() - duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{ - { - Name: "config", - }, - { - Name: "config", - }, - } + duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{{ + Name: "config", + }, { + Name: "config", + }} mismatchQueueSort := validConfig.DeepCopy() - mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{ - { - SchedulerName: "me", - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "PrioritySort"}}, - }, - }, - PluginConfig: []config.PluginConfig{ - { - Name: "PrioritySort", - }, + mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{{ + SchedulerName: "me", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "PrioritySort"}}, }, }, - { - SchedulerName: "other", - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomSort"}}, - }, - }, - PluginConfig: []config.PluginConfig{ - { - Name: "CustomSort", - }, + PluginConfig: []config.PluginConfig{{ + Name: "PrioritySort", + }}, + }, { + SchedulerName: "other", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomSort"}}, }, }, - } + PluginConfig: []config.PluginConfig{{ + Name: "CustomSort", + }}, + }} extenderDuplicateManagedResource := validConfig.DeepCopy() extenderDuplicateManagedResource.Extenders[0].ManagedResources = []config.ExtenderManagedResource{ @@ -455,42 +434,35 @@ func TestValidateKubeSchedulerConfigurationV1beta3(t *testing.T) { PodInitialBackoffSeconds: podInitialBackoffSeconds, PodMaxBackoffSeconds: podMaxBackoffSeconds, PercentageOfNodesToScore: pointer.Int32(35), - Profiles: []config.KubeSchedulerProfile{ - { - SchedulerName: "me", - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomSort"}}, - }, - Score: config.PluginSet{ - Disabled: []config.Plugin{{Name: "*"}}, - }, + Profiles: []config.KubeSchedulerProfile{{ + SchedulerName: "me", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomSort"}}, }, - PluginConfig: []config.PluginConfig{ - { - Name: "DefaultPreemption", - Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100}, - }, + Score: config.PluginSet{ + Disabled: []config.Plugin{{Name: "*"}}, }, }, - { - SchedulerName: "other", - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomSort"}}, - }, - Bind: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomBind"}}, - }, + PluginConfig: []config.PluginConfig{{ + Name: "DefaultPreemption", + Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100}, + }}, + }, { + SchedulerName: "other", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomSort"}}, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomBind"}}, }, }, - }, - Extenders: []config.Extender{ - { - PrioritizeVerb: "prioritize", - Weight: 1, - }, - }, + }}, + Extenders: []config.Extender{{ + PrioritizeVerb: "prioritize", + Weight: 1, + }}, } invalidParallelismValue := validConfig.DeepCopy() @@ -543,20 +515,16 @@ func TestValidateKubeSchedulerConfigurationV1beta3(t *testing.T) { extenderNegativeWeight.Extenders[0].Weight = -1 invalidNodePercentage := validConfig.DeepCopy() - invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{ - { - Name: "DefaultPreemption", - Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100}, - }, - } + invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{{ + Name: "DefaultPreemption", + Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100}, + }} invalidPluginArgs := validConfig.DeepCopy() - invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{ - { - Name: "DefaultPreemption", - Args: &config.InterPodAffinityArgs{}, - }, - } + invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{{ + Name: "DefaultPreemption", + Args: &config.InterPodAffinityArgs{}, + }} duplicatedPlugins := validConfig.DeepCopy() duplicatedPlugins.Profiles[0].Plugins.PreEnqueue.Enabled = []config.Plugin{ @@ -565,44 +533,34 @@ func TestValidateKubeSchedulerConfigurationV1beta3(t *testing.T) { } duplicatedPluginConfig := validConfig.DeepCopy() - duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{ - { - Name: "config", - }, - { - Name: "config", - }, - } + duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{{ + Name: "config", + }, { + Name: "config", + }} mismatchQueueSort := validConfig.DeepCopy() - mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{ - { - SchedulerName: "me", - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "PrioritySort"}}, - }, - }, - PluginConfig: []config.PluginConfig{ - { - Name: "PrioritySort", - }, + mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{{ + SchedulerName: "me", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "PrioritySort"}}, }, }, - { - SchedulerName: "other", - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomSort"}}, - }, - }, - PluginConfig: []config.PluginConfig{ - { - Name: "CustomSort", - }, + PluginConfig: []config.PluginConfig{{ + Name: "PrioritySort", + }}, + }, { + SchedulerName: "other", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomSort"}}, }, }, - } + PluginConfig: []config.PluginConfig{{ + Name: "CustomSort", + }}, + }} extenderDuplicateManagedResource := validConfig.DeepCopy() extenderDuplicateManagedResource.Extenders[0].ManagedResources = []config.ExtenderManagedResource{ @@ -858,44 +816,37 @@ func TestValidateKubeSchedulerConfigurationV1(t *testing.T) { }, PodInitialBackoffSeconds: podInitialBackoffSeconds, PodMaxBackoffSeconds: podMaxBackoffSeconds, - Profiles: []config.KubeSchedulerProfile{ - { - SchedulerName: "me", - PercentageOfNodesToScore: pointer.Int32(35), - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomSort"}}, - }, - Score: config.PluginSet{ - Disabled: []config.Plugin{{Name: "*"}}, - }, + Profiles: []config.KubeSchedulerProfile{{ + SchedulerName: "me", + PercentageOfNodesToScore: pointer.Int32(35), + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomSort"}}, }, - PluginConfig: []config.PluginConfig{ - { - Name: "DefaultPreemption", - Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100}, - }, + Score: config.PluginSet{ + Disabled: []config.Plugin{{Name: "*"}}, }, }, - { - SchedulerName: "other", - PercentageOfNodesToScore: pointer.Int32(35), - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomSort"}}, - }, - Bind: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomBind"}}, - }, + PluginConfig: []config.PluginConfig{{ + Name: "DefaultPreemption", + Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100}, + }}, + }, { + SchedulerName: "other", + PercentageOfNodesToScore: pointer.Int32(35), + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomSort"}}, + }, + Bind: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomBind"}}, }, }, - }, - Extenders: []config.Extender{ - { - PrioritizeVerb: "prioritize", - Weight: 1, - }, - }, + }}, + Extenders: []config.Extender{{ + PrioritizeVerb: "prioritize", + Weight: 1, + }}, } invalidParallelismValue := validConfig.DeepCopy() @@ -948,60 +899,46 @@ func TestValidateKubeSchedulerConfigurationV1(t *testing.T) { extenderNegativeWeight.Extenders[0].Weight = -1 invalidNodePercentage := validConfig.DeepCopy() - invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{ - { - Name: "DefaultPreemption", - Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100}, - }, - } + invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{{ + Name: "DefaultPreemption", + Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100}, + }} invalidPluginArgs := validConfig.DeepCopy() - invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{ - { - Name: "DefaultPreemption", - Args: &config.InterPodAffinityArgs{}, - }, - } + invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{{ + Name: "DefaultPreemption", + Args: &config.InterPodAffinityArgs{}, + }} duplicatedPluginConfig := validConfig.DeepCopy() - duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{ - { - Name: "config", - }, - { - Name: "config", - }, - } + duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{{ + Name: "config", + }, { + Name: "config", + }} mismatchQueueSort := validConfig.DeepCopy() - mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{ - { - SchedulerName: "me", - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "PrioritySort"}}, - }, - }, - PluginConfig: []config.PluginConfig{ - { - Name: "PrioritySort", - }, + mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{{ + SchedulerName: "me", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "PrioritySort"}}, }, }, - { - SchedulerName: "other", - Plugins: &config.Plugins{ - QueueSort: config.PluginSet{ - Enabled: []config.Plugin{{Name: "CustomSort"}}, - }, - }, - PluginConfig: []config.PluginConfig{ - { - Name: "CustomSort", - }, + PluginConfig: []config.PluginConfig{{ + Name: "PrioritySort", + }}, + }, { + SchedulerName: "other", + Plugins: &config.Plugins{ + QueueSort: config.PluginSet{ + Enabled: []config.Plugin{{Name: "CustomSort"}}, }, }, - } + PluginConfig: []config.PluginConfig{{ + Name: "CustomSort", + }}, + }} extenderDuplicateManagedResource := validConfig.DeepCopy() extenderDuplicateManagedResource.Extenders[0].ManagedResources = []config.ExtenderManagedResource{ diff --git a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation_test.go b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation_test.go index 03415d0449d..ef161730fe8 100644 --- a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation_test.go +++ b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation_test.go @@ -27,158 +27,124 @@ func TestValidateConfiguration(t *testing.T) { name string config eventratelimitapi.Configuration expectedResult bool - }{ - { - name: "valid server", - config: eventratelimitapi.Configuration{ - Limits: []eventratelimitapi.Limit{ - { - Type: "Server", - Burst: 5, - QPS: 1, - }, - }, - }, - expectedResult: true, + }{{ + name: "valid server", + config: eventratelimitapi.Configuration{ + Limits: []eventratelimitapi.Limit{{ + Type: "Server", + Burst: 5, + QPS: 1, + }}, }, - { - name: "valid namespace", - config: eventratelimitapi.Configuration{ - Limits: []eventratelimitapi.Limit{ - { - Type: "Namespace", - Burst: 10, - QPS: 2, - CacheSize: 100, - }, - }, - }, - expectedResult: true, + expectedResult: true, + }, { + name: "valid namespace", + config: eventratelimitapi.Configuration{ + Limits: []eventratelimitapi.Limit{{ + Type: "Namespace", + Burst: 10, + QPS: 2, + CacheSize: 100, + }}, }, - { - name: "valid user", - config: eventratelimitapi.Configuration{ - Limits: []eventratelimitapi.Limit{ - { - Type: "User", - Burst: 10, - QPS: 2, - CacheSize: 100, - }, - }, - }, - expectedResult: true, + expectedResult: true, + }, { + name: "valid user", + config: eventratelimitapi.Configuration{ + Limits: []eventratelimitapi.Limit{{ + Type: "User", + Burst: 10, + QPS: 2, + CacheSize: 100, + }}, }, - { - name: "valid source+object", - config: eventratelimitapi.Configuration{ - Limits: []eventratelimitapi.Limit{ - { - Type: "SourceAndObject", - Burst: 5, - QPS: 1, - CacheSize: 1000, - }, - }, - }, - expectedResult: true, + expectedResult: true, + }, { + name: "valid source+object", + config: eventratelimitapi.Configuration{ + Limits: []eventratelimitapi.Limit{{ + Type: "SourceAndObject", + Burst: 5, + QPS: 1, + CacheSize: 1000, + }}, }, - { - name: "valid multiple", - config: eventratelimitapi.Configuration{ - Limits: []eventratelimitapi.Limit{ - { - Type: "Server", - Burst: 5, - QPS: 1, - }, - { - Type: "Namespace", - Burst: 10, - QPS: 2, - CacheSize: 100, - }, - { - Type: "SourceAndObject", - Burst: 25, - QPS: 10, - CacheSize: 1000, - }, - }, - }, - expectedResult: true, + expectedResult: true, + }, { + name: "valid multiple", + config: eventratelimitapi.Configuration{ + Limits: []eventratelimitapi.Limit{{ + Type: "Server", + Burst: 5, + QPS: 1, + }, { + Type: "Namespace", + Burst: 10, + QPS: 2, + CacheSize: 100, + }, { + Type: "SourceAndObject", + Burst: 25, + QPS: 10, + CacheSize: 1000, + }}, }, - { - name: "missing limits", - config: eventratelimitapi.Configuration{}, - expectedResult: false, + expectedResult: true, + }, { + name: "missing limits", + config: eventratelimitapi.Configuration{}, + expectedResult: false, + }, { + name: "missing type", + config: eventratelimitapi.Configuration{ + Limits: []eventratelimitapi.Limit{{ + Burst: 25, + QPS: 10, + CacheSize: 1000, + }}, }, - { - name: "missing type", - config: eventratelimitapi.Configuration{ - Limits: []eventratelimitapi.Limit{ - { - Burst: 25, - QPS: 10, - CacheSize: 1000, - }, - }, - }, - expectedResult: false, + expectedResult: false, + }, { + name: "invalid type", + config: eventratelimitapi.Configuration{ + Limits: []eventratelimitapi.Limit{{ + Type: "unknown-type", + Burst: 25, + QPS: 10, + CacheSize: 1000, + }}, }, - { - name: "invalid type", - config: eventratelimitapi.Configuration{ - Limits: []eventratelimitapi.Limit{ - { - Type: "unknown-type", - Burst: 25, - QPS: 10, - CacheSize: 1000, - }, - }, - }, - expectedResult: false, + expectedResult: false, + }, { + name: "missing burst", + config: eventratelimitapi.Configuration{ + Limits: []eventratelimitapi.Limit{{ + Type: "Server", + QPS: 1, + }}, }, - { - name: "missing burst", - config: eventratelimitapi.Configuration{ - Limits: []eventratelimitapi.Limit{ - { - Type: "Server", - QPS: 1, - }, - }, - }, - expectedResult: false, + expectedResult: false, + }, { + name: "missing qps", + config: eventratelimitapi.Configuration{ + Limits: []eventratelimitapi.Limit{{ + Type: "Server", + Burst: 5, + }}, }, - { - name: "missing qps", - config: eventratelimitapi.Configuration{ - Limits: []eventratelimitapi.Limit{ - { - Type: "Server", - Burst: 5, - }, - }, - }, - expectedResult: false, + expectedResult: false, + }, { + name: "negative cache size", + config: eventratelimitapi.Configuration{ + Limits: []eventratelimitapi.Limit{{ + Type: "Namespace", + Burst: 10, + QPS: 2, + CacheSize: -1, + }}, }, - { - name: "negative cache size", - config: eventratelimitapi.Configuration{ - Limits: []eventratelimitapi.Limit{ - { - Type: "Namespace", - Burst: 10, - QPS: 2, - CacheSize: -1, - }, - }, - }, - expectedResult: false, - }, - } + expectedResult: false, + }} for _, tc := range cases { errs := ValidateConfiguration(&tc.config) if e, a := tc.expectedResult, len(errs) == 0; e != a { diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation_test.go b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation_test.go index d444921a51a..44a07f145a0 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation_test.go +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation_test.go @@ -17,9 +17,10 @@ limitations under the License. package validation import ( + "testing" + api "k8s.io/kubernetes/pkg/apis/core" internalapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction" - "testing" ) func TestValidateConfiguration(t *testing.T) { @@ -28,38 +29,34 @@ func TestValidateConfiguration(t *testing.T) { config internalapi.Configuration testName string testStatus bool - }{ - { - config: internalapi.Configuration{ - Default: []api.Toleration{ - {Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}, - {Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}, - {Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}, - {Operator: "Exists", Effect: "NoSchedule"}, - }, - Whitelist: []api.Toleration{ - {Key: "foo", Value: "bar", Effect: "NoSchedule"}, - {Key: "foo", Operator: "Equal", Value: "bar"}, - }, + }{{ + config: internalapi.Configuration{ + Default: []api.Toleration{ + {Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}, + {Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}, + {Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}, + {Operator: "Exists", Effect: "NoSchedule"}, }, - testName: "Valid cases", - testStatus: true, - }, - { - config: internalapi.Configuration{ - Whitelist: []api.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}}, + Whitelist: []api.Toleration{ + {Key: "foo", Value: "bar", Effect: "NoSchedule"}, + {Key: "foo", Operator: "Equal", Value: "bar"}, }, - testName: "Invalid case", - testStatus: false, }, - { - config: internalapi.Configuration{ - Default: []api.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}, - }, - testName: "Invalid case", - testStatus: false, + testName: "Valid cases", + testStatus: true, + }, { + config: internalapi.Configuration{ + Whitelist: []api.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}}, }, - } + testName: "Invalid case", + testStatus: false, + }, { + config: internalapi.Configuration{ + Default: []api.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}, + }, + testName: "Invalid case", + testStatus: false, + }} for i := range tests { errs := ValidateConfiguration(&tests[i].config) diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation_test.go index d6da2298882..1a32143817b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation_test.go @@ -17,9 +17,10 @@ limitations under the License. package validation import ( + "testing" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "testing" ) func TestValidateListOptions(t *testing.T) { @@ -32,164 +33,145 @@ func TestValidateListOptions(t *testing.T) { opts internalversion.ListOptions watchListFeatureEnabled bool expectErrors []string - }{ - { - name: "valid-default", - opts: internalversion.ListOptions{}, + }{{ + name: "valid-default", + opts: internalversion.ListOptions{}, + }, { + name: "valid-resourceversionmatch-exact", + opts: internalversion.ListOptions{ + ResourceVersion: "1", + ResourceVersionMatch: metav1.ResourceVersionMatchExact, }, - { - name: "valid-resourceversionmatch-exact", - opts: internalversion.ListOptions{ - ResourceVersion: "1", - ResourceVersionMatch: metav1.ResourceVersionMatchExact, - }, + }, { + name: "invalid-resourceversionmatch-exact", + opts: internalversion.ListOptions{ + ResourceVersion: "0", + ResourceVersionMatch: metav1.ResourceVersionMatchExact, }, - { - name: "invalid-resourceversionmatch-exact", - opts: internalversion.ListOptions{ - ResourceVersion: "0", - ResourceVersionMatch: metav1.ResourceVersionMatchExact, - }, - expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\""}, + expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\""}, + }, { + name: "valid-resourceversionmatch-notolderthan", + opts: internalversion.ListOptions{ + ResourceVersion: "0", + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, }, - { - name: "valid-resourceversionmatch-notolderthan", - opts: internalversion.ListOptions{ - ResourceVersion: "0", - ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, - }, + }, { + name: "invalid-resourceversionmatch", + opts: internalversion.ListOptions{ + ResourceVersion: "0", + ResourceVersionMatch: "foo", }, - { - name: "invalid-resourceversionmatch", - opts: internalversion.ListOptions{ - ResourceVersion: "0", - ResourceVersionMatch: "foo", - }, - expectErrors: []string{"resourceVersionMatch: Unsupported value: \"foo\": supported values: \"Exact\", \"NotOlderThan\", \"\""}, + expectErrors: []string{"resourceVersionMatch: Unsupported value: \"foo\": supported values: \"Exact\", \"NotOlderThan\", \"\""}, + }, { + name: "list-sendInitialEvents-forbidden", + opts: internalversion.ListOptions{ + SendInitialEvents: boolPtrFn(true), }, - { - name: "list-sendInitialEvents-forbidden", - opts: internalversion.ListOptions{ - SendInitialEvents: boolPtrFn(true), - }, - expectErrors: []string{"sendInitialEvents: Forbidden: sendInitialEvents is forbidden for list"}, + expectErrors: []string{"sendInitialEvents: Forbidden: sendInitialEvents is forbidden for list"}, + }, { + name: "valid-watch-default", + opts: internalversion.ListOptions{ + Watch: true, }, - { - name: "valid-watch-default", - opts: internalversion.ListOptions{ - Watch: true, - }, + }, { + name: "valid-watch-sendInitialEvents-on", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: boolPtrFn(true), + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + AllowWatchBookmarks: true, }, - { - name: "valid-watch-sendInitialEvents-on", - opts: internalversion.ListOptions{ - Watch: true, - SendInitialEvents: boolPtrFn(true), - ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, - AllowWatchBookmarks: true, - }, - watchListFeatureEnabled: true, + watchListFeatureEnabled: true, + }, { + name: "valid-watch-sendInitialEvents-off", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: boolPtrFn(false), + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + AllowWatchBookmarks: true, }, - { - name: "valid-watch-sendInitialEvents-off", - opts: internalversion.ListOptions{ - Watch: true, - SendInitialEvents: boolPtrFn(false), - ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, - AllowWatchBookmarks: true, - }, - watchListFeatureEnabled: true, + watchListFeatureEnabled: true, + }, { + name: "watch-resourceversionmatch-without-sendInitialEvents-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, }, - { - name: "watch-resourceversionmatch-without-sendInitialEvents-forbidden", - opts: internalversion.ListOptions{ - Watch: true, - ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, - }, - expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch is forbidden for watch unless sendInitialEvents is provided"}, + expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch is forbidden for watch unless sendInitialEvents is provided"}, + }, { + name: "watch-sendInitialEvents-without-resourceversionmatch-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: boolPtrFn(true), }, - { - name: "watch-sendInitialEvents-without-resourceversionmatch-forbidden", - opts: internalversion.ListOptions{ - Watch: true, - SendInitialEvents: boolPtrFn(true), - }, - expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + }, { + name: "watch-sendInitialEvents-with-exact-resourceversionmatch-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: boolPtrFn(true), + ResourceVersionMatch: metav1.ResourceVersionMatchExact, + AllowWatchBookmarks: true, }, - { - name: "watch-sendInitialEvents-with-exact-resourceversionmatch-forbidden", - opts: internalversion.ListOptions{ - Watch: true, - SendInitialEvents: boolPtrFn(true), - ResourceVersionMatch: metav1.ResourceVersionMatchExact, - AllowWatchBookmarks: true, - }, - watchListFeatureEnabled: true, - expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "resourceVersionMatch: Unsupported value: \"Exact\": supported values: \"NotOlderThan\""}, + watchListFeatureEnabled: true, + expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "resourceVersionMatch: Unsupported value: \"Exact\": supported values: \"NotOlderThan\""}, + }, { + name: "watch-sendInitialEvents-on-with-empty-resourceversionmatch-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: boolPtrFn(true), + ResourceVersionMatch: "", }, - { - name: "watch-sendInitialEvents-on-with-empty-resourceversionmatch-forbidden", - opts: internalversion.ListOptions{ - Watch: true, - SendInitialEvents: boolPtrFn(true), - ResourceVersionMatch: "", - }, - expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + }, { + name: "watch-sendInitialEvents-off-with-empty-resourceversionmatch-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: boolPtrFn(false), + ResourceVersionMatch: "", }, - { - name: "watch-sendInitialEvents-off-with-empty-resourceversionmatch-forbidden", - opts: internalversion.ListOptions{ - Watch: true, - SendInitialEvents: boolPtrFn(false), - ResourceVersionMatch: "", - }, - expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + }, { + name: "watch-sendInitialEvents-with-incorrect-resourceversionmatch-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: boolPtrFn(true), + ResourceVersionMatch: "incorrect", + AllowWatchBookmarks: true, }, - { - name: "watch-sendInitialEvents-with-incorrect-resourceversionmatch-forbidden", - opts: internalversion.ListOptions{ - Watch: true, - SendInitialEvents: boolPtrFn(true), - ResourceVersionMatch: "incorrect", - AllowWatchBookmarks: true, - }, - watchListFeatureEnabled: true, - expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "resourceVersionMatch: Unsupported value: \"incorrect\": supported values: \"NotOlderThan\""}, + watchListFeatureEnabled: true, + expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "resourceVersionMatch: Unsupported value: \"incorrect\": supported values: \"NotOlderThan\""}, + }, { + // note that validating allowWatchBookmarks would break backward compatibility + // because it was possible to request initial events via resourceVersion=0 before this change + name: "watch-sendInitialEvents-no-allowWatchBookmark", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: boolPtrFn(true), + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, }, - { - // note that validating allowWatchBookmarks would break backward compatibility - // because it was possible to request initial events via resourceVersion=0 before this change - name: "watch-sendInitialEvents-no-allowWatchBookmark", - opts: internalversion.ListOptions{ - Watch: true, - SendInitialEvents: boolPtrFn(true), - ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, - }, - watchListFeatureEnabled: true, + watchListFeatureEnabled: true, + }, { + name: "watch-sendInitialEvents-no-watchlist-fg-disabled", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: boolPtrFn(true), + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + AllowWatchBookmarks: true, }, - { - name: "watch-sendInitialEvents-no-watchlist-fg-disabled", - opts: internalversion.ListOptions{ - Watch: true, - SendInitialEvents: boolPtrFn(true), - ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, - AllowWatchBookmarks: true, - }, - expectErrors: []string{"sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + expectErrors: []string{"sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + }, { + name: "watch-sendInitialEvents-no-watchlist-fg-disabled", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: boolPtrFn(true), + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + AllowWatchBookmarks: true, + Continue: "123", }, - { - name: "watch-sendInitialEvents-no-watchlist-fg-disabled", - opts: internalversion.ListOptions{ - Watch: true, - SendInitialEvents: boolPtrFn(true), - ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, - AllowWatchBookmarks: true, - Continue: "123", - }, - watchListFeatureEnabled: true, - expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch is forbidden when continue is provided"}, - }, - } + watchListFeatureEnabled: true, + expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch is forbidden when continue is provided"}, + }} for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation_test.go index 60b0cd3dbad..e9c6c161620 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation_test.go @@ -136,31 +136,26 @@ func TestValidPatchOptions(t *testing.T) { tests := []struct { opts metav1.PatchOptions patchType types.PatchType - }{ - { - opts: metav1.PatchOptions{ - Force: boolPtr(true), - FieldManager: "kubectl", - }, - patchType: types.ApplyPatchType, + }{{ + opts: metav1.PatchOptions{ + Force: boolPtr(true), + FieldManager: "kubectl", }, - { - opts: metav1.PatchOptions{ - FieldManager: "kubectl", - }, - patchType: types.ApplyPatchType, + patchType: types.ApplyPatchType, + }, { + opts: metav1.PatchOptions{ + FieldManager: "kubectl", }, - { - opts: metav1.PatchOptions{}, - patchType: types.MergePatchType, + patchType: types.ApplyPatchType, + }, { + opts: metav1.PatchOptions{}, + patchType: types.MergePatchType, + }, { + opts: metav1.PatchOptions{ + FieldManager: "patcher", }, - { - opts: metav1.PatchOptions{ - FieldManager: "patcher", - }, - patchType: types.MergePatchType, - }, - } + patchType: types.MergePatchType, + }} for _, test := range tests { t.Run(fmt.Sprintf("%v", test.opts), func(t *testing.T) { @@ -243,36 +238,30 @@ func TestValidateFieldManagerInvalid(t *testing.T) { } func TestValidateManagedFieldsInvalid(t *testing.T) { - tests := []metav1.ManagedFieldsEntry{ - { - Operation: metav1.ManagedFieldsOperationUpdate, - FieldsType: "RandomVersion", - APIVersion: "v1", - }, - { - Operation: "RandomOperation", - FieldsType: "FieldsV1", - APIVersion: "v1", - }, - { - // Operation is missing - FieldsType: "FieldsV1", - APIVersion: "v1", - }, - { - Operation: metav1.ManagedFieldsOperationUpdate, - FieldsType: "FieldsV1", - // Invalid fieldManager - Manager: "field\nmanager", - APIVersion: "v1", - }, - { - Operation: metav1.ManagedFieldsOperationApply, - FieldsType: "FieldsV1", - APIVersion: "v1", - Subresource: "TooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLong", - }, - } + tests := []metav1.ManagedFieldsEntry{{ + Operation: metav1.ManagedFieldsOperationUpdate, + FieldsType: "RandomVersion", + APIVersion: "v1", + }, { + Operation: "RandomOperation", + FieldsType: "FieldsV1", + APIVersion: "v1", + }, { + // Operation is missing + FieldsType: "FieldsV1", + APIVersion: "v1", + }, { + Operation: metav1.ManagedFieldsOperationUpdate, + FieldsType: "FieldsV1", + // Invalid fieldManager + Manager: "field\nmanager", + APIVersion: "v1", + }, { + Operation: metav1.ManagedFieldsOperationApply, + FieldsType: "FieldsV1", + APIVersion: "v1", + Subresource: "TooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLong", + }} for _, test := range tests { t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) { @@ -285,30 +274,25 @@ func TestValidateManagedFieldsInvalid(t *testing.T) { } func TestValidateMangedFieldsValid(t *testing.T) { - tests := []metav1.ManagedFieldsEntry{ - { - Operation: metav1.ManagedFieldsOperationUpdate, - APIVersion: "v1", - // FieldsType is missing - }, - { - Operation: metav1.ManagedFieldsOperationUpdate, - FieldsType: "FieldsV1", - APIVersion: "v1", - }, - { - Operation: metav1.ManagedFieldsOperationApply, - FieldsType: "FieldsV1", - APIVersion: "v1", - Subresource: "scale", - }, - { - Operation: metav1.ManagedFieldsOperationApply, - FieldsType: "FieldsV1", - APIVersion: "v1", - Manager: "🍔", - }, - } + tests := []metav1.ManagedFieldsEntry{{ + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "v1", + // FieldsType is missing + }, { + Operation: metav1.ManagedFieldsOperationUpdate, + FieldsType: "FieldsV1", + APIVersion: "v1", + }, { + Operation: metav1.ManagedFieldsOperationApply, + FieldsType: "FieldsV1", + APIVersion: "v1", + Subresource: "scale", + }, { + Operation: metav1.ManagedFieldsOperationApply, + FieldsType: "FieldsV1", + APIVersion: "v1", + Manager: "🍔", + }} for _, test := range tests { t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) { @@ -325,99 +309,90 @@ func TestValidateConditions(t *testing.T) { name string conditions []metav1.Condition validateErrs func(t *testing.T, errs field.ErrorList) - }{ - { - name: "bunch-of-invalid-fields", - conditions: []metav1.Condition{{ - Type: ":invalid", - Status: "unknown", - ObservedGeneration: -1, - LastTransitionTime: metav1.Time{}, - Reason: "invalid;val", - Message: "", - }}, - validateErrs: func(t *testing.T, errs field.ErrorList) { - needle := `status.conditions[0].type: Invalid value: ":invalid": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')` - if !hasError(errs, needle) { - t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) - } - needle = `status.conditions[0].status: Unsupported value: "unknown": supported values: "False", "True", "Unknown"` - if !hasError(errs, needle) { - t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) - } - needle = `status.conditions[0].observedGeneration: Invalid value: -1: must be greater than or equal to zero` - if !hasError(errs, needle) { - t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) - } - needle = `status.conditions[0].lastTransitionTime: Required value: must be set` - if !hasError(errs, needle) { - t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) - } - needle = `status.conditions[0].reason: Invalid value: "invalid;val": a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', or 'ReasonA,ReasonB', or 'ReasonA:ReasonB', regex used for validation is '[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?')` - if !hasError(errs, needle) { - t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) - } - }, + }{{ + name: "bunch-of-invalid-fields", + conditions: []metav1.Condition{{ + Type: ":invalid", + Status: "unknown", + ObservedGeneration: -1, + LastTransitionTime: metav1.Time{}, + Reason: "invalid;val", + Message: "", + }}, + validateErrs: func(t *testing.T, errs field.ErrorList) { + needle := `status.conditions[0].type: Invalid value: ":invalid": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + needle = `status.conditions[0].status: Unsupported value: "unknown": supported values: "False", "True", "Unknown"` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + needle = `status.conditions[0].observedGeneration: Invalid value: -1: must be greater than or equal to zero` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + needle = `status.conditions[0].lastTransitionTime: Required value: must be set` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + needle = `status.conditions[0].reason: Invalid value: "invalid;val": a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', or 'ReasonA,ReasonB', or 'ReasonA:ReasonB', regex used for validation is '[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?')` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } }, - { - name: "duplicates", - conditions: []metav1.Condition{{ - Type: "First", - }, - { - Type: "Second", - }, - { - Type: "First", - }, - }, - validateErrs: func(t *testing.T, errs field.ErrorList) { - needle := `status.conditions[2].type: Duplicate value: "First"` - if !hasError(errs, needle) { - t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) - } - }, + }, { + name: "duplicates", + conditions: []metav1.Condition{{ + Type: "First", + }, { + Type: "Second", + }, { + Type: "First", + }}, + validateErrs: func(t *testing.T, errs field.ErrorList) { + needle := `status.conditions[2].type: Duplicate value: "First"` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } }, - { - name: "colon-allowed-in-reason", - conditions: []metav1.Condition{{ - Type: "First", - Reason: "valid:val", - }}, - validateErrs: func(t *testing.T, errs field.ErrorList) { - needle := `status.conditions[0].reason` - if hasPrefixError(errs, needle) { - t.Errorf("has %q in\n%v", needle, errorsAsString(errs)) - } - }, + }, { + name: "colon-allowed-in-reason", + conditions: []metav1.Condition{{ + Type: "First", + Reason: "valid:val", + }}, + validateErrs: func(t *testing.T, errs field.ErrorList) { + needle := `status.conditions[0].reason` + if hasPrefixError(errs, needle) { + t.Errorf("has %q in\n%v", needle, errorsAsString(errs)) + } }, - { - name: "comma-allowed-in-reason", - conditions: []metav1.Condition{{ - Type: "First", - Reason: "valid,val", - }}, - validateErrs: func(t *testing.T, errs field.ErrorList) { - needle := `status.conditions[0].reason` - if hasPrefixError(errs, needle) { - t.Errorf("has %q in\n%v", needle, errorsAsString(errs)) - } - }, + }, { + name: "comma-allowed-in-reason", + conditions: []metav1.Condition{{ + Type: "First", + Reason: "valid,val", + }}, + validateErrs: func(t *testing.T, errs field.ErrorList) { + needle := `status.conditions[0].reason` + if hasPrefixError(errs, needle) { + t.Errorf("has %q in\n%v", needle, errorsAsString(errs)) + } }, - { - name: "reason-does-not-end-in-delimiter", - conditions: []metav1.Condition{{ - Type: "First", - Reason: "valid,val:", - }}, - validateErrs: func(t *testing.T, errs field.ErrorList) { - needle := `status.conditions[0].reason: Invalid value: "valid,val:": a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', or 'ReasonA,ReasonB', or 'ReasonA:ReasonB', regex used for validation is '[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?')` - if !hasError(errs, needle) { - t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) - } - }, + }, { + name: "reason-does-not-end-in-delimiter", + conditions: []metav1.Condition{{ + Type: "First", + Reason: "valid,val:", + }}, + validateErrs: func(t *testing.T, errs field.ErrorList) { + needle := `status.conditions[0].reason: Invalid value: "valid,val:": a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', or 'ReasonA,ReasonB', or 'ReasonA:ReasonB', regex used for validation is '[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?')` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } }, - } + }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -433,79 +408,66 @@ func TestLabelSelectorMatchExpression(t *testing.T) { labelSelector *metav1.LabelSelector wantErrorNumber int validateErrs func(t *testing.T, errs field.ErrorList) - }{ - { - name: "Valid LabelSelector", - labelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"value"}, - }, - }, - }, - wantErrorNumber: 0, - validateErrs: nil, + }{{ + name: "Valid LabelSelector", + labelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value"}, + }}, }, - { - name: "MatchExpression's key name isn't valid", - labelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "-key", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"value"}, - }, - }, - }, - wantErrorNumber: 1, - validateErrs: func(t *testing.T, errs field.ErrorList) { - errMessage := "name part must consist of alphanumeric characters" - if !partStringInErrorMessage(errs, errMessage) { - t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) - } - }, + wantErrorNumber: 0, + validateErrs: nil, + }, { + name: "MatchExpression's key name isn't valid", + labelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "-key", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value"}, + }}, }, - { - name: "MatchExpression's operator isn't valid", - labelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key", - Operator: "abc", - Values: []string{"value"}, - }, - }, - }, - wantErrorNumber: 1, - validateErrs: func(t *testing.T, errs field.ErrorList) { - errMessage := "not a valid selector operator" - if !partStringInErrorMessage(errs, errMessage) { - t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) - } - }, + wantErrorNumber: 1, + validateErrs: func(t *testing.T, errs field.ErrorList) { + errMessage := "name part must consist of alphanumeric characters" + if !partStringInErrorMessage(errs, errMessage) { + t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) + } }, - { - name: "MatchExpression's value name isn't valid", - labelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"-value"}, - }, - }, - }, - wantErrorNumber: 1, - validateErrs: func(t *testing.T, errs field.ErrorList) { - errMessage := "a valid label must be an empty string or consist of" - if !partStringInErrorMessage(errs, errMessage) { - t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) - } - }, + }, { + name: "MatchExpression's operator isn't valid", + labelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key", + Operator: "abc", + Values: []string{"value"}, + }}, }, - } + wantErrorNumber: 1, + validateErrs: func(t *testing.T, errs field.ErrorList) { + errMessage := "not a valid selector operator" + if !partStringInErrorMessage(errs, errMessage) { + t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) + } + }, + }, { + name: "MatchExpression's value name isn't valid", + labelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"-value"}, + }}, + }, + wantErrorNumber: 1, + validateErrs: func(t *testing.T, errs field.ErrorList) { + errMessage := "a valid label must be an empty string or consist of" + if !partStringInErrorMessage(errs, errMessage) { + t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) + } + }, + }} for index, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { allErrs := ValidateLabelSelector(testCase.labelSelector, LabelSelectorValidationOptions{false}, field.NewPath("labelSelector")) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go index 142d546c514..499b13c737a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go @@ -630,33 +630,27 @@ func TestIsFullyQualifiedName(t *testing.T) { name string targetName string err string - }{ - { - name: "name needs to be fully qualified, i.e., contains at least 2 dots", - targetName: "k8s.io", - err: "should be a domain with at least three segments separated by dots", - }, - { - name: "name should not include scheme", - targetName: "http://foo.k8s.io", - err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", - }, - { - name: "email should be invalid", - targetName: "example@foo.k8s.io", - err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", - }, - { - name: "name cannot be empty", - targetName: "", - err: "Required value", - }, - { - name: "name must conform to RFC 1123", - targetName: "A.B.C", - err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", - }, - } + }{{ + name: "name needs to be fully qualified, i.e., contains at least 2 dots", + targetName: "k8s.io", + err: "should be a domain with at least three segments separated by dots", + }, { + name: "name should not include scheme", + targetName: "http://foo.k8s.io", + err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", + }, { + name: "email should be invalid", + targetName: "example@foo.k8s.io", + err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", + }, { + name: "name cannot be empty", + targetName: "", + err: "Required value", + }, { + name: "name must conform to RFC 1123", + targetName: "A.B.C", + err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", + }} for _, tc := range messageTests { err := IsFullyQualifiedName(field.NewPath(""), tc.targetName).ToAggregate() switch { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/validation/validation_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/validation/validation_test.go index 3e7c53c4d67..a163eb6314e 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/validation/validation_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/validation/validation_test.go @@ -23,23 +23,17 @@ import ( ) func TestValidateConfiguration(t *testing.T) { - successCases := []resourcequotaapi.Configuration{ - { - LimitedResources: []resourcequotaapi.LimitedResource{ - { - Resource: "pods", - MatchContains: []string{"requests.cpu"}, - }, - }, - }, - { - LimitedResources: []resourcequotaapi.LimitedResource{ - { - Resource: "persistentvolumeclaims", - MatchContains: []string{"requests.storage"}, - }, - }, - }, + successCases := []resourcequotaapi.Configuration{{ + LimitedResources: []resourcequotaapi.LimitedResource{{ + Resource: "pods", + MatchContains: []string{"requests.cpu"}, + }}, + }, { + LimitedResources: []resourcequotaapi.LimitedResource{{ + Resource: "persistentvolumeclaims", + MatchContains: []string{"requests.storage"}, + }}, + }, } for i := range successCases { configuration := successCases[i] diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation_test.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation_test.go index a25d5dfcaa6..dfda29a8f57 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation_test.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation_test.go @@ -134,11 +134,9 @@ func TestValidatePolicy(t *testing.T) { policy := audit.Policy{OmitStages: []audit.Stage{ audit.Stage("foo"), }, - Rules: []audit.PolicyRule{ - { - Level: audit.LevelMetadata, - }, - }, + Rules: []audit.PolicyRule{{ + Level: audit.LevelMetadata, + }}, } errorCases = append(errorCases, policy) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation_test.go b/staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation_test.go index d57a3f5294b..b5337cadf86 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation_test.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation_test.go @@ -37,1072 +37,887 @@ func TestStructure(t *testing.T) { in *config.EncryptionConfiguration reload bool want field.ErrorList - }{ - { - desc: "nil encryption config", - in: nil, - want: field.ErrorList{ - field.Required(root, encryptionConfigNilErr), - }, + }{{ + desc: "nil encryption config", + in: nil, + want: field.ErrorList{ + field.Required(root, encryptionConfigNilErr), }, - { - desc: "empty encryption config", - in: &config.EncryptionConfiguration{}, - want: field.ErrorList{ - field.Required(root, fmt.Sprintf(atLeastOneRequiredErrFmt, root)), - }, + }, { + desc: "empty encryption config", + in: &config.EncryptionConfiguration{}, + want: field.ErrorList{ + field.Required(root, fmt.Sprintf(atLeastOneRequiredErrFmt, root)), }, - { - desc: "no k8s resources", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Providers: []config.ProviderConfiguration{ - { - AESCBC: &config.AESConfiguration{ - Keys: []config.Key{ - { - Name: "foo", - Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", - }, - }, - }, - }, - }, + }, { + desc: "no k8s resources", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Providers: []config.ProviderConfiguration{{ + AESCBC: &config.AESConfiguration{ + Keys: []config.Key{{ + Name: "foo", + Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", + }}, + }, + }}, + }}, + }, + want: field.ErrorList{ + field.Required(firstResourcePath.Child("resources"), fmt.Sprintf(atLeastOneRequiredErrFmt, root.Index(0).Child("resources"))), + }, + }, { + desc: "no providers", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{"secrets"}, + }}, + }, + want: field.ErrorList{ + field.Required(firstResourcePath.Child("providers"), fmt.Sprintf(atLeastOneRequiredErrFmt, root.Index(0).Child("providers"))), + }, + }, { + desc: "multiple providers", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{"secrets"}, + Providers: []config.ProviderConfiguration{{ + AESGCM: &config.AESConfiguration{ + Keys: []config.Key{{ + Name: "foo", + Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", + }}, + }, + AESCBC: &config.AESConfiguration{ + Keys: []config.Key{{ + Name: "foo", + Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", + }}, + }, + }}, + }}, + }, + want: field.ErrorList{ + field.Invalid( + firstResourcePath.Child("providers").Index(0), + config.ProviderConfiguration{ + AESGCM: &config.AESConfiguration{ + Keys: []config.Key{{ + Name: "foo", + Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", + }}, + }, + AESCBC: &config.AESConfiguration{ + Keys: []config.Key{{ + Name: "foo", + Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", + }}, }, }, - }, - want: field.ErrorList{ - field.Required(firstResourcePath.Child("resources"), fmt.Sprintf(atLeastOneRequiredErrFmt, root.Index(0).Child("resources"))), - }, + moreThanOneElementErr), }, - { - desc: "no providers", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{"secrets"}, + }, { + desc: "valid config", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{"secrets"}, + Providers: []config.ProviderConfiguration{{ + AESGCM: &config.AESConfiguration{ + Keys: []config.Key{{ + Name: "foo", + Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", + }}, }, - }, - }, - want: field.ErrorList{ - field.Required(firstResourcePath.Child("providers"), fmt.Sprintf(atLeastOneRequiredErrFmt, root.Index(0).Child("providers"))), - }, + }}, + }}, }, - { - desc: "multiple providers", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{"secrets"}, - Providers: []config.ProviderConfiguration{ - { - AESGCM: &config.AESConfiguration{ - Keys: []config.Key{ - { - Name: "foo", - Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", - }, - }, - }, - AESCBC: &config.AESConfiguration{ - Keys: []config.Key{ - { - Name: "foo", - Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", - }, - }, - }, - }, - }, + want: field.ErrorList{}, + }, { + desc: "duplicate kms v2 config name with kms v1 config", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{"secrets"}, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-1.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", }, - }, - }, - want: field.ErrorList{ - field.Invalid( - firstResourcePath.Child("providers").Index(0), - config.ProviderConfiguration{ - AESGCM: &config.AESConfiguration{ - Keys: []config.Key{ - { - Name: "foo", - Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", - }, - }, - }, - AESCBC: &config.AESConfiguration{ - Keys: []config.Key{ - { - Name: "foo", - Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", - }, - }, - }, + }, { + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-2.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + APIVersion: "v2", }, - moreThanOneElementErr), - }, + }}, + }}, }, - { - desc: "valid config", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{"secrets"}, - Providers: []config.ProviderConfiguration{ - { - AESGCM: &config.AESConfiguration{ - Keys: []config.Key{ - { - Name: "foo", - Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=", - }, - }, - }, - }, - }, - }, - }, - }, - want: field.ErrorList{}, + want: field.ErrorList{ + field.Invalid(firstResourcePath.Child("providers").Index(1).Child("kms").Child("name"), + "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), }, - { - desc: "duplicate kms v2 config name with kms v1 config", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{"secrets"}, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-1.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-2.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - APIVersion: "v2", - }, - }, - }, + }, { + desc: "duplicate kms v2 config names", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{"secrets"}, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-1.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + APIVersion: "v2", }, - }, - }, - want: field.ErrorList{ - field.Invalid(firstResourcePath.Child("providers").Index(1).Child("kms").Child("name"), - "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), - }, + }, { + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-2.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + APIVersion: "v2", + }, + }}, + }}, }, - { - desc: "duplicate kms v2 config names", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{"secrets"}, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-1.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - APIVersion: "v2", - }, - }, - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-2.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - APIVersion: "v2", - }, - }, - }, - }, - }, - }, - want: field.ErrorList{ - field.Invalid(firstResourcePath.Child("providers").Index(1).Child("kms").Child("name"), - "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), - }, + want: field.ErrorList{ + field.Invalid(firstResourcePath.Child("providers").Index(1).Child("kms").Child("name"), + "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), }, - { - desc: "duplicate kms v2 config name across providers", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{"secrets"}, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-1.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - APIVersion: "v2", - }, - }, - }, + }, { + desc: "duplicate kms v2 config name across providers", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{"secrets"}, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-1.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + APIVersion: "v2", }, - { - Resources: []string{"secrets"}, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-2.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - APIVersion: "v2", - }, - }, - }, + }}, + }, { + Resources: []string{"secrets"}, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-2.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + APIVersion: "v2", }, - }, - }, - want: field.ErrorList{ - field.Invalid(root.Index(1).Child("providers").Index(0).Child("kms").Child("name"), - "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), - }, + }}, + }}, }, - { - desc: "duplicate kms config name with v1 and v2 across providers", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{"secrets"}, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-1.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, - { - Resources: []string{"secrets"}, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-2.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - APIVersion: "v2", - }, - }, - }, - }, - }, - }, - want: field.ErrorList{ - field.Invalid(root.Index(1).Child("providers").Index(0).Child("kms").Child("name"), - "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), - }, + want: field.ErrorList{ + field.Invalid(root.Index(1).Child("providers").Index(0).Child("kms").Child("name"), + "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), }, - { - desc: "duplicate kms v1 config names shouldn't error", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{"secrets"}, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-1.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-2.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, + }, { + desc: "duplicate kms config name with v1 and v2 across providers", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{"secrets"}, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-1.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", }, - }, - }, - want: field.ErrorList{}, + }}, + }, { + Resources: []string{"secrets"}, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-2.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + APIVersion: "v2", + }, + }}, + }}, }, - { - desc: "duplicate kms v1 config names should error when reload=true", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{"secrets"}, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-1.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider-2.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, - }, - }, - reload: true, - want: field.ErrorList{ - field.Invalid(root.Index(0).Child("providers").Index(1).Child("kms").Child("name"), - "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), - }, + want: field.ErrorList{ + field.Invalid(root.Index(1).Child("providers").Index(0).Child("kms").Child("name"), + "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), }, - { - desc: "config should error when events.k8s.io group is used", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "events.events.k8s.io", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, + }, { + desc: "duplicate kms v1 config names shouldn't error", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{"secrets"}, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-1.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", }, - }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources").Index(0), + }, { + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-2.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + want: field.ErrorList{}, + }, { + desc: "duplicate kms v1 config names should error when reload=true", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{"secrets"}, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-1.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }, { + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider-2.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: true, + want: field.ErrorList{ + field.Invalid(root.Index(0).Child("providers").Index(1).Child("kms").Child("name"), + "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), + }, + }, { + desc: "config should error when events.k8s.io group is used", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ "events.events.k8s.io", - eventsGroupErr, - ), - }, - }, { - desc: "config should error when events.k8s.io group is used later in the list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "secrets", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, - { - Resources: []string{ - "secret", - "events.events.k8s.io", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(1).Child("resources").Index(1), - "events.events.k8s.io", - eventsGroupErr, - ), - }, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, }, - { - desc: "config should error when *.events.k8s.io group is used", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "*.events.k8s.io", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources").Index(0), + "events.events.k8s.io", + eventsGroupErr, + ), + }, + }, { + desc: "config should error when events.k8s.io group is used later in the list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "secrets", }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources").Index(0), + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }, { + Resources: []string{ + "secret", + "events.events.k8s.io", + }, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(1).Child("resources").Index(1), + "events.events.k8s.io", + eventsGroupErr, + ), + }, + }, { + desc: "config should error when *.events.k8s.io group is used", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ "*.events.k8s.io", - eventsGroupErr, - ), - }, - }, - { - desc: "config should error when extensions group is used", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "*.extensions", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources").Index(0), + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources").Index(0), + "*.events.k8s.io", + eventsGroupErr, + ), + }, + }, { + desc: "config should error when extensions group is used", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ "*.extensions", - extensionsGroupErr, - ), - }, - }, - { - desc: "config should error when foo.extensions group is used", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "foo.extensions", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources").Index(0), + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources").Index(0), + "*.extensions", + extensionsGroupErr, + ), + }, + }, { + desc: "config should error when foo.extensions group is used", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ "foo.extensions", - extensionsGroupErr, - ), - }, - }, - { - desc: "config should error when '*' resource is used", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "*", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources").Index(0), + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources").Index(0), + "foo.extensions", + extensionsGroupErr, + ), + }, + }, { + desc: "config should error when '*' resource is used", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ "*", - starResourceErr, - ), - }, - }, - { - desc: "should error when resource name has capital letters", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "apiServerIPInfo", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources").Index(0), + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources").Index(0), + "*", + starResourceErr, + ), + }, + }, { + desc: "should error when resource name has capital letters", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ "apiServerIPInfo", - resourceNameErr, - ), - }, - }, - { - desc: "should error when resource name is apiserveripinfo", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "apiserveripinfo", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources").Index(0), + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources").Index(0), + "apiServerIPInfo", + resourceNameErr, + ), + }, + }, { + desc: "should error when resource name is apiserveripinfo", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ "apiserveripinfo", - nonRESTAPIResourceErr, - ), - }, - }, - { - desc: "should error when resource name is serviceipallocations", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "serviceipallocations", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources").Index(0), + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources").Index(0), + "apiserveripinfo", + nonRESTAPIResourceErr, + ), + }, + }, { + desc: "should error when resource name is serviceipallocations", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ "serviceipallocations", - nonRESTAPIResourceErr, - ), - }, - }, - { - desc: "should error when resource name is servicenodeportallocations", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "servicenodeportallocations", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources").Index(0), + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources").Index(0), + "serviceipallocations", + nonRESTAPIResourceErr, + ), + }, + }, { + desc: "should error when resource name is servicenodeportallocations", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ "servicenodeportallocations", - nonRESTAPIResourceErr, - ), - }, - }, - { - desc: "should not error when '*.apps' and '*.' are used within the same resource list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "*.apps", - "*.", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, }, - }, - reload: false, - want: field.ErrorList{}, - }, - { - desc: "should error when the same resource across groups is encrypted", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "*.", - "foos.*", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources").Index(0), + "servicenodeportallocations", + nonRESTAPIResourceErr, + ), + }, + }, { + desc: "should not error when '*.apps' and '*.' are used within the same resource list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "*.apps", + "*.", }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources").Index(1), + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{}, + }, { + desc: "should error when the same resource across groups is encrypted", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "*.", "foos.*", - resourceAcrossGroupErr, - ), - }, - }, - { - desc: "should error when secrets are specified twice within the same resource list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "secrets", - "secrets", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources"), - []string{ - "secrets", - "secrets", + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", }, - duplicateResourceErr, - ), - }, + }}, + }}, }, - { - desc: "should error once when secrets are specified many times within the same resource list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "secrets", - "secrets", - "secrets", - "secrets", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources").Index(1), + "foos.*", + resourceAcrossGroupErr, + ), + }, + }, { + desc: "should error when secrets are specified twice within the same resource list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "secrets", + "secrets", }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources"), - []string{ - "secrets", - "secrets", - "secrets", - "secrets", + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", }, - duplicateResourceErr, - ), - }, + }}, + }}, }, - { - desc: "should error when secrets are specified twice within the same resource list, via dot", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "secrets", - "secrets.", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources"), + []string{ + "secrets", + "secrets", }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources"), - []string{ - "secrets", - "secrets.", - }, - duplicateResourceErr, - ), - }, + duplicateResourceErr, + ), }, - { - desc: "should error when '*.apps' and '*.' and '*.*' are used within the same resource list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "*.apps", - "*.", - "*.*", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, + }, { + desc: "should error once when secrets are specified many times within the same resource list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "secrets", + "secrets", + "secrets", + "secrets", }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources"), - []string{ - "*.apps", - "*.", - "*.*", + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", }, - overlapErr, - ), - }, + }}, + }}, }, - { - desc: "should not error when deployments.apps are specified with '*.' within the same resource list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "deployments.apps", - "*.", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources"), + []string{ + "secrets", + "secrets", + "secrets", + "secrets", }, - }, - reload: false, - want: field.ErrorList{}, + duplicateResourceErr, + ), }, - { - desc: "should error when deployments.apps are specified with '*.apps' within the same resource list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "deployments.apps", - "*.apps", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, + }, { + desc: "should error when secrets are specified twice within the same resource list, via dot", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "secrets", + "secrets.", }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources"), - []string{ - "deployments.apps", - "*.apps", + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", }, - overlapErr, - ), - }, + }}, + }}, }, - { - desc: "should error when secrets are specified with '*.' within the same resource list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "secrets", - "*.", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources"), + []string{ + "secrets", + "secrets.", }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources"), - []string{ - "secrets", - "*.", - }, - overlapErr, - ), - }, + duplicateResourceErr, + ), }, - { - desc: "should error when pods are specified with '*.' within the same resource list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "pods", - "*.", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, + }, { + desc: "should error when '*.apps' and '*.' and '*.*' are used within the same resource list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "*.apps", + "*.", + "*.*", }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources"), - []string{ - "pods", - "*.", + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", }, - overlapErr, - ), - }, + }}, + }}, }, - { - desc: "should error when other resources are specified with '*.*' within the same resource list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "secrets", - "*.*", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources"), + []string{ + "*.apps", + "*.", + "*.*", }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources"), - []string{ - "secrets", - "*.*", - }, - overlapErr, - ), - }, + overlapErr, + ), }, - { - desc: "should error when both '*.' and '*.*' are used within the same resource list", - in: &config.EncryptionConfiguration{ - Resources: []config.ResourceConfiguration{ - { - Resources: []string{ - "*.", - "*.*", - }, - Providers: []config.ProviderConfiguration{ - { - KMS: &config.KMSConfiguration{ - Name: "foo", - Endpoint: "unix:///tmp/kms-provider.socket", - Timeout: &metav1.Duration{Duration: 3 * time.Second}, - CacheSize: &cacheSize, - APIVersion: "v1", - }, - }, - }, - }, + }, { + desc: "should not error when deployments.apps are specified with '*.' within the same resource list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "deployments.apps", + "*.", }, - }, - reload: false, - want: field.ErrorList{ - field.Invalid( - root.Index(0).Child("resources"), - []string{ - "*.", - "*.*", + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", }, - overlapErr, - ), - }, + }}, + }}, }, - } + reload: false, + want: field.ErrorList{}, + }, { + desc: "should error when deployments.apps are specified with '*.apps' within the same resource list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "deployments.apps", + "*.apps", + }, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources"), + []string{ + "deployments.apps", + "*.apps", + }, + overlapErr, + ), + }, + }, { + desc: "should error when secrets are specified with '*.' within the same resource list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "secrets", + "*.", + }, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources"), + []string{ + "secrets", + "*.", + }, + overlapErr, + ), + }, + }, { + desc: "should error when pods are specified with '*.' within the same resource list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "pods", + "*.", + }, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources"), + []string{ + "pods", + "*.", + }, + overlapErr, + ), + }, + }, { + desc: "should error when other resources are specified with '*.*' within the same resource list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "secrets", + "*.*", + }, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources"), + []string{ + "secrets", + "*.*", + }, + overlapErr, + ), + }, + }, { + desc: "should error when both '*.' and '*.*' are used within the same resource list", + in: &config.EncryptionConfiguration{ + Resources: []config.ResourceConfiguration{{ + Resources: []string{ + "*.", + "*.*", + }, + Providers: []config.ProviderConfiguration{{ + KMS: &config.KMSConfiguration{ + Name: "foo", + Endpoint: "unix:///tmp/kms-provider.socket", + Timeout: &metav1.Duration{Duration: 3 * time.Second}, + CacheSize: &cacheSize, + APIVersion: "v1", + }, + }}, + }}, + }, + reload: false, + want: field.ErrorList{ + field.Invalid( + root.Index(0).Child("resources"), + []string{ + "*.", + "*.*", + }, + overlapErr, + ), + }, + }} for _, tt := range testCases { t.Run(tt.desc, func(t *testing.T) { @@ -1120,41 +935,35 @@ func TestKey(t *testing.T) { desc string in config.Key want field.ErrorList - }{ - { - desc: "valid key", - in: config.Key{Name: "foo", Secret: "c2VjcmV0IGlzIHNlY3VyZQ=="}, - want: field.ErrorList{}, + }{{ + desc: "valid key", + in: config.Key{Name: "foo", Secret: "c2VjcmV0IGlzIHNlY3VyZQ=="}, + want: field.ErrorList{}, + }, { + desc: "key without name", + in: config.Key{Secret: "c2VjcmV0IGlzIHNlY3VyZQ=="}, + want: field.ErrorList{ + field.Required(path.Child("name"), fmt.Sprintf(mandatoryFieldErrFmt, "name", "key")), }, - { - desc: "key without name", - in: config.Key{Secret: "c2VjcmV0IGlzIHNlY3VyZQ=="}, - want: field.ErrorList{ - field.Required(path.Child("name"), fmt.Sprintf(mandatoryFieldErrFmt, "name", "key")), - }, + }, { + desc: "key without secret", + in: config.Key{Name: "foo"}, + want: field.ErrorList{ + field.Required(path.Child("secret"), fmt.Sprintf(mandatoryFieldErrFmt, "secret", "key")), }, - { - desc: "key without secret", - in: config.Key{Name: "foo"}, - want: field.ErrorList{ - field.Required(path.Child("secret"), fmt.Sprintf(mandatoryFieldErrFmt, "secret", "key")), - }, + }, { + desc: "key is not base64 encoded", + in: config.Key{Name: "foo", Secret: "P@ssword"}, + want: field.ErrorList{ + field.Invalid(path.Child("secret"), "REDACTED", base64EncodingErr), }, - { - desc: "key is not base64 encoded", - in: config.Key{Name: "foo", Secret: "P@ssword"}, - want: field.ErrorList{ - field.Invalid(path.Child("secret"), "REDACTED", base64EncodingErr), - }, + }, { + desc: "key is not of expected length", + in: config.Key{Name: "foo", Secret: "cGFzc3dvcmQK"}, + want: field.ErrorList{ + field.Invalid(path.Child("secret"), "REDACTED", fmt.Sprintf(keyLenErrFmt, 9, aesKeySizes)), }, - { - desc: "key is not of expected length", - in: config.Key{Name: "foo", Secret: "cGFzc3dvcmQK"}, - want: field.ErrorList{ - field.Invalid(path.Child("secret"), "REDACTED", fmt.Sprintf(keyLenErrFmt, 9, aesKeySizes)), - }, - }, - } + }} for _, tt := range testCases { t.Run(tt.desc, func(t *testing.T) { @@ -1175,27 +984,23 @@ func TestKMSProviderTimeout(t *testing.T) { desc string in *config.KMSConfiguration want field.ErrorList - }{ - { - desc: "valid timeout", - in: &config.KMSConfiguration{Timeout: &metav1.Duration{Duration: 1 * time.Minute}}, - want: field.ErrorList{}, + }{{ + desc: "valid timeout", + in: &config.KMSConfiguration{Timeout: &metav1.Duration{Duration: 1 * time.Minute}}, + want: field.ErrorList{}, + }, { + desc: "negative timeout", + in: &config.KMSConfiguration{Timeout: negativeTimeout}, + want: field.ErrorList{ + field.Invalid(timeoutField, negativeTimeout, fmt.Sprintf(zeroOrNegativeErrFmt, "timeout")), }, - { - desc: "negative timeout", - in: &config.KMSConfiguration{Timeout: negativeTimeout}, - want: field.ErrorList{ - field.Invalid(timeoutField, negativeTimeout, fmt.Sprintf(zeroOrNegativeErrFmt, "timeout")), - }, + }, { + desc: "zero timeout", + in: &config.KMSConfiguration{Timeout: zeroTimeout}, + want: field.ErrorList{ + field.Invalid(timeoutField, zeroTimeout, fmt.Sprintf(zeroOrNegativeErrFmt, "timeout")), }, - { - desc: "zero timeout", - in: &config.KMSConfiguration{Timeout: zeroTimeout}, - want: field.ErrorList{ - field.Invalid(timeoutField, zeroTimeout, fmt.Sprintf(zeroOrNegativeErrFmt, "timeout")), - }, - }, - } + }} for _, tt := range testCases { t.Run(tt.desc, func(t *testing.T) { @@ -1213,34 +1018,29 @@ func TestKMSEndpoint(t *testing.T) { desc string in *config.KMSConfiguration want field.ErrorList - }{ - { - desc: "valid endpoint", - in: &config.KMSConfiguration{Endpoint: "unix:///socket.sock"}, - want: field.ErrorList{}, + }{{ + desc: "valid endpoint", + in: &config.KMSConfiguration{Endpoint: "unix:///socket.sock"}, + want: field.ErrorList{}, + }, { + desc: "empty endpoint", + in: &config.KMSConfiguration{}, + want: field.ErrorList{ + field.Invalid(endpointField, "", fmt.Sprintf(mandatoryFieldErrFmt, "endpoint", "kms")), }, - { - desc: "empty endpoint", - in: &config.KMSConfiguration{}, - want: field.ErrorList{ - field.Invalid(endpointField, "", fmt.Sprintf(mandatoryFieldErrFmt, "endpoint", "kms")), - }, + }, { + desc: "non unix endpoint", + in: &config.KMSConfiguration{Endpoint: "https://www.foo.com"}, + want: field.ErrorList{ + field.Invalid(endpointField, "https://www.foo.com", fmt.Sprintf(unsupportedSchemeErrFmt, "https")), }, - { - desc: "non unix endpoint", - in: &config.KMSConfiguration{Endpoint: "https://www.foo.com"}, - want: field.ErrorList{ - field.Invalid(endpointField, "https://www.foo.com", fmt.Sprintf(unsupportedSchemeErrFmt, "https")), - }, + }, { + desc: "invalid url", + in: &config.KMSConfiguration{Endpoint: "unix:///foo\n.socket"}, + want: field.ErrorList{ + field.Invalid(endpointField, "unix:///foo\n.socket", fmt.Sprintf(invalidURLErrFmt, `parse "unix:///foo\n.socket": net/url: invalid control character in URL`)), }, - { - desc: "invalid url", - in: &config.KMSConfiguration{Endpoint: "unix:///foo\n.socket"}, - want: field.ErrorList{ - field.Invalid(endpointField, "unix:///foo\n.socket", fmt.Sprintf(invalidURLErrFmt, `parse "unix:///foo\n.socket": net/url: invalid control character in URL`)), - }, - }, - } + }} for _, tt := range testCases { t.Run(tt.desc, func(t *testing.T) { @@ -1262,32 +1062,27 @@ func TestKMSProviderCacheSize(t *testing.T) { desc string in *config.KMSConfiguration want field.ErrorList - }{ - { - desc: "valid positive cache size", - in: &config.KMSConfiguration{APIVersion: "v1", CacheSize: &positiveCacheSize}, - want: field.ErrorList{}, + }{{ + desc: "valid positive cache size", + in: &config.KMSConfiguration{APIVersion: "v1", CacheSize: &positiveCacheSize}, + want: field.ErrorList{}, + }, { + desc: "invalid zero cache size", + in: &config.KMSConfiguration{APIVersion: "v1", CacheSize: &zeroCacheSize}, + want: field.ErrorList{ + field.Invalid(cacheField, int32(0), fmt.Sprintf(nonZeroErrFmt, "cachesize")), }, - { - desc: "invalid zero cache size", - in: &config.KMSConfiguration{APIVersion: "v1", CacheSize: &zeroCacheSize}, - want: field.ErrorList{ - field.Invalid(cacheField, int32(0), fmt.Sprintf(nonZeroErrFmt, "cachesize")), - }, + }, { + desc: "valid negative caches size", + in: &config.KMSConfiguration{APIVersion: "v1", CacheSize: &negativeCacheSize}, + want: field.ErrorList{}, + }, { + desc: "cache size set with v2 provider", + in: &config.KMSConfiguration{CacheSize: &positiveCacheSize, APIVersion: "v2"}, + want: field.ErrorList{ + field.Invalid(cacheField, positiveCacheSize, "cachesize is not supported in v2"), }, - { - desc: "valid negative caches size", - in: &config.KMSConfiguration{APIVersion: "v1", CacheSize: &negativeCacheSize}, - want: field.ErrorList{}, - }, - { - desc: "cache size set with v2 provider", - in: &config.KMSConfiguration{CacheSize: &positiveCacheSize, APIVersion: "v2"}, - want: field.ErrorList{ - field.Invalid(cacheField, positiveCacheSize, "cachesize is not supported in v2"), - }, - }, - } + }} for _, tt := range testCases { t.Run(tt.desc, func(t *testing.T) { @@ -1306,25 +1101,21 @@ func TestKMSProviderAPIVersion(t *testing.T) { desc string in *config.KMSConfiguration want field.ErrorList - }{ - { - desc: "valid v1 api version", - in: &config.KMSConfiguration{APIVersion: "v1"}, - want: field.ErrorList{}, + }{{ + desc: "valid v1 api version", + in: &config.KMSConfiguration{APIVersion: "v1"}, + want: field.ErrorList{}, + }, { + desc: "valid v2 api version", + in: &config.KMSConfiguration{APIVersion: "v2"}, + want: field.ErrorList{}, + }, { + desc: "invalid api version", + in: &config.KMSConfiguration{APIVersion: "v3"}, + want: field.ErrorList{ + field.Invalid(apiVersionField, "v3", fmt.Sprintf(unsupportedKMSAPIVersionErrFmt, "apiVersion")), }, - { - desc: "valid v2 api version", - in: &config.KMSConfiguration{APIVersion: "v2"}, - want: field.ErrorList{}, - }, - { - desc: "invalid api version", - in: &config.KMSConfiguration{APIVersion: "v3"}, - want: field.ErrorList{ - field.Invalid(apiVersionField, "v3", fmt.Sprintf(unsupportedKMSAPIVersionErrFmt, "apiVersion")), - }, - }, - } + }} for _, tt := range testCases { t.Run(tt.desc, func(t *testing.T) { @@ -1345,64 +1136,55 @@ func TestKMSProviderName(t *testing.T) { reload bool kmsProviderNames sets.String want field.ErrorList - }{ - { - desc: "valid name", - in: &config.KMSConfiguration{Name: "foo"}, - want: field.ErrorList{}, + }{{ + desc: "valid name", + in: &config.KMSConfiguration{Name: "foo"}, + want: field.ErrorList{}, + }, { + desc: "empty name", + in: &config.KMSConfiguration{}, + want: field.ErrorList{ + field.Required(nameField, fmt.Sprintf(mandatoryFieldErrFmt, "name", "provider")), }, - { - desc: "empty name", - in: &config.KMSConfiguration{}, - want: field.ErrorList{ - field.Required(nameField, fmt.Sprintf(mandatoryFieldErrFmt, "name", "provider")), - }, + }, { + desc: "invalid name with :", + in: &config.KMSConfiguration{Name: "foo:bar"}, + want: field.ErrorList{ + field.Invalid(nameField, "foo:bar", fmt.Sprintf(invalidKMSConfigNameErrFmt, "foo:bar")), }, - { - desc: "invalid name with :", - in: &config.KMSConfiguration{Name: "foo:bar"}, - want: field.ErrorList{ - field.Invalid(nameField, "foo:bar", fmt.Sprintf(invalidKMSConfigNameErrFmt, "foo:bar")), - }, + }, { + desc: "invalid name with : but api version is v1", + in: &config.KMSConfiguration{Name: "foo:bar", APIVersion: "v1"}, + want: field.ErrorList{}, + }, { + desc: "duplicate name, kms v2, reload=false", + in: &config.KMSConfiguration{APIVersion: "v2", Name: "foo"}, + kmsProviderNames: sets.NewString("foo"), + want: field.ErrorList{ + field.Invalid(nameField, "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), }, - { - desc: "invalid name with : but api version is v1", - in: &config.KMSConfiguration{Name: "foo:bar", APIVersion: "v1"}, - want: field.ErrorList{}, + }, { + desc: "duplicate name, kms v2, reload=true", + in: &config.KMSConfiguration{APIVersion: "v2", Name: "foo"}, + reload: true, + kmsProviderNames: sets.NewString("foo"), + want: field.ErrorList{ + field.Invalid(nameField, "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), }, - { - desc: "duplicate name, kms v2, reload=false", - in: &config.KMSConfiguration{APIVersion: "v2", Name: "foo"}, - kmsProviderNames: sets.NewString("foo"), - want: field.ErrorList{ - field.Invalid(nameField, "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), - }, + }, { + desc: "duplicate name, kms v1, reload=false", + in: &config.KMSConfiguration{APIVersion: "v1", Name: "foo"}, + kmsProviderNames: sets.NewString("foo"), + want: field.ErrorList{}, + }, { + desc: "duplicate name, kms v1, reload=true", + in: &config.KMSConfiguration{APIVersion: "v1", Name: "foo"}, + reload: true, + kmsProviderNames: sets.NewString("foo"), + want: field.ErrorList{ + field.Invalid(nameField, "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), }, - { - desc: "duplicate name, kms v2, reload=true", - in: &config.KMSConfiguration{APIVersion: "v2", Name: "foo"}, - reload: true, - kmsProviderNames: sets.NewString("foo"), - want: field.ErrorList{ - field.Invalid(nameField, "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), - }, - }, - { - desc: "duplicate name, kms v1, reload=false", - in: &config.KMSConfiguration{APIVersion: "v1", Name: "foo"}, - kmsProviderNames: sets.NewString("foo"), - want: field.ErrorList{}, - }, - { - desc: "duplicate name, kms v1, reload=true", - in: &config.KMSConfiguration{APIVersion: "v1", Name: "foo"}, - reload: true, - kmsProviderNames: sets.NewString("foo"), - want: field.ErrorList{ - field.Invalid(nameField, "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")), - }, - }, - } + }} for _, tt := range testCases { t.Run(tt.desc, func(t *testing.T) { diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/validation_test.go b/staging/src/k8s.io/client-go/tools/clientcmd/validation_test.go index 7b8e2fd5c6c..3c56498a44f 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/validation_test.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/validation_test.go @@ -748,37 +748,31 @@ func TestErrConfigurationInvalidWithErrorsIs(t *testing.T) { err error matchAgainst error expectMatch bool - }{ - { - name: "no match", - err: errConfigurationInvalid{errors.New("my-error"), errors.New("my-other-error")}, - matchAgainst: fmt.Errorf("no entry %s", "here"), - }, - { - name: "match via .Is()", - err: errConfigurationInvalid{errors.New("forbidden"), alwaysMatchingError{}}, - matchAgainst: errors.New("unauthorized"), - expectMatch: true, - }, - { - name: "match via equality", - err: errConfigurationInvalid{errors.New("err"), someError{}}, - matchAgainst: someError{}, - expectMatch: true, - }, - { - name: "match via nested aggregate", - err: errConfigurationInvalid{errors.New("closed today"), errConfigurationInvalid{errConfigurationInvalid{someError{}}}}, - matchAgainst: someError{}, - expectMatch: true, - }, - { - name: "match via wrapped aggregate", - err: fmt.Errorf("wrap: %w", errConfigurationInvalid{errors.New("err"), someError{}}), - matchAgainst: someError{}, - expectMatch: true, - }, - } + }{{ + name: "no match", + err: errConfigurationInvalid{errors.New("my-error"), errors.New("my-other-error")}, + matchAgainst: fmt.Errorf("no entry %s", "here"), + }, { + name: "match via .Is()", + err: errConfigurationInvalid{errors.New("forbidden"), alwaysMatchingError{}}, + matchAgainst: errors.New("unauthorized"), + expectMatch: true, + }, { + name: "match via equality", + err: errConfigurationInvalid{errors.New("err"), someError{}}, + matchAgainst: someError{}, + expectMatch: true, + }, { + name: "match via nested aggregate", + err: errConfigurationInvalid{errors.New("closed today"), errConfigurationInvalid{errConfigurationInvalid{someError{}}}}, + matchAgainst: someError{}, + expectMatch: true, + }, { + name: "match via wrapped aggregate", + err: fmt.Errorf("wrap: %w", errConfigurationInvalid{errors.New("err"), someError{}}), + matchAgainst: someError{}, + expectMatch: true, + }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) {