PodSecurity: test: framework

This commit is contained in:
Jordan Liggitt 2021-06-22 14:08:30 -04:00
parent 1436d35779
commit 29f5ebf1fe
6 changed files with 734 additions and 0 deletions

View File

@ -0,0 +1,18 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package test contains tests for PodSecurity admission
package test // import "k8s.io/pod-security-admission/test"

View File

@ -0,0 +1,193 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package test
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/pod-security-admission/api"
"k8s.io/pod-security-admission/policy"
"k8s.io/utils/pointer"
)
// minimalValidPods holds minimal valid pods per-level per-version.
// To get a valid pod for a particular level/version, use getMinimalValidPod().
var minimalValidPods = map[api.Level]map[api.Version]*corev1.Pod{}
func init() {
minimalValidPods[api.LevelBaseline] = map[api.Version]*corev1.Pod{}
minimalValidPods[api.LevelRestricted] = map[api.Version]*corev1.Pod{}
// Define minimal valid baseline pod.
// This must remain valid for all versions.
baseline_1_0 := &corev1.Pod{Spec: corev1.PodSpec{
InitContainers: []corev1.Container{{Name: "initcontainer1", Image: "k8s.gcr.io/pause"}},
Containers: []corev1.Container{{Name: "container1", Image: "k8s.gcr.io/pause"}}}}
minimalValidPods[api.LevelBaseline][api.MajorMinorVersion(1, 0)] = baseline_1_0
//
// Define minimal valid restricted pods.
//
// 1.0+: baseline + runAsNonRoot=true
restricted_1_0 := tweak(baseline_1_0, func(p *corev1.Pod) {
p.Spec.SecurityContext = &corev1.PodSecurityContext{RunAsNonRoot: pointer.BoolPtr(true)}
})
minimalValidPods[api.LevelRestricted][api.MajorMinorVersion(1, 0)] = restricted_1_0
// 1.8+: runAsNonRoot=true
restricted_1_8 := tweak(restricted_1_0, func(p *corev1.Pod) {
p.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{AllowPrivilegeEscalation: pointer.BoolPtr(false)}
p.Spec.InitContainers[0].SecurityContext = &corev1.SecurityContext{AllowPrivilegeEscalation: pointer.BoolPtr(false)}
})
minimalValidPods[api.LevelRestricted][api.MajorMinorVersion(1, 8)] = restricted_1_8
}
// getValidPod returns a minimal valid pod for the specified level and version.
func getMinimalValidPod(level api.Level, version api.Version) (*corev1.Pod, error) {
originalVersion := version
for {
pod, exists := minimalValidPods[level][version]
if exists {
return pod.DeepCopy(), nil
}
if version.Minor() <= 0 {
return nil, fmt.Errorf("no valid pod fixture found in specified or older versions for %s/%s", level, originalVersion.String())
}
version = api.MajorMinorVersion(version.Major(), version.Minor()-1)
}
}
// fixtureGenerators holds fixture generators per-level per-version.
// To add generators, use registerFixtureGenerator().
// To get fixtures for a particular level/version, use getFixtures().
var fixtureGenerators = map[fixtureKey]fixtureGenerator{}
// fixtureKey is a tuple of version/level/check name
type fixtureKey struct {
version api.Version
level api.Level
check string
}
// fixtureGenerator holds generators for valid and invalid fixtures.
type fixtureGenerator struct {
// expectErrorSubstring is a substring to expect in the error message for failed pods.
// if empty, the check ID is used.
expectErrorSubstring string
// generatePass transforms a minimum valid pod into one or more valid pods.
// pods do not need to populate metadata.name.
generatePass func(*corev1.Pod) []*corev1.Pod
// generateFail transforms a minimum valid pod into one or more invalid pods.
// pods do not need to populate metadata.name.
generateFail func(*corev1.Pod) []*corev1.Pod
}
// fixtureData holds valid and invalid pod fixtures.
type fixtureData struct {
expectErrorSubstring string
pass []*corev1.Pod
fail []*corev1.Pod
}
// registerFixtureGenerator adds a generator for the given level/version/check.
// A generator registered for v1.x is used for v1.x+ if no generator is registered for the higher version.
func registerFixtureGenerator(key fixtureKey, generator fixtureGenerator) {
if err := checkKey(key); err != nil {
panic(err)
}
if _, exists := fixtureGenerators[key]; exists {
panic(fmt.Errorf("fixture generator already registered for key %#v", key))
}
if generator.generatePass == nil || generator.generateFail == nil {
panic(fmt.Errorf("adding %#v: must specify generatePass/generateFail", key))
}
fixtureGenerators[key] = generator
if key.level == api.LevelBaseline {
// also register to restricted
restrictedKey := key
restrictedKey.level = api.LevelRestricted
if _, exists := fixtureGenerators[restrictedKey]; exists {
panic(fmt.Errorf("fixture generator already registered for restricted version of key %#v", key))
}
fixtureGenerators[restrictedKey] = generator
}
}
// getFixtures returns the fixture data for the specified level/version/check.
// Fixtures are generated by applying the registered generator to the minimal valid pod for that level/version.
// If no fixture generator exists for the given version, previous generators are checked back to 1.0.
func getFixtures(key fixtureKey) (fixtureData, error) {
if err := checkKey(key); err != nil {
return fixtureData{}, err
}
validPodForLevel, err := getMinimalValidPod(key.level, key.version)
if err != nil {
return fixtureData{}, err
}
for {
if generator, exists := fixtureGenerators[key]; exists {
data := fixtureData{
expectErrorSubstring: generator.expectErrorSubstring,
pass: generator.generatePass(validPodForLevel.DeepCopy()),
fail: generator.generateFail(validPodForLevel.DeepCopy()),
}
if len(data.expectErrorSubstring) == 0 {
data.expectErrorSubstring = key.check
}
if len(data.pass) == 0 || len(data.fail) == 0 {
return fixtureData{}, fmt.Errorf("generatePass/generateFail for %#v must return at least one pod each", key)
}
return data, nil
}
if key.version.Minor() == 0 {
return fixtureData{}, fmt.Errorf("no fixture generator found in specified or older versions for %#v", key)
}
// check the next older version
key.version = api.MajorMinorVersion(key.version.Major(), key.version.Minor()-1)
}
}
// checkKey ensures the fixture key has a valid level, version, and check.
func checkKey(key fixtureKey) error {
if key.level != api.LevelBaseline && key.level != api.LevelRestricted {
return fmt.Errorf("invalid key, level must be baseline or restricted: %#v", key)
}
if key.version.Latest() || key.version.Major() != 1 || key.version.Minor() < 0 {
return fmt.Errorf("invalid key, version must be 1.0+: %#v", key)
}
if key.check == "" {
return fmt.Errorf("invalid key, check must not be empty")
}
found := false
for _, check := range policy.DefaultChecks() {
if check.ID == key.check {
found = true
break
}
}
if !found {
return fmt.Errorf("invalid key %#v, check does not exist at version", key)
}
return nil
}

View File

@ -0,0 +1,155 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package test
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/pod-security-admission/api"
"k8s.io/pod-security-admission/policy"
"sigs.k8s.io/yaml"
)
const updateEnvVar = "UPDATE_POD_SECURITY_FIXTURE_DATA"
// TestFixtures ensures fixtures are registered for every check,
// and that in-memory fixtures match serialized fixtures in testdata.
// When adding new versions or checks, serialized fixtures can be updated by running:
//
// UPDATE_POD_SECURITY_FIXTURE_DATA=true go test k8s.io/pod-security-admission/test
func TestFixtures(t *testing.T) {
expectedFiles := sets.NewString("testdata/README.md")
defaultChecks := policy.DefaultChecks()
for _, level := range []api.Level{api.LevelBaseline, api.LevelRestricted} {
// TODO: derive from registered levels
for version := 0; version <= 22; version++ {
passDir := filepath.Join("testdata", string(level), fmt.Sprintf("v1.%d", version), "pass")
failDir := filepath.Join("testdata", string(level), fmt.Sprintf("v1.%d", version), "fail")
// render the minimal valid pod fixture
validPod, err := getMinimalValidPod(level, api.MajorMinorVersion(1, version))
if err != nil {
t.Fatal(err)
}
expectedFiles.Insert(testFixtureFile(t, passDir, "base", validPod))
// render check-specific fixtures
checkIDs, err := checksForLevelAndVersion(defaultChecks, level, api.MajorMinorVersion(1, version))
if err != nil {
t.Fatal(err)
}
if len(checkIDs) == 0 {
t.Fatal(fmt.Errorf("no checks registered for %s/1.%d", level, version))
}
for _, checkID := range checkIDs {
checkData, err := getFixtures(fixtureKey{level: level, version: api.MajorMinorVersion(1, version), check: checkID})
if err != nil {
t.Fatal(err)
}
for i, pod := range checkData.pass {
expectedFiles.Insert(testFixtureFile(t, passDir, fmt.Sprintf("%s%d", strings.ToLower(checkID), i), pod))
}
for i, pod := range checkData.fail {
expectedFiles.Insert(testFixtureFile(t, failDir, fmt.Sprintf("%s%d", strings.ToLower(checkID), i), pod))
}
}
}
}
actualFileList := []string{}
err := filepath.Walk("testdata", func(path string, f os.FileInfo, err error) error {
if !f.IsDir() {
actualFileList = append(actualFileList, path)
}
return nil
})
if err != nil {
t.Fatal(err)
}
actualFiles := sets.NewString(actualFileList...)
if missingFiles := expectedFiles.Difference(actualFiles); len(missingFiles) > 0 {
t.Errorf("unexpected missing fixtures:\n%s", strings.Join(missingFiles.List(), "\n"))
}
if extraFiles := actualFiles.Difference(expectedFiles); len(extraFiles) > 0 {
t.Errorf("unexpected extra fixtures:\n%s", strings.Join(extraFiles.List(), "\n"))
if os.Getenv(updateEnvVar) == "true" {
for extra := range extraFiles {
os.Remove(extra)
}
t.Logf("Removed extra fixture files")
t.Logf("Verify the diff, commit changes, and rerun the tests")
} else {
t.Logf("If the files are expected to be removed, re-run with %s=true to drop extra fixture files", updateEnvVar)
}
}
}
func testFixtureFile(t *testing.T, dir, name string, pod *corev1.Pod) string {
filename := filepath.Join(dir, name+".yaml")
pod = pod.DeepCopy()
pod.Name = name
expectedYAML, _ := ioutil.ReadFile(filename)
jsonData, err := runtime.Encode(scheme.Codecs.LegacyCodec(corev1.SchemeGroupVersion), pod)
if err != nil {
t.Fatal(err)
}
yamlData, err := yaml.JSONToYAML(jsonData)
if err != nil {
t.Fatal(err)
}
// clean up noise in fixtures
yamlData = []byte(strings.ReplaceAll(string(yamlData), " creationTimestamp: null\n", ""))
yamlData = []byte(strings.ReplaceAll(string(yamlData), " resources: {}\n", ""))
yamlData = []byte(strings.ReplaceAll(string(yamlData), "status: {}\n", ""))
if string(yamlData) != string(expectedYAML) {
t.Errorf("fixture data does not match the test fixture in %s", filename)
if os.Getenv(updateEnvVar) == "true" {
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filename, []byte(yamlData), os.FileMode(0755)); err == nil {
t.Logf("Updated data in %s", filename)
t.Logf("Verify the diff, commit changes, and rerun the tests")
} else {
t.Logf("Could not update data in %s: %v", filename, err)
}
} else {
t.Logf("Diff between generated data and fixture data in %s:\n-------------\n%s", filename, diff.StringDiff(string(yamlData), string(expectedYAML)))
t.Logf("If the change is expected, re-run with %s=true to update the fixtures", updateEnvVar)
}
}
return filename
}

View File

@ -0,0 +1,67 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package test
import (
corev1 "k8s.io/api/core/v1"
)
// tweak makes a copy of in, passes it to f(), and returns the result.
// the input is not modified.
func tweak(in *corev1.Pod, f func(copy *corev1.Pod)) *corev1.Pod {
out := in.DeepCopy()
f(out)
return out
}
// ensureSecurityContext ensures the pod and all initContainers and containers have a non-nil security context.
func ensureSecurityContext(p *corev1.Pod) *corev1.Pod {
p = p.DeepCopy()
if p.Spec.SecurityContext == nil {
p.Spec.SecurityContext = &corev1.PodSecurityContext{}
}
for i := range p.Spec.Containers {
if p.Spec.Containers[i].SecurityContext == nil {
p.Spec.Containers[i].SecurityContext = &corev1.SecurityContext{}
}
}
for i := range p.Spec.InitContainers {
if p.Spec.InitContainers[i].SecurityContext == nil {
p.Spec.InitContainers[i].SecurityContext = &corev1.SecurityContext{}
}
}
return p
}
// ensureSELinuxOptions ensures the pod and all initContainers and containers have a non-nil seLinuxOptions.
func ensureSELinuxOptions(p *corev1.Pod) *corev1.Pod {
p = ensureSecurityContext(p)
if p.Spec.SecurityContext.SELinuxOptions == nil {
p.Spec.SecurityContext.SELinuxOptions = &corev1.SELinuxOptions{}
}
for i := range p.Spec.Containers {
if p.Spec.Containers[i].SecurityContext.SELinuxOptions == nil {
p.Spec.Containers[i].SecurityContext.SELinuxOptions = &corev1.SELinuxOptions{}
}
}
for i := range p.Spec.InitContainers {
if p.Spec.InitContainers[i].SecurityContext.SELinuxOptions == nil {
p.Spec.InitContainers[i].SecurityContext.SELinuxOptions = &corev1.SELinuxOptions{}
}
}
return p
}

View File

@ -0,0 +1,300 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package test
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"testing"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/pod-security-admission/api"
"k8s.io/pod-security-admission/policy"
)
// Options hold configuration for running integration tests against an existing server.
type Options struct {
// ClientConfig is a client configuration with sufficient permission to create, update, and delete
// namespaces, pods, and pod-template-containing objects.
// Required.
ClientConfig *rest.Config
// CreateNamespace is an optional stub for creating a namespace with the given name and labels.
// Returning an error fails the test.
// If nil, DefaultCreateNamespace is used.
CreateNamespace func(client kubernetes.Interface, name string, labels map[string]string) (*corev1.Namespace, error)
// These are the check ids/starting versions to exercise.
// If unset, policy.DefaultChecks() are used.
Checks []policy.Check
// ExemptClient is an optional client interface to exercise behavior of an exempt client.
ExemptClient kubernetes.Interface
// ExemptNamespaces are optional namespaces not expected to have PodSecurity controls enforced.
ExemptNamespaces []string
// ExemptRuntimeClasses are optional runtimeclasses not expected to have PodSecurity controls enforced.
ExemptRuntimeClasses []string
}
func toJSON(pod *corev1.Pod) string {
data, _ := json.Marshal(pod)
return string(data)
}
// checksForLevelAndVersion returns the set of check IDs that apply when evaluating the given level and version.
// checks are assumed to be well-formed and valid to pass to policy.NewEvaluator().
// level must be api.LevelRestricted or api.LevelBaseline
func checksForLevelAndVersion(checks []policy.Check, level api.Level, version api.Version) ([]string, error) {
retval := []string{}
for _, check := range checks {
if !version.Older(check.Versions[0].MinimumVersion) && (level == check.Level || level == api.LevelRestricted) {
retval = append(retval, check.ID)
}
}
return retval, nil
}
// maxMinorVersionToTest returns the maximum minor version to exercise for a given set of checks.
// checks are assumed to be well-formed and valid to pass to policy.NewEvaluator().
func maxMinorVersionToTest(checks []policy.Check) (int, error) {
// start with the release under development (1.22 at time of writing).
// this can be incremented to the current version whenever is convenient.
maxTestMinor := 22
for _, check := range checks {
lastCheckVersion := check.Versions[len(check.Versions)-1].MinimumVersion
if lastCheckVersion.Major() != 1 {
return 0, fmt.Errorf("expected major version 1, got %d", lastCheckVersion.Major())
}
if lastCheckVersion.Minor() > maxTestMinor {
maxTestMinor = lastCheckVersion.Minor()
}
}
return maxTestMinor, nil
}
type testWarningHandler struct {
lock sync.Mutex
warnings []string
}
func (t *testWarningHandler) HandleWarningHeader(code int, agent string, warning string) {
t.lock.Lock()
defer t.lock.Unlock()
t.warnings = append(t.warnings, warning)
}
func (t *testWarningHandler) FlushWarnings() []string {
t.lock.Lock()
defer t.lock.Unlock()
warnings := t.warnings
t.warnings = nil
return warnings
}
// and ensures pod fixtures expected to pass and fail against that level/version work as expected.
func Run(t *testing.T, opts Options) {
warningHandler := &testWarningHandler{}
configCopy := rest.CopyConfig(opts.ClientConfig)
configCopy.WarningHandler = warningHandler
client, err := kubernetes.NewForConfig(configCopy)
if err != nil {
t.Fatalf("error creating client: %v", err)
}
if opts.CreateNamespace == nil {
opts.CreateNamespace = DefaultCreateNamespace
}
if len(opts.Checks) == 0 {
opts.Checks = policy.DefaultChecks()
}
_, err = policy.NewEvaluator(opts.Checks)
if err != nil {
t.Fatalf("invalid checks: %v", err)
}
maxMinor, err := maxMinorVersionToTest(opts.Checks)
if err != nil {
t.Fatalf("invalid checks: %v", err)
}
for _, level := range []api.Level{api.LevelBaseline, api.LevelRestricted} {
for minor := 0; minor <= maxMinor; minor++ {
version := api.MajorMinorVersion(1, minor)
// create test name
ns := fmt.Sprintf("podsecurity-%s-1-%d", level, minor)
// create namespace
_, err := opts.CreateNamespace(client, ns, map[string]string{
api.EnforceLevelLabel: string(level),
api.EnforceVersionLabel: fmt.Sprintf("v1.%d", minor),
api.WarnLevelLabel: string(level),
api.WarnVersionLabel: fmt.Sprintf("v1.%d", minor),
})
if err != nil {
t.Errorf("failed creating namespace %s: %v", ns, err)
continue
}
t.Cleanup(func() {
client.CoreV1().Namespaces().Delete(context.Background(), ns, metav1.DeleteOptions{})
})
// create service account (to allow pod to pass serviceaccount admission)
sa, err := client.CoreV1().ServiceAccounts(ns).Create(
context.Background(),
&corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "default"}},
metav1.CreateOptions{},
)
if err != nil && !apierrors.IsAlreadyExists(err) {
t.Errorf("failed creating serviceaccount %s: %v", ns, err)
continue
}
t.Cleanup(func() {
client.CoreV1().ServiceAccounts(ns).Delete(context.Background(), sa.Name, metav1.DeleteOptions{})
})
// create pod
createPod := func(t *testing.T, i int, pod *corev1.Pod, expectSuccess bool, expectErrorSubstring string) {
t.Helper()
// avoid mutating original pod fixture
pod = pod.DeepCopy()
// assign pod name and serviceaccount
pod.Name = "test"
pod.Spec.ServiceAccountName = "default"
// dry-run create
_, err := client.CoreV1().Pods(ns).Create(context.Background(), pod, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}})
if !expectSuccess {
if err == nil {
t.Errorf("%d: expected error creating %s, got none", i, toJSON(pod))
return
}
if strings.Contains(err.Error(), policy.UnknownForbiddenReason) {
t.Errorf("%d: unexpected unknown forbidden reason creating %s: %v", i, toJSON(pod), err)
return
}
if !strings.Contains(err.Error(), expectErrorSubstring) {
t.Errorf("%d: expected error with substring %q, got %v", i, expectErrorSubstring, err)
return
}
}
if expectSuccess && err != nil {
t.Errorf("%d: unexpected error creating %s: %v", i, toJSON(pod), err)
}
}
// create controller
createController := func(t *testing.T, i int, pod *corev1.Pod, expectSuccess bool, expectErrorSubstring string) {
t.Helper()
// avoid mutating original pod fixture
pod = pod.DeepCopy()
if pod.Labels == nil {
pod.Labels = map[string]string{}
}
pod.Labels["test"] = "true"
warningHandler.FlushWarnings()
// dry-run create
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"test": "true"}},
Template: corev1.PodTemplateSpec{
ObjectMeta: pod.ObjectMeta,
Spec: pod.Spec,
},
},
}
_, err := client.AppsV1().Deployments(ns).Create(context.Background(), deployment, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}})
if err != nil {
t.Errorf("%d: unexpected error creating controller with %s: %v", i, toJSON(pod), err)
return
}
warningText := strings.Join(warningHandler.FlushWarnings(), "; ")
if !expectSuccess {
if len(warningText) == 0 {
t.Errorf("%d: expected warnings creating %s, got none", i, toJSON(pod))
return
}
if strings.Contains(warningText, policy.UnknownForbiddenReason) {
t.Errorf("%d: unexpected unknown forbidden reason creating %s: %v", i, toJSON(pod), warningText)
return
}
if !strings.Contains(warningText, expectErrorSubstring) {
t.Errorf("%d: expected warning with substring %q, got %v", i, expectErrorSubstring, warningText)
return
}
}
if expectSuccess && len(warningText) > 0 {
t.Errorf("%d: unexpected warning creating %s: %v", i, toJSON(pod), warningText)
}
}
minimalValidPod, err := getMinimalValidPod(level, version)
if err != nil {
t.Fatal(err)
}
t.Run(ns+"_pass_base", func(t *testing.T) {
createPod(t, 0, minimalValidPod.DeepCopy(), true, "")
createController(t, 0, minimalValidPod.DeepCopy(), true, "")
})
checkIDs, err := checksForLevelAndVersion(opts.Checks, level, version)
if err != nil {
t.Fatal(err)
}
if len(checkIDs) == 0 {
t.Fatal(fmt.Errorf("no checks registered for %s/1.%d", level, minor))
}
for _, checkID := range checkIDs {
checkData, err := getFixtures(fixtureKey{level: level, version: version, check: checkID})
if err != nil {
t.Fatal(err)
}
t.Run(ns+"_pass_"+checkID, func(t *testing.T) {
for i, pod := range checkData.pass {
createPod(t, i, pod, true, "")
createController(t, i, pod, true, "")
}
})
t.Run(ns+"_fail_"+checkID, func(t *testing.T) {
for i, pod := range checkData.fail {
createPod(t, i, pod, false, checkData.expectErrorSubstring)
createController(t, i, pod, false, checkData.expectErrorSubstring)
}
})
}
}
}
}
func DefaultCreateNamespace(client kubernetes.Interface, name string, labels map[string]string) (*corev1.Namespace, error) {
return client.CoreV1().Namespaces().Create(
context.Background(),
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: name, Labels: labels},
},
metav1.CreateOptions{},
)
}

View File

@ -0,0 +1 @@
The fixtures in this folder are generated by TestFixtures.