Skip to content

Commit affea40

Browse files
committed
add .spec.serviceAccountName to helm repository for type oci
Signed-off-by: Somtochi Onyekwere <[email protected]>
1 parent 38cff76 commit affea40

File tree

4 files changed

+96
-7
lines changed

4 files changed

+96
-7
lines changed

api/v1beta2/helmrepository_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ type HelmRepositorySpec struct {
104104
// +optional
105105
Type string `json:"type,omitempty"`
106106

107+
// ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate
108+
// the OCI image pull if the service account has attached pull secrets. For more information:
109+
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account
110+
// +optional
111+
ServiceAccountName string `json:"serviceAccountName,omitempty"`
112+
107113
// Provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'.
108114
// This field is optional, and only taken into account if the .spec.type field is set to 'oci'.
109115
// When not specified, defaults to 'generic'.

config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,11 @@ spec:
345345
required:
346346
- name
347347
type: object
348+
serviceAccountName:
349+
description: 'ServiceAccountName is the name of the Kubernetes ServiceAccount
350+
used to authenticate the OCI image pull if the service account has
351+
attached pull secrets. For more information: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account'
352+
type: string
348353
suspend:
349354
description: Suspend tells the controller to suspend the reconciliation
350355
of this HelmRepository.

internal/helm/getter/client_opts.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
"github.com/fluxcd/pkg/oci"
2828
"github.com/google/go-containerregistry/pkg/authn"
29+
"github.com/google/go-containerregistry/pkg/authn/k8schain"
2930
helmgetter "helm.sh/helm/v3/pkg/getter"
3031
helmreg "helm.sh/helm/v3/pkg/registry"
3132
corev1 "k8s.io/api/core/v1"
@@ -80,6 +81,39 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *helmv1.HelmReposit
8081

8182
var authSecret *corev1.Secret
8283
var deprecatedTLSConfig bool
84+
85+
if ociRepo {
86+
if obj.Spec.ServiceAccountName != "" {
87+
serviceAccount := corev1.ServiceAccount{}
88+
// Lookup service account
89+
if err := c.Get(ctx, types.NamespacedName{
90+
Namespace: obj.GetNamespace(),
91+
Name: obj.Spec.ServiceAccountName,
92+
}, &serviceAccount); err != nil {
93+
return nil, fmt.Errorf("failed to get serviceaccout: %s", err)
94+
}
95+
96+
if len(serviceAccount.ImagePullSecrets) > 0 {
97+
imagePullSecrets := make([]corev1.Secret, len(serviceAccount.ImagePullSecrets))
98+
for i, ips := range serviceAccount.ImagePullSecrets {
99+
var saAuthSecret corev1.Secret
100+
if err := c.Get(ctx, types.NamespacedName{
101+
Namespace: obj.GetNamespace(),
102+
Name: ips.Name,
103+
}, &saAuthSecret); err != nil {
104+
return nil, fmt.Errorf("failed to get image pull secret '%s' for serviceaccount '%s': %w",
105+
ips.Name, obj.Spec.ServiceAccountName, err)
106+
}
107+
imagePullSecrets[i] = saAuthSecret
108+
}
109+
hrOpts.Keychain, err = k8schain.NewFromPullSecrets(ctx, imagePullSecrets)
110+
if err != nil {
111+
return nil, fmt.Errorf("error constructing keychain from image pull secrets: %w", err)
112+
}
113+
}
114+
}
115+
}
116+
83117
if obj.Spec.SecretRef != nil {
84118
authSecret, err = fetchSecret(ctx, c, obj.Spec.SecretRef.Name, obj.GetNamespace())
85119
if err != nil {
@@ -107,10 +141,16 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *helmv1.HelmReposit
107141
}
108142

109143
if ociRepo {
110-
hrOpts.Keychain, err = registry.LoginOptionFromSecret(url, *authSecret)
144+
keychain, err := registry.LoginOptionFromSecret(url, *authSecret)
111145
if err != nil {
112146
return nil, fmt.Errorf("failed to configure login options: %w", err)
113147
}
148+
149+
if hrOpts.Keychain != nil {
150+
hrOpts.Keychain = authn.NewMultiKeychain(keychain, hrOpts.Keychain)
151+
} else {
152+
hrOpts.Keychain = keychain
153+
}
114154
}
115155
} else if obj.Spec.Provider != helmv1.GenericOCIProvider && obj.Spec.Type == helmv1.HelmRepositoryTypeOCI && ociRepo {
116156
authenticator, authErr := soci.OIDCAuth(ctx, obj.Spec.URL, obj.Spec.Provider)

internal/helm/getter/client_opts_test.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,13 @@ func TestGetClientOpts(t *testing.T) {
4444
}
4545

4646
tests := []struct {
47-
name string
48-
certSecret *corev1.Secret
49-
authSecret *corev1.Secret
50-
afterFunc func(t *WithT, hcOpts *ClientOpts)
51-
oci bool
52-
err error
47+
name string
48+
certSecret *corev1.Secret
49+
authSecret *corev1.Secret
50+
serviceAccount *corev1.ServiceAccount
51+
afterFunc func(t *WithT, hcOpts *ClientOpts)
52+
oci bool
53+
err error
5354
}{
5455
{
5556
name: "HelmRepository with certSecretRef discards TLS config in secretRef",
@@ -117,6 +118,39 @@ func TestGetClientOpts(t *testing.T) {
117118
},
118119
oci: true,
119120
},
121+
{
122+
name: "OCI HelmRepository with serviceaccount name",
123+
serviceAccount: &corev1.ServiceAccount{
124+
ObjectMeta: metav1.ObjectMeta{
125+
Name: "test-sa",
126+
},
127+
ImagePullSecrets: []corev1.LocalObjectReference{
128+
{
129+
Name: "pull-secret",
130+
},
131+
},
132+
},
133+
authSecret: &corev1.Secret{
134+
ObjectMeta: metav1.ObjectMeta{
135+
Name: "pull-secret",
136+
},
137+
Type: corev1.SecretTypeDockerConfigJson,
138+
Data: map[string][]byte{
139+
corev1.DockerConfigJsonKey: []byte(`{"auths":{"ghcr.io":{"username":"user","password":"pass","auth":"dXNlcjpwYXNz"}}}`),
140+
},
141+
},
142+
afterFunc: func(t *WithT, hcOpts *ClientOpts) {
143+
repo, err := name.NewRepository("ghcr.io/dummy")
144+
t.Expect(err).ToNot(HaveOccurred())
145+
authenticator, err := hcOpts.Keychain.Resolve(repo)
146+
t.Expect(err).ToNot(HaveOccurred())
147+
config, err := authenticator.Authorization()
148+
t.Expect(err).ToNot(HaveOccurred())
149+
t.Expect(config.Username).To(Equal("user"))
150+
t.Expect(config.Password).To(Equal("pass"))
151+
},
152+
oci: true,
153+
},
120154
}
121155

122156
for _, tt := range tests {
@@ -147,6 +181,10 @@ func TestGetClientOpts(t *testing.T) {
147181
Name: tt.certSecret.Name,
148182
}
149183
}
184+
if tt.serviceAccount != nil {
185+
clientBuilder.WithObjects(tt.serviceAccount.DeepCopy())
186+
helmRepo.Spec.ServiceAccountName = tt.serviceAccount.Name
187+
}
150188
c := clientBuilder.Build()
151189

152190
clientOpts, err := GetClientOpts(context.TODO(), c, helmRepo, "https://ghcr.io/dummy")

0 commit comments

Comments
 (0)