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