From 434e3fe4353f54ba364ac464d852702a2062a78a Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Thu, 24 Sep 2020 17:51:43 -0500 Subject: [PATCH 1/2] Refactor integration test environment helpers to be more structured. This change replaces our previous test helpers for checking cluster capabilities and passing external test parameters. Prior to this change, we always used `$PINNIPED_*` environment variables and these variables were accessed throughout the test code. The new code introduces a more strongly-typed `TestEnv` structure and helpers which load and expose the parameters. Tests can now call `env := library.IntegrationEnv(t)`, then access parameters such as `env.Namespace` or `env.TestUser.Token`. This should make this data dependency easier to manage and refactor in the future. In many ways this is just an extended version of the previous cluster capabilities YAML. Tests can also check for cluster capabilities easily by using `env := library.IntegrationEnv(t).WithCapability(xyz)`. The actual parameters are still loaded from OS environment variables by default (for compatibility), but the code now also tries to load the data from a Kubernetes Secret (`integration/pinniped-test-env` by default). I'm hoping this will be a more convenient way to pass data between various scripts than the local `/tmp` directory. I hope to remove the OS environment code in a future commit. Signed-off-by: Matt Moyer --- test/integration/api_serving_certs_test.go | 12 +- test/integration/app_availability_test.go | 7 +- test/integration/cli_test.go | 21 +-- test/integration/client_test.go | 7 +- .../credentialissuerconfig_test.go | 8 +- test/integration/credentialrequest_test.go | 34 ++--- test/integration/kubecertagent_test.go | 12 +- test/library/client.go | 11 +- test/library/cluster_capabilities.go | 63 --------- test/library/env.go | 131 +++++++++++++++++- 10 files changed, 164 insertions(+), 142 deletions(-) delete mode 100644 test/library/cluster_capabilities.go diff --git a/test/integration/api_serving_certs_test.go b/test/integration/api_serving_certs_test.go index 7a1f8943..c291ca70 100644 --- a/test/integration/api_serving_certs_test.go +++ b/test/integration/api_serving_certs_test.go @@ -19,7 +19,7 @@ import ( ) func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { - library.SkipUnlessIntegration(t) + env := library.IntegrationEnv(t) const defaultServingCertResourceName = "pinniped-api-tls-serving-certificate" @@ -74,8 +74,6 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - namespaceName := library.GetEnv(t, "PINNIPED_NAMESPACE") - kubeClient := library.NewClientset(t) aggregatedClient := library.NewAggregatedClientset(t) pinnipedClient := library.NewPinnipedClientset(t) @@ -85,7 +83,7 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { const apiServiceName = "v1alpha1.login.pinniped.dev" // Get the initial auto-generated version of the Secret. - secret, err := kubeClient.CoreV1().Secrets(namespaceName).Get(ctx, defaultServingCertResourceName, metav1.GetOptions{}) + secret, err := kubeClient.CoreV1().Secrets(env.Namespace).Get(ctx, defaultServingCertResourceName, metav1.GetOptions{}) require.NoError(t, err) initialCACert := secret.Data["caCertificate"] initialPrivateKey := secret.Data["tlsPrivateKey"] @@ -100,11 +98,11 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { require.Equal(t, initialCACert, apiService.Spec.CABundle) // Force rotation to happen. - require.NoError(t, test.forceRotation(ctx, kubeClient, namespaceName)) + require.NoError(t, test.forceRotation(ctx, kubeClient, env.Namespace)) // Expect that the Secret comes back right away with newly minted certs. secretIsRegenerated := func() bool { - secret, err = kubeClient.CoreV1().Secrets(namespaceName).Get(ctx, defaultServingCertResourceName, metav1.GetOptions{}) + secret, err = kubeClient.CoreV1().Secrets(env.Namespace).Get(ctx, defaultServingCertResourceName, metav1.GetOptions{}) return err == nil } assert.Eventually(t, secretIsRegenerated, 10*time.Second, 250*time.Millisecond) @@ -135,7 +133,7 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { // pod has rotated their cert, but not the other ones sitting behind the service. aggregatedAPIWorking := func() bool { for i := 0; i < 10; i++ { - _, err = pinnipedClient.LoginV1alpha1().TokenCredentialRequests(namespaceName).Create(ctx, &loginv1alpha1.TokenCredentialRequest{ + _, err = pinnipedClient.LoginV1alpha1().TokenCredentialRequests(env.Namespace).Create(ctx, &loginv1alpha1.TokenCredentialRequest{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{}, Spec: loginv1alpha1.TokenCredentialRequestSpec{Token: "not a good token"}, diff --git a/test/integration/app_availability_test.go b/test/integration/app_availability_test.go index 0b1d2546..16b338e6 100644 --- a/test/integration/app_availability_test.go +++ b/test/integration/app_availability_test.go @@ -17,16 +17,13 @@ import ( ) func TestGetDeployment(t *testing.T) { - library.SkipUnlessIntegration(t) - namespaceName := library.GetEnv(t, "PINNIPED_NAMESPACE") - deploymentName := library.GetEnv(t, "PINNIPED_APP_NAME") - + env := library.IntegrationEnv(t) client := library.NewClientset(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - appDeployment, err := client.AppsV1().Deployments(namespaceName).Get(ctx, deploymentName, metav1.GetOptions{}) + appDeployment, err := client.AppsV1().Deployments(env.Namespace).Get(ctx, env.AppName, metav1.GetOptions{}) require.NoError(t, err) cond := getDeploymentCondition(appDeployment.Status, appsv1.DeploymentAvailable) diff --git a/test/integration/cli_test.go b/test/integration/cli_test.go index 60e525df..731f97fe 100644 --- a/test/integration/cli_test.go +++ b/test/integration/cli_test.go @@ -18,14 +18,7 @@ import ( ) func TestCLI(t *testing.T) { - library.SkipUnlessIntegration(t) - library.SkipUnlessClusterHasCapability(t, library.ClusterSigningKeyIsAvailable) - token := library.GetEnv(t, "PINNIPED_TEST_USER_TOKEN") - namespaceName := library.GetEnv(t, "PINNIPED_NAMESPACE") - testUsername := library.GetEnv(t, "PINNIPED_TEST_USER_USERNAME") - expectedTestUserGroups := strings.Split( - strings.ReplaceAll(library.GetEnv(t, "PINNIPED_TEST_USER_GROUPS"), " ", ""), ",", - ) + env := library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable) // Create a test webhook configuration to use with the CLI. ctx, cancelFunc := context.WithTimeout(context.Background(), 4*time.Minute) @@ -62,20 +55,20 @@ func TestCLI(t *testing.T) { defer cleanupFunc() // Run pinniped CLI to get kubeconfig. - kubeConfigYAML := runPinnipedCLI(t, pinnipedExe, token, namespaceName, "webhook", idp.Name) + kubeConfigYAML := runPinnipedCLI(t, pinnipedExe, env.TestUser.Token, env.Namespace, "webhook", idp.Name) // In addition to the client-go based testing below, also try the kubeconfig // with kubectl to validate that it works. adminClient := library.NewClientset(t) t.Run( "access as user with kubectl", - accessAsUserWithKubectlTest(ctx, adminClient, kubeConfigYAML, testUsername, namespaceName), + accessAsUserWithKubectlTest(ctx, adminClient, kubeConfigYAML, env.TestUser.ExpectedUsername, env.Namespace), ) - for _, group := range expectedTestUserGroups { + for _, group := range env.TestUser.ExpectedGroups { group := group t.Run( "access as group "+group+" with kubectl", - accessAsGroupWithKubectlTest(ctx, adminClient, kubeConfigYAML, group, namespaceName), + accessAsGroupWithKubectlTest(ctx, adminClient, kubeConfigYAML, group, env.Namespace), ) } @@ -83,8 +76,8 @@ func TestCLI(t *testing.T) { kubeClient := library.NewClientsetForKubeConfig(t, kubeConfigYAML) // Validate that we can auth to the API via our user. - t.Run("access as user with client-go", accessAsUserTest(ctx, adminClient, testUsername, kubeClient)) - for _, group := range expectedTestUserGroups { + t.Run("access as user with client-go", accessAsUserTest(ctx, adminClient, env.TestUser.ExpectedUsername, kubeClient)) + for _, group := range env.TestUser.ExpectedGroups { group := group t.Run("access as group "+group+" with client-go", accessAsGroupTest(ctx, adminClient, group, kubeClient)) } diff --git a/test/integration/client_test.go b/test/integration/client_test.go index 1c5da211..ebf1776b 100644 --- a/test/integration/client_test.go +++ b/test/integration/client_test.go @@ -55,10 +55,7 @@ var ( var maskKey = func(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } func TestClient(t *testing.T) { - library.SkipUnlessIntegration(t) - library.SkipUnlessClusterHasCapability(t, library.ClusterSigningKeyIsAvailable) - token := library.GetEnv(t, "PINNIPED_TEST_USER_TOKEN") - namespace := library.GetEnv(t, "PINNIPED_NAMESPACE") + env := library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() @@ -75,7 +72,7 @@ func TestClient(t *testing.T) { var resp *clientauthenticationv1beta1.ExecCredential assert.Eventually(t, func() bool { - resp, err = client.ExchangeToken(ctx, namespace, idp, token, string(clientConfig.CAData), clientConfig.Host) + resp, err = client.ExchangeToken(ctx, env.Namespace, idp, env.TestUser.Token, string(clientConfig.CAData), clientConfig.Host) return err == nil }, 10*time.Second, 500*time.Millisecond) require.NoError(t, err) diff --git a/test/integration/credentialissuerconfig_test.go b/test/integration/credentialissuerconfig_test.go index 6a52d93b..a5323b6f 100644 --- a/test/integration/credentialissuerconfig_test.go +++ b/test/integration/credentialissuerconfig_test.go @@ -17,9 +17,7 @@ import ( ) func TestCredentialIssuerConfig(t *testing.T) { - library.SkipUnlessIntegration(t) - namespaceName := library.GetEnv(t, "PINNIPED_NAMESPACE") - + env := library.IntegrationEnv(t) config := library.NewClientConfig(t) client := library.NewPinnipedClientset(t) @@ -29,7 +27,7 @@ func TestCredentialIssuerConfig(t *testing.T) { t.Run("test successful CredentialIssuerConfig", func(t *testing.T) { actualConfigList, err := client. ConfigV1alpha1(). - CredentialIssuerConfigs(namespaceName). + CredentialIssuerConfigs(env.Namespace). List(ctx, metav1.ListOptions{}) require.NoError(t, err) @@ -43,7 +41,7 @@ func TestCredentialIssuerConfig(t *testing.T) { actualStatusStrategy := actualStatusStrategies[0] require.Equal(t, configv1alpha1.KubeClusterSigningCertificateStrategyType, actualStatusStrategy.Type) - if library.ClusterHasCapability(t, library.ClusterSigningKeyIsAvailable) { + if env.HasCapability(library.ClusterSigningKeyIsAvailable) { require.Equal(t, configv1alpha1.SuccessStrategyStatus, actualStatusStrategy.Status) require.Equal(t, configv1alpha1.FetchedKeyStrategyReason, actualStatusStrategy.Reason) require.Equal(t, "Key was fetched successfully", actualStatusStrategy.Message) diff --git a/test/integration/credentialrequest_test.go b/test/integration/credentialrequest_test.go index 0c69a04b..277e7f3d 100644 --- a/test/integration/credentialrequest_test.go +++ b/test/integration/credentialrequest_test.go @@ -7,7 +7,6 @@ import ( "context" "crypto/x509" "encoding/pem" - "strings" "testing" "time" @@ -40,12 +39,8 @@ func TestUnsuccessfulCredentialRequest(t *testing.T) { } func TestSuccessfulCredentialRequest(t *testing.T) { - library.SkipUnlessIntegration(t) - library.SkipUnlessClusterHasCapability(t, library.ClusterSigningKeyIsAvailable) - testUsername := library.GetEnv(t, "PINNIPED_TEST_USER_USERNAME") - expectedTestUserGroups := strings.Split( - strings.ReplaceAll(library.GetEnv(t, "PINNIPED_TEST_USER_GROUPS"), " ", ""), ",", - ) + env := library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable) + ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute) defer cancel() @@ -64,8 +59,8 @@ func TestSuccessfulCredentialRequest(t *testing.T) { require.Empty(t, response.Spec) require.Empty(t, response.Status.Credential.Token) require.NotEmpty(t, response.Status.Credential.ClientCertificateData) - require.Equal(t, testUsername, getCommonName(t, response.Status.Credential.ClientCertificateData)) - require.ElementsMatch(t, expectedTestUserGroups, getOrganizations(t, response.Status.Credential.ClientCertificateData)) + require.Equal(t, env.TestUser.ExpectedUsername, getCommonName(t, response.Status.Credential.ClientCertificateData)) + require.ElementsMatch(t, env.TestUser.ExpectedGroups, getOrganizations(t, response.Status.Credential.ClientCertificateData)) require.NotEmpty(t, response.Status.Credential.ClientKeyData) require.NotNil(t, response.Status.Credential.ExpirationTimestamp) require.InDelta(t, time.Until(response.Status.Credential.ExpirationTimestamp.Time), 1*time.Hour, float64(3*time.Minute)) @@ -82,9 +77,9 @@ func TestSuccessfulCredentialRequest(t *testing.T) { t.Run( "access as user", - accessAsUserTest(ctx, adminClient, testUsername, clientWithCertFromCredentialRequest), + accessAsUserTest(ctx, adminClient, env.TestUser.ExpectedUsername, clientWithCertFromCredentialRequest), ) - for _, group := range expectedTestUserGroups { + for _, group := range env.TestUser.ExpectedGroups { group := group t.Run( "access as group "+group, @@ -94,8 +89,7 @@ func TestSuccessfulCredentialRequest(t *testing.T) { } func TestFailedCredentialRequestWhenTheRequestIsValidButTheTokenDoesNotAuthenticateTheUser(t *testing.T) { - library.SkipUnlessIntegration(t) - library.SkipUnlessClusterHasCapability(t, library.ClusterSigningKeyIsAvailable) + library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable) response, err := makeRequest(context.Background(), t, loginv1alpha1.TokenCredentialRequestSpec{Token: "not a good token"}) @@ -107,8 +101,7 @@ func TestFailedCredentialRequestWhenTheRequestIsValidButTheTokenDoesNotAuthentic } func TestCredentialRequest_ShouldFailWhenRequestDoesNotIncludeToken(t *testing.T) { - library.SkipUnlessIntegration(t) - library.SkipUnlessClusterHasCapability(t, library.ClusterSigningKeyIsAvailable) + library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable) response, err := makeRequest(context.Background(), t, loginv1alpha1.TokenCredentialRequestSpec{Token: ""}) @@ -127,8 +120,7 @@ func TestCredentialRequest_ShouldFailWhenRequestDoesNotIncludeToken(t *testing.T } func TestCredentialRequest_OtherwiseValidRequestWithRealTokenShouldFailWhenTheClusterIsNotCapable(t *testing.T) { - library.SkipUnlessIntegration(t) - library.SkipWhenClusterHasCapability(t, library.ClusterSigningKeyIsAvailable) + library.IntegrationEnv(t).WithoutCapability(library.ClusterSigningKeyIsAvailable) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() @@ -146,23 +138,23 @@ func TestCredentialRequest_OtherwiseValidRequestWithRealTokenShouldFailWhenTheCl func makeRequest(ctx context.Context, t *testing.T, spec loginv1alpha1.TokenCredentialRequestSpec) (*loginv1alpha1.TokenCredentialRequest, error) { t.Helper() + env := library.IntegrationEnv(t) client := library.NewAnonymousPinnipedClientset(t) ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - ns := library.GetEnv(t, "PINNIPED_NAMESPACE") - return client.LoginV1alpha1().TokenCredentialRequests(ns).Create(ctx, &loginv1alpha1.TokenCredentialRequest{ + return client.LoginV1alpha1().TokenCredentialRequests(env.Namespace).Create(ctx, &loginv1alpha1.TokenCredentialRequest{ TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{Namespace: ns}, + ObjectMeta: metav1.ObjectMeta{Namespace: env.Namespace}, Spec: spec, }, metav1.CreateOptions{}) } func validCredentialRequestSpecWithRealToken(t *testing.T, idp corev1.TypedLocalObjectReference) loginv1alpha1.TokenCredentialRequestSpec { return loginv1alpha1.TokenCredentialRequestSpec{ - Token: library.GetEnv(t, "PINNIPED_TEST_USER_TOKEN"), + Token: library.IntegrationEnv(t).TestUser.Token, IdentityProvider: idp, } } diff --git a/test/integration/kubecertagent_test.go b/test/integration/kubecertagent_test.go index f266cf99..4dfb7121 100644 --- a/test/integration/kubecertagent_test.go +++ b/test/integration/kubecertagent_test.go @@ -25,9 +25,7 @@ const ( ) func TestKubeCertAgent(t *testing.T) { - library.SkipUnlessIntegration(t) - library.SkipUnlessClusterHasCapability(t, library.ClusterSigningKeyIsAvailable) - namespaceName := library.GetEnv(t, "PINNIPED_NAMESPACE") + env := library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) defer cancel() @@ -39,7 +37,7 @@ func TestKubeCertAgent(t *testing.T) { // We can pretty safely assert there should be more than 1, since there should be a // kube-cert-agent pod per kube-controller-manager pod, and there should probably be at least // 1 kube-controller-manager for this to be a working kube API. - originalAgentPods, err := kubeClient.CoreV1().Pods(namespaceName).List(ctx, metav1.ListOptions{ + originalAgentPods, err := kubeClient.CoreV1().Pods(env.Namespace).List(ctx, metav1.ListOptions{ LabelSelector: kubeCertAgentLabelSelector, }) require.NoError(t, err) @@ -48,7 +46,7 @@ func TestKubeCertAgent(t *testing.T) { agentPodsReconciled := func() bool { var currentAgentPods *corev1.PodList - currentAgentPods, err = kubeClient.CoreV1().Pods(namespaceName).List(ctx, metav1.ListOptions{ + currentAgentPods, err = kubeClient.CoreV1().Pods(env.Namespace).List(ctx, metav1.ListOptions{ LabelSelector: kubeCertAgentLabelSelector, }) @@ -92,7 +90,7 @@ func TestKubeCertAgent(t *testing.T) { updatedAgentPod.Spec.Tolerations, corev1.Toleration{Key: "fake-toleration"}, ) - _, err = kubeClient.CoreV1().Pods(namespaceName).Update(ctx, updatedAgentPod, metav1.UpdateOptions{}) + _, err = kubeClient.CoreV1().Pods(env.Namespace).Update(ctx, updatedAgentPod, metav1.UpdateOptions{}) require.NoError(t, err) // Make sure the original pods come back. @@ -104,7 +102,7 @@ func TestKubeCertAgent(t *testing.T) { // Delete the first pod. The controller should see it, and flip it back. err = kubeClient. CoreV1(). - Pods(namespaceName). + Pods(env.Namespace). Delete(ctx, originalAgentPods.Items[0].Name, metav1.DeleteOptions{}) require.NoError(t, err) diff --git a/test/library/client.go b/test/library/client.go index 73c10b1c..a73cc4a9 100644 --- a/test/library/client.go +++ b/test/library/client.go @@ -147,12 +147,10 @@ func newAnonymousClientRestConfigWithCertAndKeyAdded(t *testing.T, clientCertifi // descibes the test IDP within the test namespace. func CreateTestWebhookIDP(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference { t.Helper() + testEnv := IntegrationEnv(t) - namespace := GetEnv(t, "PINNIPED_NAMESPACE") - endpoint := GetEnv(t, "PINNIPED_TEST_WEBHOOK_ENDPOINT") - caBundle := GetEnv(t, "PINNIPED_TEST_WEBHOOK_CA_BUNDLE") client := NewPinnipedClientset(t) - webhooks := client.IDPV1alpha1().WebhookIdentityProviders(namespace) + webhooks := client.IDPV1alpha1().WebhookIdentityProviders(testEnv.Namespace) createContext, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() @@ -163,10 +161,7 @@ func CreateTestWebhookIDP(ctx context.Context, t *testing.T) corev1.TypedLocalOb Labels: map[string]string{"pinniped.dev/test": ""}, Annotations: map[string]string{"pinniped.dev/testName": t.Name()}, }, - Spec: idpv1alpha1.WebhookIdentityProviderSpec{ - Endpoint: endpoint, - TLS: &idpv1alpha1.TLSSpec{CertificateAuthorityData: caBundle}, - }, + Spec: testEnv.TestWebhook, }, metav1.CreateOptions{}) require.NoError(t, err, "could not create test WebhookIdentityProvider") t.Logf("created test WebhookIdentityProvider %s/%s", idp.Namespace, idp.Name) diff --git a/test/library/cluster_capabilities.go b/test/library/cluster_capabilities.go deleted file mode 100644 index e32ad142..00000000 --- a/test/library/cluster_capabilities.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2020 the Pinniped contributors. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package library - -import ( - "io/ioutil" - "os" - "testing" - - "github.com/stretchr/testify/require" - "sigs.k8s.io/yaml" -) - -type TestClusterCapability string - -const ( - ClusterSigningKeyIsAvailable = TestClusterCapability("clusterSigningKeyIsAvailable") -) - -type capabilitiesConfig struct { - Capabilities map[TestClusterCapability]bool `yaml:"capabilities,omitempty"` -} - -func ClusterHasCapability(t *testing.T, capability TestClusterCapability) bool { - t.Helper() - - capabilitiesDescriptionYAML := os.Getenv("PINNIPED_CLUSTER_CAPABILITY_YAML") - capabilitiesDescriptionFile := os.Getenv("PINNIPED_CLUSTER_CAPABILITY_FILE") - require.NotEmptyf(t, - capabilitiesDescriptionYAML+capabilitiesDescriptionFile, - "must specify either PINNIPED_CLUSTER_CAPABILITY_YAML or PINNIPED_CLUSTER_CAPABILITY_FILE env var for integration tests", - ) - - if capabilitiesDescriptionYAML == "" { - bytes, err := ioutil.ReadFile(capabilitiesDescriptionFile) - capabilitiesDescriptionYAML = string(bytes) - require.NoError(t, err) - } - - var capabilities capabilitiesConfig - err := yaml.Unmarshal([]byte(capabilitiesDescriptionYAML), &capabilities) - require.NoError(t, err) - - isCapable, capabilityWasDescribed := capabilities.Capabilities[capability] - require.True(t, capabilityWasDescribed, `the cluster's "%s" capability was not described`, capability) - - return isCapable -} - -func SkipUnlessClusterHasCapability(t *testing.T, capability TestClusterCapability) { - t.Helper() - if !ClusterHasCapability(t, capability) { - t.Skipf(`skipping integration test because cluster lacks the "%s" capability`, capability) - } -} - -func SkipWhenClusterHasCapability(t *testing.T, capability TestClusterCapability) { - t.Helper() - if ClusterHasCapability(t, capability) { - t.Skipf(`skipping integration test because cluster has the "%s" capability`, capability) - } -} diff --git a/test/library/env.go b/test/library/env.go index db95a162..16a4667d 100644 --- a/test/library/env.go +++ b/test/library/env.go @@ -4,17 +4,134 @@ package library import ( + "context" + "io/ioutil" "os" + "strings" "testing" + "time" "github.com/stretchr/testify/require" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" + + idpv1alpha1 "go.pinniped.dev/generated/1.19/apis/idp/v1alpha1" ) -// GetEnv gets the environment variable with key and asserts that it is not -// empty. It returns the value of the environment variable. -func GetEnv(t *testing.T, key string) string { - t.Helper() - value := os.Getenv(key) - require.NotEmptyf(t, value, "must specify %s env var for integration tests", key) - return value +type TestClusterCapability string + +const ( + ClusterSigningKeyIsAvailable = TestClusterCapability("clusterSigningKeyIsAvailable") +) + +// TestEnv captures all the external parameters consumed by our integration tests. +type TestEnv struct { + t *testing.T + + Namespace string `json:"namespace"` + AppName string `json:"appName"` + Capabilities map[TestClusterCapability]bool `json:"capabilities"` + TestWebhook idpv1alpha1.WebhookIdentityProviderSpec `json:"testWebhook"` + TestUser struct { + Token string `json:"token"` + ExpectedUsername string `json:"expectedUsername"` + ExpectedGroups []string `json:"expectedGroups"` + } `json:"testUser"` +} + +// IntegrationEnv gets the integration test environment from a Kubernetes Secret in the test cluster. This +// method also implies SkipUnlessIntegration(). +func IntegrationEnv(t *testing.T) *TestEnv { + t.Helper() + SkipUnlessIntegration(t) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + secretNamespace := getDefaultedEnv("PINNIPED_NAMESPACE", "integration") + secretName := getDefaultedEnv("PINNIPED_ENVIRONMENT", "pinniped-test-env") + + secret, err := NewClientset(t).CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{}) + if k8serrors.IsNotFound(err) { + return envFromOSEnviron(t) + } + require.NoErrorf(t, err, "could not fetch test environment from %s/%s", secretNamespace, secretName) + + yamlEnv, ok := secret.Data["env.yaml"] + require.True(t, ok, "test environment secret %s/%s did not contain expected 'env.yaml' key", secretNamespace, secretName) + + var result TestEnv + err = yaml.Unmarshal(yamlEnv, &result) + require.NoErrorf(t, err, "test environment secret %s/%s contained invalid YAML", secretNamespace, secretName) + result.t = t + return &result +} + +func (e *TestEnv) HasCapability(cap TestClusterCapability) bool { + e.t.Helper() + isCapable, capabilityWasDescribed := e.Capabilities[cap] + require.True(e.t, capabilityWasDescribed, `the cluster's "%s" capability was not described`, cap) + return isCapable +} + +func (e *TestEnv) WithCapability(cap TestClusterCapability) *TestEnv { + e.t.Helper() + if !e.HasCapability(cap) { + e.t.Skipf(`skipping integration test because cluster lacks the "%s" capability`, cap) + } + return e +} + +func (e *TestEnv) WithoutCapability(cap TestClusterCapability) *TestEnv { + e.t.Helper() + if e.HasCapability(cap) { + e.t.Skipf(`skipping integration test because cluster has the "%s" capability`, cap) + } + return e +} + +func getDefaultedEnv(name, defaultValue string) string { + if val := os.Getenv(name); val != "" { + return val + } + return defaultValue +} + +// envFromOSEnviron is a (temporary?) helper to pull information from os.Environ instead of the test cluster. +func envFromOSEnviron(t *testing.T) *TestEnv { + t.Helper() + + capabilitiesDescriptionYAML := os.Getenv("PINNIPED_CLUSTER_CAPABILITY_YAML") + capabilitiesDescriptionFile := os.Getenv("PINNIPED_CLUSTER_CAPABILITY_FILE") + require.NotEmptyf(t, + capabilitiesDescriptionYAML+capabilitiesDescriptionFile, + "must specify either PINNIPED_CLUSTER_CAPABILITY_YAML or PINNIPED_CLUSTER_CAPABILITY_FILE env var for integration tests", + ) + if capabilitiesDescriptionYAML == "" { + bytes, err := ioutil.ReadFile(capabilitiesDescriptionFile) + capabilitiesDescriptionYAML = string(bytes) + require.NoError(t, err) + } + + var result TestEnv + err := yaml.Unmarshal([]byte(capabilitiesDescriptionYAML), &result) + require.NoErrorf(t, err, "capabilities specification was invalid YAML") + + needEnv := func(key string) string { + t.Helper() + value := os.Getenv(key) + require.NotEmptyf(t, value, "must specify %s env var for integration tests", key) + return value + } + + result.Namespace = needEnv("PINNIPED_NAMESPACE") + result.AppName = needEnv("PINNIPED_APP_NAME") + result.TestUser.ExpectedUsername = needEnv("PINNIPED_TEST_USER_USERNAME") + result.TestUser.ExpectedGroups = strings.Split(strings.ReplaceAll(needEnv("PINNIPED_TEST_USER_GROUPS"), " ", ""), ",") + result.TestUser.Token = needEnv("PINNIPED_TEST_USER_TOKEN") + result.TestWebhook.Endpoint = needEnv("PINNIPED_TEST_WEBHOOK_ENDPOINT") + result.TestWebhook.TLS = &idpv1alpha1.TLSSpec{CertificateAuthorityData: needEnv("PINNIPED_TEST_WEBHOOK_CA_BUNDLE")} + result.t = t + return &result } From 70480260dd0fa7e0deef05086c73afa1ca67e06d Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Fri, 25 Sep 2020 09:37:17 -0500 Subject: [PATCH 2/2] Remove support for loading test context from a Secret. Signed-off-by: Matt Moyer --- test/library/env.go | 83 +++++++++++++-------------------------------- 1 file changed, 23 insertions(+), 60 deletions(-) diff --git a/test/library/env.go b/test/library/env.go index 16a4667d..c3479a37 100644 --- a/test/library/env.go +++ b/test/library/env.go @@ -4,16 +4,12 @@ package library import ( - "context" "io/ioutil" "os" "strings" "testing" - "time" "github.com/stretchr/testify/require" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" idpv1alpha1 "go.pinniped.dev/generated/1.19/apis/idp/v1alpha1" @@ -46,62 +42,6 @@ func IntegrationEnv(t *testing.T) *TestEnv { t.Helper() SkipUnlessIntegration(t) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - secretNamespace := getDefaultedEnv("PINNIPED_NAMESPACE", "integration") - secretName := getDefaultedEnv("PINNIPED_ENVIRONMENT", "pinniped-test-env") - - secret, err := NewClientset(t).CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{}) - if k8serrors.IsNotFound(err) { - return envFromOSEnviron(t) - } - require.NoErrorf(t, err, "could not fetch test environment from %s/%s", secretNamespace, secretName) - - yamlEnv, ok := secret.Data["env.yaml"] - require.True(t, ok, "test environment secret %s/%s did not contain expected 'env.yaml' key", secretNamespace, secretName) - - var result TestEnv - err = yaml.Unmarshal(yamlEnv, &result) - require.NoErrorf(t, err, "test environment secret %s/%s contained invalid YAML", secretNamespace, secretName) - result.t = t - return &result -} - -func (e *TestEnv) HasCapability(cap TestClusterCapability) bool { - e.t.Helper() - isCapable, capabilityWasDescribed := e.Capabilities[cap] - require.True(e.t, capabilityWasDescribed, `the cluster's "%s" capability was not described`, cap) - return isCapable -} - -func (e *TestEnv) WithCapability(cap TestClusterCapability) *TestEnv { - e.t.Helper() - if !e.HasCapability(cap) { - e.t.Skipf(`skipping integration test because cluster lacks the "%s" capability`, cap) - } - return e -} - -func (e *TestEnv) WithoutCapability(cap TestClusterCapability) *TestEnv { - e.t.Helper() - if e.HasCapability(cap) { - e.t.Skipf(`skipping integration test because cluster has the "%s" capability`, cap) - } - return e -} - -func getDefaultedEnv(name, defaultValue string) string { - if val := os.Getenv(name); val != "" { - return val - } - return defaultValue -} - -// envFromOSEnviron is a (temporary?) helper to pull information from os.Environ instead of the test cluster. -func envFromOSEnviron(t *testing.T) *TestEnv { - t.Helper() - capabilitiesDescriptionYAML := os.Getenv("PINNIPED_CLUSTER_CAPABILITY_YAML") capabilitiesDescriptionFile := os.Getenv("PINNIPED_CLUSTER_CAPABILITY_FILE") require.NotEmptyf(t, @@ -135,3 +75,26 @@ func envFromOSEnviron(t *testing.T) *TestEnv { result.t = t return &result } + +func (e *TestEnv) HasCapability(cap TestClusterCapability) bool { + e.t.Helper() + isCapable, capabilityWasDescribed := e.Capabilities[cap] + require.True(e.t, capabilityWasDescribed, `the cluster's "%s" capability was not described`, cap) + return isCapable +} + +func (e *TestEnv) WithCapability(cap TestClusterCapability) *TestEnv { + e.t.Helper() + if !e.HasCapability(cap) { + e.t.Skipf(`skipping integration test because cluster lacks the "%s" capability`, cap) + } + return e +} + +func (e *TestEnv) WithoutCapability(cap TestClusterCapability) *TestEnv { + e.t.Helper() + if e.HasCapability(cap) { + e.t.Skipf(`skipping integration test because cluster has the "%s" capability`, cap) + } + return e +}