Skip to content

Commit 4009fb6

Browse files
authored
Merge pull request #659 from fluxcd/flag-persistent-client
2 parents 6f0fd5f + c93b3af commit 4009fb6

File tree

8 files changed

+246
-30
lines changed

8 files changed

+246
-30
lines changed

api/v2beta1/helmrelease_types.go

+24
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,21 @@ type HelmReleaseSpec struct {
137137
// +optional
138138
ServiceAccountName string `json:"serviceAccountName,omitempty"`
139139

140+
// PersistentClient tells the controller to use a persistent Kubernetes
141+
// client for this release. When enabled, the client will be reused for the
142+
// duration of the reconciliation, instead of being created and destroyed
143+
// for each (step of a) Helm action.
144+
//
145+
// This can improve performance, but may cause issues with some Helm charts
146+
// that for example do create Custom Resource Definitions during installation
147+
// outside Helm's CRD lifecycle hooks, which are then not observed to be
148+
// available by e.g. post-install hooks.
149+
//
150+
// If not set, it defaults to true.
151+
//
152+
// +optional
153+
PersistentClient *bool `json:"persistentClient,omitempty"`
154+
140155
// Install holds the configuration for Helm install actions for this HelmRelease.
141156
// +optional
142157
Install *Install `json:"install,omitempty"`
@@ -1039,6 +1054,15 @@ func (in HelmRelease) GetMaxHistory() int {
10391054
return *in.Spec.MaxHistory
10401055
}
10411056

1057+
// UsePersistentClient returns the configured PersistentClient, or the default
1058+
// of true.
1059+
func (in HelmRelease) UsePersistentClient() bool {
1060+
if in.Spec.PersistentClient == nil {
1061+
return true
1062+
}
1063+
return *in.Spec.PersistentClient
1064+
}
1065+
10421066
// GetDependsOn returns the list of dependencies across-namespaces.
10431067
func (in HelmRelease) GetDependsOn() []meta.NamespacedObjectReference {
10441068
return in.Spec.DependsOn

api/v2beta1/zz_generated.deepcopy.go

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml

+11
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,17 @@ spec:
322322
this HelmRelease. Use '0' for an unlimited number of revisions;
323323
defaults to '10'.
324324
type: integer
325+
persistentClient:
326+
description: "PersistentClient tells the controller to use a persistent
327+
Kubernetes client for this release. When enabled, the client will
328+
be reused for the duration of the reconciliation, instead of being
329+
created and destroyed for each (step of a) Helm action. \n This
330+
can improve performance, but may cause issues with some Helm charts
331+
that for example do create Custom Resource Definitions during installation
332+
outside Helm's CRD lifecycle hooks, which are then not observed
333+
to be available by e.g. post-install hooks. \n If not set, it defaults
334+
to true."
335+
type: boolean
325336
postRenderers:
326337
description: PostRenderers holds an array of Helm PostRenderers, which
327338
will be applied in order of their definition.

docs/api/helmrelease.md

+40
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,26 @@ when reconciling this HelmRelease.</p>
226226
</tr>
227227
<tr>
228228
<td>
229+
<code>persistentClient</code><br>
230+
<em>
231+
bool
232+
</em>
233+
</td>
234+
<td>
235+
<em>(Optional)</em>
236+
<p>PersistentClient tells the controller to use a persistent Kubernetes
237+
client for this release. When enabled, the client will be reused for the
238+
duration of the reconciliation, instead of being created and destroyed
239+
for each (step of a) Helm action.</p>
240+
<p>This can improve performance, but may cause issues with some Helm charts
241+
that for example do create Custom Resource Definitions during installation
242+
outside Helm&rsquo;s CRD lifecycle hooks, which are then not observed to be
243+
available by e.g. post-install hooks.</p>
244+
<p>If not set, it defaults to true.</p>
245+
</td>
246+
</tr>
247+
<tr>
248+
<td>
229249
<code>install</code><br>
230250
<em>
231251
<a href="#helm.toolkit.fluxcd.io/v2beta1.Install">
@@ -1015,6 +1035,26 @@ when reconciling this HelmRelease.</p>
10151035
</tr>
10161036
<tr>
10171037
<td>
1038+
<code>persistentClient</code><br>
1039+
<em>
1040+
bool
1041+
</em>
1042+
</td>
1043+
<td>
1044+
<em>(Optional)</em>
1045+
<p>PersistentClient tells the controller to use a persistent Kubernetes
1046+
client for this release. When enabled, the client will be reused for the
1047+
duration of the reconciliation, instead of being created and destroyed
1048+
for each (step of a) Helm action.</p>
1049+
<p>This can improve performance, but may cause issues with some Helm charts
1050+
that for example do create Custom Resource Definitions during installation
1051+
outside Helm&rsquo;s CRD lifecycle hooks, which are then not observed to be
1052+
available by e.g. post-install hooks.</p>
1053+
<p>If not set, it defaults to true.</p>
1054+
</td>
1055+
</tr>
1056+
<tr>
1057+
<td>
10181058
<code>install</code><br>
10191059
<em>
10201060
<a href="#helm.toolkit.fluxcd.io/v2beta1.Install">

docs/spec/v2beta1/helmreleases.md

+15
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ type HelmReleaseSpec struct {
6666
// +optional
6767
MaxHistory *int `json:"maxHistory,omitempty"`
6868

69+
// PersistentClient tells the controller to use a persistent Kubernetes
70+
// client for this release. When enabled, the client will be reused for the
71+
// duration of the reconciliation, instead of being created and destroyed
72+
// for each (step of a) Helm action.
73+
//
74+
// This can improve performance, but may cause issues with some Helm charts
75+
// that for example do create Custom Resource Definitions during installation
76+
// outside Helm's CRD lifecycle hooks, which are then not observed to be
77+
// available by e.g. post-install hooks.
78+
//
79+
// If not set, it defaults to true.
80+
//
81+
// +optional
82+
PersistentClient *bool `json:"persistentClient,omitempty"`
83+
6984
// Install holds the configuration for Helm install actions for this HelmRelease.
7085
// +optional
7186
Install *Install `json:"install,omitempty"`

internal/controllers/helmrelease_controller.go

+1
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, hr v2
528528
// When ServiceAccountName is empty, it will fall back to the configured default.
529529
// If this is not configured either, this option will result in a no-op.
530530
kube.WithImpersonate(hr.Spec.ServiceAccountName, hr.GetNamespace()),
531+
kube.WithPersistent(hr.UsePersistentClient()),
531532
}
532533
if hr.Spec.KubeConfig != nil {
533534
secretName := types.NamespacedName{

internal/kube/client.go

+80-27
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,27 @@ func WithClientOptions(opts client.Options) Option {
6161
}
6262
}
6363

64+
// WithPersistent sets whether the client should persist the underlying client
65+
// config, REST mapper, and discovery client.
66+
func WithPersistent(persist bool) Option {
67+
return func(c *MemoryRESTClientGetter) {
68+
c.persistent = persist
69+
}
70+
}
71+
6472
// MemoryRESTClientGetter is a resource.RESTClientGetter that uses an
65-
// in-memory REST config, REST mapper, and discovery client. The REST config,
66-
// REST mapper, and discovery client are lazily initialized, and cached for
67-
// subsequent calls.
73+
// in-memory REST config, REST mapper, and discovery client.
74+
// If configured, the client config, REST mapper, and discovery client are
75+
// lazily initialized, and cached for subsequent calls.
6876
type MemoryRESTClientGetter struct {
6977
// namespace is the namespace to use for the client.
7078
namespace string
7179
// impersonate is the username to use for the client.
7280
impersonate string
81+
// persistent indicates whether the client should persist the restMapper,
82+
// clientCfg, and discoveryClient. Rather than re-initializing them on
83+
// every call, they will be cached and reused.
84+
persistent bool
7385

7486
cfg *rest.Config
7587

@@ -124,59 +136,100 @@ func (c *MemoryRESTClientGetter) ToRESTConfig() (*rest.Config, error) {
124136
// ToDiscoveryClient returns a memory cached discovery client. Calling it
125137
// multiple times will return the same instance.
126138
func (c *MemoryRESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
127-
c.clientCfgMu.Lock()
128-
defer c.clientCfgMu.Unlock()
139+
if c.persistent {
140+
return c.toPersistentDiscoveryClient()
141+
}
142+
return c.toDiscoveryClient()
143+
}
129144

130-
if c.discoveryClient == nil {
131-
config, err := c.ToRESTConfig()
132-
if err != nil {
133-
return nil, err
134-
}
145+
func (c *MemoryRESTClientGetter) toPersistentDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
146+
c.discoveryMu.Lock()
147+
defer c.discoveryMu.Unlock()
135148

136-
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
149+
if c.discoveryClient == nil {
150+
discoveryClient, err := c.toDiscoveryClient()
137151
if err != nil {
138152
return nil, err
139153
}
140-
c.discoveryClient = memory.NewMemCacheClient(discoveryClient)
154+
c.discoveryClient = discoveryClient
141155
}
142156
return c.discoveryClient, nil
143157
}
144158

159+
func (c *MemoryRESTClientGetter) toDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
160+
config, err := c.ToRESTConfig()
161+
if err != nil {
162+
return nil, err
163+
}
164+
165+
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
166+
if err != nil {
167+
return nil, err
168+
}
169+
return memory.NewMemCacheClient(discoveryClient), nil
170+
}
171+
145172
// ToRESTMapper returns a meta.RESTMapper using the discovery client. Calling
146173
// it multiple times will return the same instance.
147174
func (c *MemoryRESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) {
148-
c.discoveryMu.Lock()
149-
defer c.discoveryMu.Unlock()
175+
if c.persistent {
176+
return c.toPersistentRESTMapper()
177+
}
178+
return c.toRESTMapper()
179+
}
180+
181+
func (c *MemoryRESTClientGetter) toPersistentRESTMapper() (meta.RESTMapper, error) {
182+
c.restMapperMu.Lock()
183+
defer c.restMapperMu.Unlock()
150184

151185
if c.restMapper == nil {
152-
discoveryClient, err := c.ToDiscoveryClient()
186+
restMapper, err := c.toRESTMapper()
153187
if err != nil {
154188
return nil, err
155189
}
156-
mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
157-
c.restMapper = restmapper.NewShortcutExpander(mapper, discoveryClient)
190+
c.restMapper = restMapper
158191
}
159192
return c.restMapper, nil
160193
}
161194

195+
func (c *MemoryRESTClientGetter) toRESTMapper() (meta.RESTMapper, error) {
196+
discoveryClient, err := c.ToDiscoveryClient()
197+
if err != nil {
198+
return nil, err
199+
}
200+
mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
201+
return restmapper.NewShortcutExpander(mapper, discoveryClient), nil
202+
}
203+
162204
// ToRawKubeConfigLoader returns a clientcmd.ClientConfig using
163205
// clientcmd.DefaultClientConfig. With clientcmd.ClusterDefaults, namespace, and
164206
// impersonate configured as overwrites.
165207
func (c *MemoryRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {
208+
if c.persistent {
209+
return c.toPersistentRawKubeConfigLoader()
210+
}
211+
return c.toRawKubeConfigLoader()
212+
}
213+
214+
func (c *MemoryRESTClientGetter) toPersistentRawKubeConfigLoader() clientcmd.ClientConfig {
166215
c.clientCfgMu.Lock()
167216
defer c.clientCfgMu.Unlock()
168217

169218
if c.clientCfg == nil {
170-
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
171-
// use the standard defaults for this client command
172-
// DEPRECATED: remove and replace with something more accurate
173-
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
174-
175-
overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
176-
overrides.Context.Namespace = c.namespace
177-
overrides.AuthInfo.Impersonate = c.impersonate
178-
179-
c.clientCfg = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
219+
c.clientCfg = c.toRawKubeConfigLoader()
180220
}
181221
return c.clientCfg
182222
}
223+
224+
func (c *MemoryRESTClientGetter) toRawKubeConfigLoader() clientcmd.ClientConfig {
225+
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
226+
// use the standard defaults for this client command
227+
// DEPRECATED: remove and replace with something more accurate
228+
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
229+
230+
overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
231+
overrides.Context.Namespace = c.namespace
232+
overrides.AuthInfo.Impersonate = c.impersonate
233+
234+
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
235+
}

0 commit comments

Comments
 (0)