github.com/someshkoli/terratest@v0.41.1/modules/k8s/service_account.go (about)

     1  package k8s
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/gruntwork-io/go-commons/errors"
     9  	"github.com/stretchr/testify/require"
    10  	corev1 "k8s.io/api/core/v1"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	"k8s.io/client-go/tools/clientcmd"
    13  	"k8s.io/client-go/tools/clientcmd/api"
    14  
    15  	"github.com/gruntwork-io/terratest/modules/logger"
    16  	"github.com/gruntwork-io/terratest/modules/retry"
    17  	"github.com/gruntwork-io/terratest/modules/testing"
    18  )
    19  
    20  // GetServiceAccount returns a Kubernetes service account resource in the provided namespace with the given name. The
    21  // namespace used is the one provided in the KubectlOptions. This will fail the test if there is an error.
    22  func GetServiceAccount(t testing.TestingT, options *KubectlOptions, serviceAccountName string) *corev1.ServiceAccount {
    23  	serviceAccount, err := GetServiceAccountE(t, options, serviceAccountName)
    24  	require.NoError(t, err)
    25  	return serviceAccount
    26  }
    27  
    28  // GetServiceAccountE returns a Kubernetes service account resource in the provided namespace with the given name. The
    29  // namespace used is the one provided in the KubectlOptions.
    30  func GetServiceAccountE(t testing.TestingT, options *KubectlOptions, serviceAccountName string) (*corev1.ServiceAccount, error) {
    31  	clientset, err := GetKubernetesClientFromOptionsE(t, options)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  	return clientset.CoreV1().ServiceAccounts(options.Namespace).Get(context.Background(), serviceAccountName, metav1.GetOptions{})
    36  }
    37  
    38  // CreateServiceAccount will create a new service account resource in the provided namespace with the given name. The
    39  // namespace used is the one provided in the KubectlOptions. This will fail the test if there is an error.
    40  func CreateServiceAccount(t testing.TestingT, options *KubectlOptions, serviceAccountName string) {
    41  	require.NoError(t, CreateServiceAccountE(t, options, serviceAccountName))
    42  }
    43  
    44  // CreateServiceAccountE will create a new service account resource in the provided namespace with the given name. The
    45  // namespace used is the one provided in the KubectlOptions.
    46  func CreateServiceAccountE(t testing.TestingT, options *KubectlOptions, serviceAccountName string) error {
    47  	clientset, err := GetKubernetesClientFromOptionsE(t, options)
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	serviceAccount := corev1.ServiceAccount{
    53  		ObjectMeta: metav1.ObjectMeta{
    54  			Name:      serviceAccountName,
    55  			Namespace: options.Namespace,
    56  		},
    57  	}
    58  	_, err = clientset.CoreV1().ServiceAccounts(options.Namespace).Create(context.Background(), &serviceAccount, metav1.CreateOptions{})
    59  	return err
    60  }
    61  
    62  // GetServiceAccountAuthToken will retrieve the ServiceAccount token from the cluster so it can be used to
    63  // authenticate requests as that ServiceAccount. This will fail the test if there is an error.
    64  func GetServiceAccountAuthToken(t testing.TestingT, kubectlOptions *KubectlOptions, serviceAccountName string) string {
    65  	token, err := GetServiceAccountAuthTokenE(t, kubectlOptions, serviceAccountName)
    66  	require.NoError(t, err)
    67  	return token
    68  }
    69  
    70  // GetServiceAccountAuthTokenE will retrieve the ServiceAccount token from the cluster so it can be used to
    71  // authenticate requests as that ServiceAccount.
    72  func GetServiceAccountAuthTokenE(t testing.TestingT, kubectlOptions *KubectlOptions, serviceAccountName string) (string, error) {
    73  	// Wait for the TokenController to provision a ServiceAccount token
    74  	msg, err := retry.DoWithRetryE(
    75  		t,
    76  		"Waiting for ServiceAccount Token to be provisioned",
    77  		30,
    78  		10*time.Second,
    79  		func() (string, error) {
    80  			logger.Logf(t, "Checking if service account has secret")
    81  			serviceAccount := GetServiceAccount(t, kubectlOptions, serviceAccountName)
    82  			if len(serviceAccount.Secrets) == 0 {
    83  				msg := "No secrets on the service account yet"
    84  				logger.Logf(t, msg)
    85  				return "", fmt.Errorf(msg)
    86  			}
    87  			return "Service Account has secret", nil
    88  		},
    89  	)
    90  	if err != nil {
    91  		return "", err
    92  	}
    93  	logger.Logf(t, msg)
    94  
    95  	// Then get the service account token
    96  	serviceAccount, err := GetServiceAccountE(t, kubectlOptions, serviceAccountName)
    97  	if err != nil {
    98  		return "", err
    99  	}
   100  	if len(serviceAccount.Secrets) != 1 {
   101  		return "", errors.WithStackTrace(ServiceAccountTokenNotAvailable{serviceAccountName})
   102  	}
   103  	secret := GetSecret(t, kubectlOptions, serviceAccount.Secrets[0].Name)
   104  	return string(secret.Data["token"]), nil
   105  }
   106  
   107  // AddConfigContextForServiceAccountE will add a new config context that binds the ServiceAccount auth token to the
   108  // Kubernetes cluster of the current config context.
   109  func AddConfigContextForServiceAccountE(
   110  	t testing.TestingT,
   111  	kubectlOptions *KubectlOptions,
   112  	contextName string,
   113  	serviceAccountName string,
   114  	token string,
   115  ) error {
   116  	// First load the config context
   117  	config := LoadConfigFromPath(kubectlOptions.ConfigPath)
   118  	rawConfig, err := config.RawConfig()
   119  	if err != nil {
   120  		return errors.WithStackTrace(err)
   121  	}
   122  
   123  	// Next get the current cluster
   124  	currentContext := rawConfig.Contexts[rawConfig.CurrentContext]
   125  	currentCluster := currentContext.Cluster
   126  
   127  	// Now insert the auth info for the service account
   128  	rawConfig.AuthInfos[serviceAccountName] = &api.AuthInfo{Token: token}
   129  
   130  	// We now have enough info to add the new context
   131  	UpsertConfigContext(&rawConfig, contextName, currentCluster, serviceAccountName)
   132  
   133  	// Finally, overwrite the config
   134  	if err := clientcmd.ModifyConfig(config.ConfigAccess(), rawConfig, false); err != nil {
   135  		return errors.WithStackTrace(err)
   136  	}
   137  	return nil
   138  }