Skip to content

Commit 41f17ea

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 41f17ea

File tree

6 files changed

+994
-37
lines changed

6 files changed

+994
-37
lines changed

pkg/client/actionconfig.go

Lines changed: 105 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,25 @@ func NewActionConfigGetter(baseRestConfig *rest.Config, rm meta.RESTMapper, opts
5757
if acg.objectToClientNamespace == nil {
5858
acg.objectToClientNamespace = getObjectNamespace
5959
}
60-
if acg.objectToStorageNamespace == nil {
61-
acg.objectToStorageNamespace = getObjectNamespace
60+
if acg.objectToClientRestConfig == nil {
61+
acg.objectToClientRestConfig = func(_ context.Context, _ client.Object, baseRestConfig *rest.Config) (*rest.Config, error) {
62+
return rest.CopyConfig(baseRestConfig), nil
63+
}
6264
}
63-
if acg.objectToRestConfig == nil {
64-
acg.objectToRestConfig = func(_ context.Context, _ client.Object, baseRestConfig *rest.Config) (*rest.Config, error) {
65+
if acg.objectToStorageRestConfig == nil {
66+
acg.objectToStorageRestConfig = func(_ context.Context, _ client.Object, baseRestConfig *rest.Config) (*rest.Config, error) {
6567
return rest.CopyConfig(baseRestConfig), nil
6668
}
6769
}
70+
if acg.objectToStorageDriver == nil {
71+
if acg.objectToStorageNamespace == nil {
72+
acg.objectToStorageNamespace = getObjectNamespace
73+
}
74+
acg.objectToStorageDriver = DefaultSecretsStorageDriver(SecretsStorageDriverOpts{
75+
DisableOwnerRefInjection: acg.disableStorageOwnerRefInjection,
76+
StorageNamespaceMapper: acg.objectToStorageNamespace,
77+
})
78+
}
6879
return acg, nil
6980
}
7081

@@ -73,28 +84,52 @@ var _ ActionConfigGetter = &actionConfigGetter{}
7384
type ActionConfigGetterOption func(getter *actionConfigGetter)
7485

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

7796
func ClientNamespaceMapper(m ObjectToStringMapper) ActionConfigGetterOption { // nolint:revive
7897
return func(getter *actionConfigGetter) {
7998
getter.objectToClientNamespace = m
8099
}
81100
}
82101

102+
func StorageRestConfigMapper(f ObjectToRestConfigMapper) ActionConfigGetterOption {
103+
return func(getter *actionConfigGetter) {
104+
getter.objectToStorageRestConfig = f
105+
}
106+
}
107+
108+
func StorageDriverMapper(f ObjectToStorageDriverMapper) ActionConfigGetterOption {
109+
return func(getter *actionConfigGetter) {
110+
getter.objectToStorageDriver = f
111+
}
112+
}
113+
114+
// Deprecated: use StorageDriverMapper(DefaultSecretsStorageDriver(SecretsStorageDriverOpts)) instead.
83115
func StorageNamespaceMapper(m ObjectToStringMapper) ActionConfigGetterOption {
84116
return func(getter *actionConfigGetter) {
85117
getter.objectToStorageNamespace = m
86118
}
87119
}
88120

121+
// Deprecated: use StorageDriverMapper(DefaultSecretsStorageDriver(SecretsStorageDriverOpts)) instead.
89122
func DisableStorageOwnerRefInjection(v bool) ActionConfigGetterOption {
90123
return func(getter *actionConfigGetter) {
91124
getter.disableStorageOwnerRefInjection = v
92125
}
93126
}
94127

128+
// Deprecated: use ClientRestConfigMapper and StorageRestConfigMapper instead.
95129
func RestConfigMapper(f func(context.Context, client.Object, *rest.Config) (*rest.Config, error)) ActionConfigGetterOption {
96130
return func(getter *actionConfigGetter) {
97-
getter.objectToRestConfig = f
131+
getter.objectToClientRestConfig = f
132+
getter.objectToStorageRestConfig = f
98133
}
99134
}
100135

@@ -107,58 +142,53 @@ type actionConfigGetter struct {
107142
restMapper meta.RESTMapper
108143
discoveryClient discovery.CachedDiscoveryInterface
109144

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

116157
func (acg *actionConfigGetter) ActionConfigFor(ctx context.Context, obj client.Object) (*action.Configuration, error) {
117-
storageNs, err := acg.objectToStorageNamespace(obj)
118-
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)
158+
clientRestConfig, err := acg.objectToClientRestConfig(ctx, obj, acg.baseRestConfig)
123159
if err != nil {
124-
return nil, fmt.Errorf("get rest config for object: %v", err)
160+
return nil, fmt.Errorf("get client rest config for object: %v", err)
125161
}
126162

127163
clientNamespace, err := acg.objectToClientNamespace(obj)
128164
if err != nil {
129165
return nil, fmt.Errorf("get client namespace for object: %v", err)
130166
}
131167

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-
}
168+
clientRCG := newRESTClientGetter(clientRestConfig, acg.restMapper, acg.discoveryClient, clientNamespace)
169+
clientKC := kube.New(clientRCG)
170+
clientKC.Namespace = clientNamespace
140171

141172
// Setup the debug log function that Helm will use
142173
debugLog := getDebugLogger(ctx)
143174

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-
}
175+
storageRestConfig, err := acg.objectToStorageRestConfig(ctx, obj, acg.baseRestConfig)
176+
if err != nil {
177+
return nil, fmt.Errorf("get storage rest config for object: %v", err)
178+
}
179+
180+
d, err := acg.objectToStorageDriver(ctx, obj, storageRestConfig)
181+
if err != nil {
182+
return nil, fmt.Errorf("get storage driver for object: %v", err)
151183
}
152-
d := driver.NewSecrets(secretClient)
153-
d.Log = debugLog
154184

155185
// Initialize the storage backend
156186
s := storage.Init(d)
157187

158188
return &action.Configuration{
159-
RESTClientGetter: rcg,
189+
RESTClientGetter: clientRCG,
160190
Releases: s,
161-
KubeClient: kc,
191+
KubeClient: clientKC,
162192
Log: debugLog,
163193
}, nil
164194
}
@@ -177,15 +207,53 @@ var _ v1.SecretInterface = &ownerRefSecretClient{}
177207

178208
type ownerRefSecretClient struct {
179209
v1.SecretInterface
180-
refs []metav1.OwnerReference
210+
match func(secret *corev1.Secret) bool
211+
refs []metav1.OwnerReference
181212
}
182213

183214
func (c *ownerRefSecretClient) Create(ctx context.Context, in *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) {
184-
in.OwnerReferences = append(in.OwnerReferences, c.refs...)
215+
if c.match == nil || c.match(in) {
216+
in.OwnerReferences = append(in.OwnerReferences, c.refs...)
217+
}
185218
return c.SecretInterface.Create(ctx, in, opts)
186219
}
187220

188221
func (c *ownerRefSecretClient) Update(ctx context.Context, in *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
189-
in.OwnerReferences = append(in.OwnerReferences, c.refs...)
222+
if c.match == nil || c.match(in) {
223+
in.OwnerReferences = append(in.OwnerReferences, c.refs...)
224+
}
190225
return c.SecretInterface.Update(ctx, in, opts)
191226
}
227+
228+
type SecretsStorageDriverOpts struct {
229+
DisableOwnerRefInjection bool
230+
StorageNamespaceMapper ObjectToStringMapper
231+
}
232+
233+
func DefaultSecretsStorageDriver(opts SecretsStorageDriverOpts) ObjectToStorageDriverMapper {
234+
if opts.StorageNamespaceMapper == nil {
235+
opts.StorageNamespaceMapper = getObjectNamespace
236+
}
237+
return func(ctx context.Context, obj client.Object, restConfig *rest.Config) (driver.Driver, error) {
238+
storageNamespace, err := opts.StorageNamespaceMapper(obj)
239+
if err != nil {
240+
return nil, fmt.Errorf("get storage namespace for object: %v", err)
241+
}
242+
secretsInterface, err := v1.NewForConfig(restConfig)
243+
if err != nil {
244+
return nil, fmt.Errorf("create secrets client for storage: %v", err)
245+
}
246+
247+
secretClient := secretsInterface.Secrets(storageNamespace)
248+
if !opts.DisableOwnerRefInjection {
249+
ownerRef := metav1.NewControllerRef(obj, obj.GetObjectKind().GroupVersionKind())
250+
secretClient = &ownerRefSecretClient{
251+
SecretInterface: secretClient,
252+
refs: []metav1.OwnerReference{*ownerRef},
253+
}
254+
}
255+
d := driver.NewSecrets(secretClient)
256+
d.Log = getDebugLogger(ctx)
257+
return d, nil
258+
}
259+
}

pkg/client/actionconfig_test.go

Lines changed: 29 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,32 @@ 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 := storageDriver.List(func(r *release.Release) bool { return true })
213+
Expect(err).ToNot(HaveOccurred())
214+
Expect(actual).To(HaveLen(1))
215+
Expect(actual[0]).To(Equal(expected))
216+
})
188217
})
189218
})
190219

0 commit comments

Comments
 (0)