/
serviceAccountManager.go
217 lines (188 loc) · 6.53 KB
/
serviceAccountManager.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package k8s
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
)
const (
checkRoleExistence = true
)
//ServiceAccountManager manages serviceAccounts
type ServiceAccountManager interface {
CreateServiceAccount(ctx context.Context, name string, pipelineCloneSecretName string, imagePullSecretNames []string) (*ServiceAccountWrap, error)
GetServiceAccount(ctx context.Context, name string) (*ServiceAccountWrap, error)
}
type serviceAccountManager struct {
factory ClientFactory
client corev1client.ServiceAccountInterface
}
// ServiceAccountWrap wraps a Service Account and enriches it with futher things
type ServiceAccountWrap struct {
factory ClientFactory
cache *corev1.ServiceAccount
}
// RoleName to be attached
type RoleName string
//NewServiceAccountManager creates ServiceAccountManager
func NewServiceAccountManager(factory ClientFactory, namespace string) ServiceAccountManager {
return &serviceAccountManager{
factory: factory,
client: factory.CoreV1().ServiceAccounts(namespace),
}
}
// CreateServiceAccount creates a service account on the cluster
// name name of the service account
// pipelineCloneSecretName (optional) the name of the secret to be used to authenticate at the Git repository hosting the pipeline definition.
// imagePullSecretNames (optional) a list of image pull secrets to attach to this service account (e.g. for pulling the Jenkinsfile Runner image)
func (c *serviceAccountManager) CreateServiceAccount(ctx context.Context, name string, pipelineCloneSecretName string, imagePullSecretNames []string) (*ServiceAccountWrap, error) {
serviceAccount := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: name}}
serviceAccountWrap := &ServiceAccountWrap{
factory: c.factory,
cache: serviceAccount,
}
if pipelineCloneSecretName != "" {
serviceAccountWrap.AttachSecrets(pipelineCloneSecretName)
}
serviceAccountWrap.AttachImagePullSecrets(imagePullSecretNames...)
serviceAccount, err := c.client.Create(ctx, serviceAccount, metav1.CreateOptions{})
serviceAccountWrap.cache = serviceAccount
return serviceAccountWrap, err
}
// GetServiceAccount gets a ServiceAccount from the cluster
func (c *serviceAccountManager) GetServiceAccount(ctx context.Context, name string) (serviceAccount *ServiceAccountWrap, err error) {
var account *corev1.ServiceAccount
if account, err = c.client.Get(ctx, name, metav1.GetOptions{}); err != nil {
return
}
serviceAccount = &ServiceAccountWrap{
factory: c.factory,
cache: account,
}
return
}
// AttachSecrets attaches a number of secrets to the service account.
// It does NOT create or update the resource via the underlying client.
func (a *ServiceAccountWrap) AttachSecrets(secretNames ...string) {
if len(secretNames) == 0 {
return
}
secretRefs := a.cache.Secrets
haveSecretAlready := func(name string) bool {
for _, secretRef := range secretRefs {
if secretRef.Name == name {
return true
}
}
return false
}
changed := false
for _, secretName := range secretNames {
if secretName == "" {
continue
}
if !haveSecretAlready(secretName) {
secretRef := corev1.ObjectReference{Name: secretName}
secretRefs = append(secretRefs, secretRef)
changed = true
}
}
if changed {
a.cache.Secrets = secretRefs
}
}
// AttachImagePullSecrets attaches a number of secrets to the service account.
// It does NOT create or update the resource via the underlying client.
func (a *ServiceAccountWrap) AttachImagePullSecrets(secretNames ...string) {
if len(secretNames) == 0 {
return
}
secretRefs := a.cache.ImagePullSecrets
haveSecretAlready := func(name string) bool {
for _, secretRef := range secretRefs {
if secretRef.Name == name {
return true
}
}
return false
}
changed := false
for _, secretName := range secretNames {
if secretName == "" {
continue
}
if !haveSecretAlready(secretName) {
secretRef := corev1.LocalObjectReference{Name: secretName}
secretRefs = append(secretRefs, secretRef)
changed = true
}
}
if changed {
a.cache.ImagePullSecrets = secretRefs
}
}
// SetDoAutomountServiceAccountToken sets the `automountServiceAccountToken` flag in the
// service account spec.
// It does NOT create or update the resource via the underlying client.
func (a ServiceAccountWrap) SetDoAutomountServiceAccountToken(doAutomount bool) {
var doAutomountPtrFromResource *bool = a.cache.AutomountServiceAccountToken
if doAutomountPtrFromResource == nil || *doAutomountPtrFromResource != doAutomount {
a.cache.AutomountServiceAccountToken = &doAutomount
}
}
// Update performs an update of the service account resource object
// via the underlying client.
func (a *ServiceAccountWrap) Update(ctx context.Context) error {
client := a.factory.CoreV1().ServiceAccounts(a.cache.GetNamespace())
updatedObj, err := client.Update(ctx, a.cache, metav1.UpdateOptions{})
if err != nil {
return err
}
a.cache = updatedObj
return nil
}
// AddRoleBinding creates a role binding in the targetNamespace connecting the service account with the specified cluster role
func (a *ServiceAccountWrap) AddRoleBinding(ctx context.Context, clusterRole RoleName, targetNamespace string) (*rbacv1.RoleBinding, error) {
//Check if cluster role exists
if checkRoleExistence {
clusterRole, err := a.factory.RbacV1().ClusterRoles().Get(ctx, string(clusterRole), metav1.GetOptions{})
if err != nil {
return nil, err
}
if clusterRole == nil {
return nil, fmt.Errorf("Cluster Role '%v' does not exist", clusterRole)
}
}
//Create role binding
roleBindingClient := a.factory.RbacV1().RoleBindings(targetNamespace)
roleBinding := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: string(clusterRole),
Namespace: targetNamespace,
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: a.cache.GetName(),
Namespace: a.cache.GetNamespace(),
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: string(clusterRole),
},
}
return roleBindingClient.Create(ctx, roleBinding, metav1.CreateOptions{})
}
// GetServiceAccount returns *v1.ServiceAccount
func (a *ServiceAccountWrap) GetServiceAccount() *corev1.ServiceAccount {
return a.cache
}
// ServiceAccountHelper implements functions to get service account secret
type ServiceAccountHelper interface {
GetServiceAccountSecretNameRepeat(ctx context.Context) (string, error)
GetServiceAccountSecretName(ctx context.Context) (string, error)
}