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 <moyerm@vmware.com>
This commit is contained in:
Matt Moyer 2020-09-24 17:51:43 -05:00
parent b21b43c654
commit 434e3fe435
No known key found for this signature in database
GPG Key ID: EAE88AD172C5AE2D
10 changed files with 164 additions and 142 deletions

View File

@ -19,7 +19,7 @@ import (
) )
func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) {
library.SkipUnlessIntegration(t) env := library.IntegrationEnv(t)
const defaultServingCertResourceName = "pinniped-api-tls-serving-certificate" const defaultServingCertResourceName = "pinniped-api-tls-serving-certificate"
@ -74,8 +74,6 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
namespaceName := library.GetEnv(t, "PINNIPED_NAMESPACE")
kubeClient := library.NewClientset(t) kubeClient := library.NewClientset(t)
aggregatedClient := library.NewAggregatedClientset(t) aggregatedClient := library.NewAggregatedClientset(t)
pinnipedClient := library.NewPinnipedClientset(t) pinnipedClient := library.NewPinnipedClientset(t)
@ -85,7 +83,7 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) {
const apiServiceName = "v1alpha1.login.pinniped.dev" const apiServiceName = "v1alpha1.login.pinniped.dev"
// Get the initial auto-generated version of the Secret. // 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) require.NoError(t, err)
initialCACert := secret.Data["caCertificate"] initialCACert := secret.Data["caCertificate"]
initialPrivateKey := secret.Data["tlsPrivateKey"] initialPrivateKey := secret.Data["tlsPrivateKey"]
@ -100,11 +98,11 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) {
require.Equal(t, initialCACert, apiService.Spec.CABundle) require.Equal(t, initialCACert, apiService.Spec.CABundle)
// Force rotation to happen. // 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. // Expect that the Secret comes back right away with newly minted certs.
secretIsRegenerated := func() bool { 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 return err == nil
} }
assert.Eventually(t, secretIsRegenerated, 10*time.Second, 250*time.Millisecond) 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. // pod has rotated their cert, but not the other ones sitting behind the service.
aggregatedAPIWorking := func() bool { aggregatedAPIWorking := func() bool {
for i := 0; i < 10; i++ { 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{}, TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{}, ObjectMeta: metav1.ObjectMeta{},
Spec: loginv1alpha1.TokenCredentialRequestSpec{Token: "not a good token"}, Spec: loginv1alpha1.TokenCredentialRequestSpec{Token: "not a good token"},

View File

@ -17,16 +17,13 @@ import (
) )
func TestGetDeployment(t *testing.T) { func TestGetDeployment(t *testing.T) {
library.SkipUnlessIntegration(t) env := library.IntegrationEnv(t)
namespaceName := library.GetEnv(t, "PINNIPED_NAMESPACE")
deploymentName := library.GetEnv(t, "PINNIPED_APP_NAME")
client := library.NewClientset(t) client := library.NewClientset(t)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() 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) require.NoError(t, err)
cond := getDeploymentCondition(appDeployment.Status, appsv1.DeploymentAvailable) cond := getDeploymentCondition(appDeployment.Status, appsv1.DeploymentAvailable)

View File

@ -18,14 +18,7 @@ import (
) )
func TestCLI(t *testing.T) { func TestCLI(t *testing.T) {
library.SkipUnlessIntegration(t) env := library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable)
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"), " ", ""), ",",
)
// Create a test webhook configuration to use with the CLI. // Create a test webhook configuration to use with the CLI.
ctx, cancelFunc := context.WithTimeout(context.Background(), 4*time.Minute) ctx, cancelFunc := context.WithTimeout(context.Background(), 4*time.Minute)
@ -62,20 +55,20 @@ func TestCLI(t *testing.T) {
defer cleanupFunc() defer cleanupFunc()
// Run pinniped CLI to get kubeconfig. // 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 // In addition to the client-go based testing below, also try the kubeconfig
// with kubectl to validate that it works. // with kubectl to validate that it works.
adminClient := library.NewClientset(t) adminClient := library.NewClientset(t)
t.Run( t.Run(
"access as user with kubectl", "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 group := group
t.Run( t.Run(
"access as group "+group+" with kubectl", "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) kubeClient := library.NewClientsetForKubeConfig(t, kubeConfigYAML)
// Validate that we can auth to the API via our user. // Validate that we can auth to the API via our user.
t.Run("access as user with client-go", accessAsUserTest(ctx, adminClient, testUsername, kubeClient)) t.Run("access as user with client-go", accessAsUserTest(ctx, adminClient, env.TestUser.ExpectedUsername, kubeClient))
for _, group := range expectedTestUserGroups { for _, group := range env.TestUser.ExpectedGroups {
group := group group := group
t.Run("access as group "+group+" with client-go", accessAsGroupTest(ctx, adminClient, group, kubeClient)) t.Run("access as group "+group+" with client-go", accessAsGroupTest(ctx, adminClient, group, kubeClient))
} }

View File

@ -55,10 +55,7 @@ var (
var maskKey = func(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } var maskKey = func(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
func TestClient(t *testing.T) { func TestClient(t *testing.T) {
library.SkipUnlessIntegration(t) env := library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable)
library.SkipUnlessClusterHasCapability(t, library.ClusterSigningKeyIsAvailable)
token := library.GetEnv(t, "PINNIPED_TEST_USER_TOKEN")
namespace := library.GetEnv(t, "PINNIPED_NAMESPACE")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
@ -75,7 +72,7 @@ func TestClient(t *testing.T) {
var resp *clientauthenticationv1beta1.ExecCredential var resp *clientauthenticationv1beta1.ExecCredential
assert.Eventually(t, func() bool { 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 return err == nil
}, 10*time.Second, 500*time.Millisecond) }, 10*time.Second, 500*time.Millisecond)
require.NoError(t, err) require.NoError(t, err)

View File

@ -17,9 +17,7 @@ import (
) )
func TestCredentialIssuerConfig(t *testing.T) { func TestCredentialIssuerConfig(t *testing.T) {
library.SkipUnlessIntegration(t) env := library.IntegrationEnv(t)
namespaceName := library.GetEnv(t, "PINNIPED_NAMESPACE")
config := library.NewClientConfig(t) config := library.NewClientConfig(t)
client := library.NewPinnipedClientset(t) client := library.NewPinnipedClientset(t)
@ -29,7 +27,7 @@ func TestCredentialIssuerConfig(t *testing.T) {
t.Run("test successful CredentialIssuerConfig", func(t *testing.T) { t.Run("test successful CredentialIssuerConfig", func(t *testing.T) {
actualConfigList, err := client. actualConfigList, err := client.
ConfigV1alpha1(). ConfigV1alpha1().
CredentialIssuerConfigs(namespaceName). CredentialIssuerConfigs(env.Namespace).
List(ctx, metav1.ListOptions{}) List(ctx, metav1.ListOptions{})
require.NoError(t, err) require.NoError(t, err)
@ -43,7 +41,7 @@ func TestCredentialIssuerConfig(t *testing.T) {
actualStatusStrategy := actualStatusStrategies[0] actualStatusStrategy := actualStatusStrategies[0]
require.Equal(t, configv1alpha1.KubeClusterSigningCertificateStrategyType, actualStatusStrategy.Type) 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.SuccessStrategyStatus, actualStatusStrategy.Status)
require.Equal(t, configv1alpha1.FetchedKeyStrategyReason, actualStatusStrategy.Reason) require.Equal(t, configv1alpha1.FetchedKeyStrategyReason, actualStatusStrategy.Reason)
require.Equal(t, "Key was fetched successfully", actualStatusStrategy.Message) require.Equal(t, "Key was fetched successfully", actualStatusStrategy.Message)

View File

@ -7,7 +7,6 @@ import (
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"strings"
"testing" "testing"
"time" "time"
@ -40,12 +39,8 @@ func TestUnsuccessfulCredentialRequest(t *testing.T) {
} }
func TestSuccessfulCredentialRequest(t *testing.T) { func TestSuccessfulCredentialRequest(t *testing.T) {
library.SkipUnlessIntegration(t) env := library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable)
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"), " ", ""), ",",
)
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute)
defer cancel() defer cancel()
@ -64,8 +59,8 @@ func TestSuccessfulCredentialRequest(t *testing.T) {
require.Empty(t, response.Spec) require.Empty(t, response.Spec)
require.Empty(t, response.Status.Credential.Token) require.Empty(t, response.Status.Credential.Token)
require.NotEmpty(t, response.Status.Credential.ClientCertificateData) require.NotEmpty(t, response.Status.Credential.ClientCertificateData)
require.Equal(t, testUsername, getCommonName(t, response.Status.Credential.ClientCertificateData)) require.Equal(t, env.TestUser.ExpectedUsername, getCommonName(t, response.Status.Credential.ClientCertificateData))
require.ElementsMatch(t, expectedTestUserGroups, getOrganizations(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.NotEmpty(t, response.Status.Credential.ClientKeyData)
require.NotNil(t, response.Status.Credential.ExpirationTimestamp) require.NotNil(t, response.Status.Credential.ExpirationTimestamp)
require.InDelta(t, time.Until(response.Status.Credential.ExpirationTimestamp.Time), 1*time.Hour, float64(3*time.Minute)) 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( t.Run(
"access as user", "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 group := group
t.Run( t.Run(
"access as group "+group, "access as group "+group,
@ -94,8 +89,7 @@ func TestSuccessfulCredentialRequest(t *testing.T) {
} }
func TestFailedCredentialRequestWhenTheRequestIsValidButTheTokenDoesNotAuthenticateTheUser(t *testing.T) { func TestFailedCredentialRequestWhenTheRequestIsValidButTheTokenDoesNotAuthenticateTheUser(t *testing.T) {
library.SkipUnlessIntegration(t) library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable)
library.SkipUnlessClusterHasCapability(t, library.ClusterSigningKeyIsAvailable)
response, err := makeRequest(context.Background(), t, loginv1alpha1.TokenCredentialRequestSpec{Token: "not a good token"}) 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) { func TestCredentialRequest_ShouldFailWhenRequestDoesNotIncludeToken(t *testing.T) {
library.SkipUnlessIntegration(t) library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable)
library.SkipUnlessClusterHasCapability(t, library.ClusterSigningKeyIsAvailable)
response, err := makeRequest(context.Background(), t, loginv1alpha1.TokenCredentialRequestSpec{Token: ""}) 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) { func TestCredentialRequest_OtherwiseValidRequestWithRealTokenShouldFailWhenTheClusterIsNotCapable(t *testing.T) {
library.SkipUnlessIntegration(t) library.IntegrationEnv(t).WithoutCapability(library.ClusterSigningKeyIsAvailable)
library.SkipWhenClusterHasCapability(t, library.ClusterSigningKeyIsAvailable)
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel() defer cancel()
@ -146,23 +138,23 @@ func TestCredentialRequest_OtherwiseValidRequestWithRealTokenShouldFailWhenTheCl
func makeRequest(ctx context.Context, t *testing.T, spec loginv1alpha1.TokenCredentialRequestSpec) (*loginv1alpha1.TokenCredentialRequest, error) { func makeRequest(ctx context.Context, t *testing.T, spec loginv1alpha1.TokenCredentialRequestSpec) (*loginv1alpha1.TokenCredentialRequest, error) {
t.Helper() t.Helper()
env := library.IntegrationEnv(t)
client := library.NewAnonymousPinnipedClientset(t) client := library.NewAnonymousPinnipedClientset(t)
ctx, cancel := context.WithTimeout(ctx, 10*time.Second) ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel() defer cancel()
ns := library.GetEnv(t, "PINNIPED_NAMESPACE") return client.LoginV1alpha1().TokenCredentialRequests(env.Namespace).Create(ctx, &loginv1alpha1.TokenCredentialRequest{
return client.LoginV1alpha1().TokenCredentialRequests(ns).Create(ctx, &loginv1alpha1.TokenCredentialRequest{
TypeMeta: metav1.TypeMeta{}, TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{Namespace: ns}, ObjectMeta: metav1.ObjectMeta{Namespace: env.Namespace},
Spec: spec, Spec: spec,
}, metav1.CreateOptions{}) }, metav1.CreateOptions{})
} }
func validCredentialRequestSpecWithRealToken(t *testing.T, idp corev1.TypedLocalObjectReference) loginv1alpha1.TokenCredentialRequestSpec { func validCredentialRequestSpecWithRealToken(t *testing.T, idp corev1.TypedLocalObjectReference) loginv1alpha1.TokenCredentialRequestSpec {
return loginv1alpha1.TokenCredentialRequestSpec{ return loginv1alpha1.TokenCredentialRequestSpec{
Token: library.GetEnv(t, "PINNIPED_TEST_USER_TOKEN"), Token: library.IntegrationEnv(t).TestUser.Token,
IdentityProvider: idp, IdentityProvider: idp,
} }
} }

View File

@ -25,9 +25,7 @@ const (
) )
func TestKubeCertAgent(t *testing.T) { func TestKubeCertAgent(t *testing.T) {
library.SkipUnlessIntegration(t) env := library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable)
library.SkipUnlessClusterHasCapability(t, library.ClusterSigningKeyIsAvailable)
namespaceName := library.GetEnv(t, "PINNIPED_NAMESPACE")
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel() 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 // 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 // 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. // 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, LabelSelector: kubeCertAgentLabelSelector,
}) })
require.NoError(t, err) require.NoError(t, err)
@ -48,7 +46,7 @@ func TestKubeCertAgent(t *testing.T) {
agentPodsReconciled := func() bool { agentPodsReconciled := func() bool {
var currentAgentPods *corev1.PodList 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, LabelSelector: kubeCertAgentLabelSelector,
}) })
@ -92,7 +90,7 @@ func TestKubeCertAgent(t *testing.T) {
updatedAgentPod.Spec.Tolerations, updatedAgentPod.Spec.Tolerations,
corev1.Toleration{Key: "fake-toleration"}, 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) require.NoError(t, err)
// Make sure the original pods come back. // 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. // Delete the first pod. The controller should see it, and flip it back.
err = kubeClient. err = kubeClient.
CoreV1(). CoreV1().
Pods(namespaceName). Pods(env.Namespace).
Delete(ctx, originalAgentPods.Items[0].Name, metav1.DeleteOptions{}) Delete(ctx, originalAgentPods.Items[0].Name, metav1.DeleteOptions{})
require.NoError(t, err) require.NoError(t, err)

View File

@ -147,12 +147,10 @@ func newAnonymousClientRestConfigWithCertAndKeyAdded(t *testing.T, clientCertifi
// descibes the test IDP within the test namespace. // descibes the test IDP within the test namespace.
func CreateTestWebhookIDP(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference { func CreateTestWebhookIDP(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference {
t.Helper() 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) client := NewPinnipedClientset(t)
webhooks := client.IDPV1alpha1().WebhookIdentityProviders(namespace) webhooks := client.IDPV1alpha1().WebhookIdentityProviders(testEnv.Namespace)
createContext, cancel := context.WithTimeout(ctx, 5*time.Second) createContext, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
@ -163,10 +161,7 @@ func CreateTestWebhookIDP(ctx context.Context, t *testing.T) corev1.TypedLocalOb
Labels: map[string]string{"pinniped.dev/test": ""}, Labels: map[string]string{"pinniped.dev/test": ""},
Annotations: map[string]string{"pinniped.dev/testName": t.Name()}, Annotations: map[string]string{"pinniped.dev/testName": t.Name()},
}, },
Spec: idpv1alpha1.WebhookIdentityProviderSpec{ Spec: testEnv.TestWebhook,
Endpoint: endpoint,
TLS: &idpv1alpha1.TLSSpec{CertificateAuthorityData: caBundle},
},
}, metav1.CreateOptions{}) }, metav1.CreateOptions{})
require.NoError(t, err, "could not create test WebhookIdentityProvider") require.NoError(t, err, "could not create test WebhookIdentityProvider")
t.Logf("created test WebhookIdentityProvider %s/%s", idp.Namespace, idp.Name) t.Logf("created test WebhookIdentityProvider %s/%s", idp.Namespace, idp.Name)

View File

@ -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)
}
}

View File

@ -4,17 +4,134 @@
package library package library
import ( import (
"context"
"io/ioutil"
"os" "os"
"strings"
"testing" "testing"
"time"
"github.com/stretchr/testify/require" "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 type TestClusterCapability string
// empty. It returns the value of the environment variable.
func GetEnv(t *testing.T, key string) string { const (
t.Helper() ClusterSigningKeyIsAvailable = TestClusterCapability("clusterSigningKeyIsAvailable")
value := os.Getenv(key) )
require.NotEmptyf(t, value, "must specify %s env var for integration tests", key)
return value // 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
} }