Skip to content

Commit fb76722

Browse files
committed
Add support for a chunked release storage driver
This commit adds a custom helm release storage driver that overcomes limitations in the size of a single value that can be stored in etcd. In order to remain backward-compatible and also make this storage driver available, this commit also refactors the ActionConfigGetter options so that a custom function can be provided to the ActionConfigGetter that can create the desired storage driver. This commit also separates the rest config mapping into two separate options. One for interactions with the storage backend, and the other for managing release content. Signed-off-by: Joe Lanford <[email protected]>
1 parent 788eb07 commit fb76722

File tree

6 files changed

+931
-37
lines changed

6 files changed

+931
-37
lines changed

pkg/client/actionconfig.go

Lines changed: 136 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import (
3333
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
3434
"k8s.io/client-go/rest"
3535
"sigs.k8s.io/controller-runtime/pkg/client"
36+
37+
customstorage "github.com/operator-framework/helm-operator-plugins/pkg/storage"
3638
)
3739

3840
type ActionConfigGetter interface {
@@ -57,14 +59,25 @@ func NewActionConfigGetter(baseRestConfig *rest.Config, rm meta.RESTMapper, opts
5759
if acg.objectToClientNamespace == nil {
5860
acg.objectToClientNamespace = getObjectNamespace
5961
}
60-
if acg.objectToStorageNamespace == nil {
61-
acg.objectToStorageNamespace = getObjectNamespace
62+
if acg.objectToClientRestConfig == nil {
63+
acg.objectToClientRestConfig = func(_ context.Context, _ client.Object, baseRestConfig *rest.Config) (*rest.Config, error) {
64+
return rest.CopyConfig(baseRestConfig), nil
65+
}
6266
}
63-
if acg.objectToRestConfig == nil {
64-
acg.objectToRestConfig = func(_ context.Context, _ client.Object, baseRestConfig *rest.Config) (*rest.Config, error) {
67+
if acg.objectToStorageRestConfig == nil {
68+
acg.objectToStorageRestConfig = func(_ context.Context, _ client.Object, baseRestConfig *rest.Config) (*rest.Config, error) {
6569
return rest.CopyConfig(baseRestConfig), nil
6670
}
6771
}
72+
if acg.objectToStorageDriver == nil {
73+
if acg.objectToStorageNamespace == nil {
74+
acg.objectToStorageNamespace = getObjectNamespace
75+
}
76+
acg.objectToStorageDriver = DefaultSecretsStorageDriver(SecretsStorageDriverOpts{
77+
DisableOwnerRefInjection: acg.disableStorageOwnerRefInjection,
78+
StorageNamespaceMapper: acg.objectToStorageNamespace,
79+
})
80+
}
6881
return acg, nil
6982
}
7083

@@ -73,28 +86,52 @@ var _ ActionConfigGetter = &actionConfigGetter{}
7386
type ActionConfigGetterOption func(getter *actionConfigGetter)
7487

7588
type ObjectToStringMapper func(client.Object) (string, error)
89+
type ObjectToRestConfigMapper func(context.Context, client.Object, *rest.Config) (*rest.Config, error)
90+
type ObjectToStorageDriverMapper func(context.Context, client.Object, *rest.Config) (driver.Driver, error)
91+
92+
func ClientRestConfigMapper(f ObjectToRestConfigMapper) ActionConfigGetterOption { // nolint:revive
93+
return func(getter *actionConfigGetter) {
94+
getter.objectToClientRestConfig = f
95+
}
96+
}
7697

7798
func ClientNamespaceMapper(m ObjectToStringMapper) ActionConfigGetterOption { // nolint:revive
7899
return func(getter *actionConfigGetter) {
79100
getter.objectToClientNamespace = m
80101
}
81102
}
82103

104+
func StorageRestConfigMapper(f ObjectToRestConfigMapper) ActionConfigGetterOption {
105+
return func(getter *actionConfigGetter) {
106+
getter.objectToStorageRestConfig = f
107+
}
108+
}
109+
110+
func StorageDriverMapper(f ObjectToStorageDriverMapper) ActionConfigGetterOption {
111+
return func(getter *actionConfigGetter) {
112+
getter.objectToStorageDriver = f
113+
}
114+
}
115+
116+
// Deprecated: use StorageDriverMapper(DefaultSecretsStorageDriver(SecretsStorageDriverOpts)) instead.
83117
func StorageNamespaceMapper(m ObjectToStringMapper) ActionConfigGetterOption {
84118
return func(getter *actionConfigGetter) {
85119
getter.objectToStorageNamespace = m
86120
}
87121
}
88122

123+
// Deprecated: use StorageDriverMapper(DefaultSecretsStorageDriver(SecretsStorageDriverOpts)) instead.
89124
func DisableStorageOwnerRefInjection(v bool) ActionConfigGetterOption {
90125
return func(getter *actionConfigGetter) {
91126
getter.disableStorageOwnerRefInjection = v
92127
}
93128
}
94129

130+
// Deprecated: use ClientRestConfigMapper and StorageRestConfigMapper instead.
95131
func RestConfigMapper(f func(context.Context, client.Object, *rest.Config) (*rest.Config, error)) ActionConfigGetterOption {
96132
return func(getter *actionConfigGetter) {
97-
getter.objectToRestConfig = f
133+
getter.objectToClientRestConfig = f
134+
getter.objectToStorageRestConfig = f
98135
}
99136
}
100137

@@ -107,58 +144,53 @@ type actionConfigGetter struct {
107144
restMapper meta.RESTMapper
108145
discoveryClient discovery.CachedDiscoveryInterface
109146

110-
objectToClientNamespace ObjectToStringMapper
111-
objectToStorageNamespace ObjectToStringMapper
112-
objectToRestConfig func(context.Context, client.Object, *rest.Config) (*rest.Config, error)
147+
objectToClientRestConfig ObjectToRestConfigMapper
148+
objectToClientNamespace ObjectToStringMapper
149+
150+
objectToStorageRestConfig ObjectToRestConfigMapper
151+
objectToStorageDriver ObjectToStorageDriverMapper
152+
153+
// Deprecated: only keep around for backward compatibility with StorageNamespaceMapper option.
154+
objectToStorageNamespace ObjectToStringMapper
155+
// Deprecated: only keep around for backward compatibility with DisableStorageOwnerRefInjection option.
113156
disableStorageOwnerRefInjection bool
114157
}
115158

116159
func (acg *actionConfigGetter) ActionConfigFor(ctx context.Context, obj client.Object) (*action.Configuration, error) {
117-
storageNs, err := acg.objectToStorageNamespace(obj)
160+
clientRestConfig, err := acg.objectToClientRestConfig(ctx, obj, acg.baseRestConfig)
118161
if err != nil {
119-
return nil, fmt.Errorf("get storage namespace for object: %v", err)
120-
}
121-
122-
restConfig, err := acg.objectToRestConfig(ctx, obj, acg.baseRestConfig)
123-
if err != nil {
124-
return nil, fmt.Errorf("get rest config for object: %v", err)
162+
return nil, fmt.Errorf("get client rest config for object: %v", err)
125163
}
126164

127165
clientNamespace, err := acg.objectToClientNamespace(obj)
128166
if err != nil {
129167
return nil, fmt.Errorf("get client namespace for object: %v", err)
130168
}
131169

132-
rcg := newRESTClientGetter(restConfig, acg.restMapper, acg.discoveryClient, clientNamespace)
133-
kc := kube.New(rcg)
134-
kc.Namespace = clientNamespace
135-
136-
kcs, err := kc.Factory.KubernetesClientSet()
137-
if err != nil {
138-
return nil, fmt.Errorf("create kubernetes clientset: %v", err)
139-
}
170+
clientRCG := newRESTClientGetter(clientRestConfig, acg.restMapper, acg.discoveryClient, clientNamespace)
171+
clientKC := kube.New(clientRCG)
172+
clientKC.Namespace = clientNamespace
140173

141174
// Setup the debug log function that Helm will use
142175
debugLog := getDebugLogger(ctx)
143176

144-
secretClient := kcs.CoreV1().Secrets(storageNs)
145-
if !acg.disableStorageOwnerRefInjection {
146-
ownerRef := metav1.NewControllerRef(obj, obj.GetObjectKind().GroupVersionKind())
147-
secretClient = &ownerRefSecretClient{
148-
SecretInterface: secretClient,
149-
refs: []metav1.OwnerReference{*ownerRef},
150-
}
177+
storageRestConfig, err := acg.objectToStorageRestConfig(ctx, obj, acg.baseRestConfig)
178+
if err != nil {
179+
return nil, fmt.Errorf("get storage rest config for object: %v", err)
180+
}
181+
182+
d, err := acg.objectToStorageDriver(ctx, obj, storageRestConfig)
183+
if err != nil {
184+
return nil, fmt.Errorf("get storage driver for object: %v", err)
151185
}
152-
d := driver.NewSecrets(secretClient)
153-
d.Log = debugLog
154186

155187
// Initialize the storage backend
156188
s := storage.Init(d)
157189

158190
return &action.Configuration{
159-
RESTClientGetter: rcg,
191+
RESTClientGetter: clientRCG,
160192
Releases: s,
161-
KubeClient: kc,
193+
KubeClient: clientKC,
162194
Log: debugLog,
163195
}, nil
164196
}
@@ -177,15 +209,82 @@ var _ v1.SecretInterface = &ownerRefSecretClient{}
177209

178210
type ownerRefSecretClient struct {
179211
v1.SecretInterface
180-
refs []metav1.OwnerReference
212+
match func(secret *corev1.Secret) bool
213+
refs []metav1.OwnerReference
181214
}
182215

183216
func (c *ownerRefSecretClient) Create(ctx context.Context, in *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) {
184-
in.OwnerReferences = append(in.OwnerReferences, c.refs...)
217+
if c.match == nil || c.match(in) {
218+
in.OwnerReferences = append(in.OwnerReferences, c.refs...)
219+
}
185220
return c.SecretInterface.Create(ctx, in, opts)
186221
}
187222

188223
func (c *ownerRefSecretClient) Update(ctx context.Context, in *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
189-
in.OwnerReferences = append(in.OwnerReferences, c.refs...)
224+
if c.match == nil || c.match(in) {
225+
in.OwnerReferences = append(in.OwnerReferences, c.refs...)
226+
}
190227
return c.SecretInterface.Update(ctx, in, opts)
191228
}
229+
230+
type SecretsStorageDriverOpts struct {
231+
DisableOwnerRefInjection bool
232+
StorageNamespaceMapper ObjectToStringMapper
233+
}
234+
235+
func DefaultSecretsStorageDriver(opts SecretsStorageDriverOpts) ObjectToStorageDriverMapper {
236+
if opts.StorageNamespaceMapper == nil {
237+
opts.StorageNamespaceMapper = getObjectNamespace
238+
}
239+
return func(ctx context.Context, obj client.Object, restConfig *rest.Config) (driver.Driver, error) {
240+
storageNamespace, err := opts.StorageNamespaceMapper(obj)
241+
if err != nil {
242+
return nil, fmt.Errorf("get storage namespace for object: %v", err)
243+
}
244+
secretsInterface, err := v1.NewForConfig(restConfig)
245+
if err != nil {
246+
return nil, fmt.Errorf("create secrets client for storage: %v", err)
247+
}
248+
249+
secretClient := secretsInterface.Secrets(storageNamespace)
250+
if !opts.DisableOwnerRefInjection {
251+
ownerRef := metav1.NewControllerRef(obj, obj.GetObjectKind().GroupVersionKind())
252+
secretClient = &ownerRefSecretClient{
253+
SecretInterface: secretClient,
254+
refs: []metav1.OwnerReference{*ownerRef},
255+
}
256+
}
257+
d := driver.NewSecrets(secretClient)
258+
d.Log = getDebugLogger(ctx)
259+
return d, nil
260+
}
261+
}
262+
263+
func ChunkedSecretsStorageDriver(owner string, chunkSize int, opts SecretsStorageDriverOpts) ObjectToStorageDriverMapper {
264+
if opts.StorageNamespaceMapper == nil {
265+
opts.StorageNamespaceMapper = getObjectNamespace
266+
}
267+
return func(ctx context.Context, obj client.Object, restConfig *rest.Config) (driver.Driver, error) {
268+
storageNamespace, err := opts.StorageNamespaceMapper(obj)
269+
if err != nil {
270+
return nil, fmt.Errorf("get storage namespace for object: %v", err)
271+
}
272+
secretsInterface, err := v1.NewForConfig(restConfig)
273+
if err != nil {
274+
return nil, fmt.Errorf("create secrets client for storage: %v", err)
275+
}
276+
277+
secretClient := secretsInterface.Secrets(storageNamespace)
278+
if !opts.DisableOwnerRefInjection {
279+
ownerRef := metav1.NewControllerRef(obj, obj.GetObjectKind().GroupVersionKind())
280+
secretClient = &ownerRefSecretClient{
281+
SecretInterface: secretClient,
282+
match: func(secret *corev1.Secret) bool { return secret.Type == customstorage.SecretTypeChunkedIndex },
283+
refs: []metav1.OwnerReference{*ownerRef},
284+
}
285+
}
286+
d := customstorage.NewChunkedSecrets(secretClient, chunkSize, owner)
287+
d.Log = getDebugLogger(ctx)
288+
return d, nil
289+
}
290+
}

pkg/client/actionconfig_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ import (
2323

2424
. "github.com/onsi/ginkgo/v2"
2525
. "github.com/onsi/gomega"
26+
2627
"helm.sh/helm/v3/pkg/action"
2728
"helm.sh/helm/v3/pkg/kube"
29+
"helm.sh/helm/v3/pkg/release"
30+
"helm.sh/helm/v3/pkg/storage/driver"
2831
corev1 "k8s.io/api/core/v1"
2932
"k8s.io/apimachinery/pkg/api/meta"
3033
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -185,6 +188,31 @@ metadata:
185188
Expect(err).ToNot(HaveOccurred())
186189
Expect(ac2.RESTClientGetter.ToRESTConfig()).To(WithTransform(func(c *rest.Config) string { return c.BearerToken }, Equal("test2")))
187190
})
191+
192+
It("should use a custom storage driver", func() {
193+
storageDriver := driver.NewMemory()
194+
195+
storageDriverMapper := func(ctx context.Context, obj client.Object, cfg *rest.Config) (driver.Driver, error) {
196+
return storageDriver, nil
197+
}
198+
acg, err := NewActionConfigGetter(cfg, rm, StorageDriverMapper(storageDriverMapper))
199+
Expect(err).ToNot(HaveOccurred())
200+
201+
testObject := func(name string) client.Object {
202+
u := unstructured.Unstructured{}
203+
u.SetName(name)
204+
return &u
205+
}
206+
207+
ac, err := acg.ActionConfigFor(context.Background(), testObject("test1"))
208+
Expect(err).ToNot(HaveOccurred())
209+
210+
expected := &release.Release{Name: "test1", Version: 2, Info: &release.Info{Status: release.StatusDeployed}}
211+
Expect(ac.Releases.Create(expected)).To(Succeed())
212+
actual, err := ac.Releases.Get(expected.Name, expected.Version)
213+
Expect(err).ToNot(HaveOccurred())
214+
Expect(actual).To(Equal(expected))
215+
})
188216
})
189217
})
190218

0 commit comments

Comments
 (0)