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 }