1
16
17 package action
18
19 import (
20 "bytes"
21 "fmt"
22 "os"
23 "path"
24 "path/filepath"
25 "regexp"
26 "strings"
27
28 "github.com/pkg/errors"
29 "k8s.io/apimachinery/pkg/api/meta"
30 "k8s.io/cli-runtime/pkg/genericclioptions"
31 "k8s.io/client-go/discovery"
32 "k8s.io/client-go/kubernetes"
33 "k8s.io/client-go/rest"
34
35 "helm.sh/helm/v3/pkg/chart"
36 "helm.sh/helm/v3/pkg/chartutil"
37 "helm.sh/helm/v3/pkg/engine"
38 "helm.sh/helm/v3/pkg/kube"
39 "helm.sh/helm/v3/pkg/postrender"
40 "helm.sh/helm/v3/pkg/registry"
41 "helm.sh/helm/v3/pkg/release"
42 "helm.sh/helm/v3/pkg/releaseutil"
43 "helm.sh/helm/v3/pkg/storage"
44 "helm.sh/helm/v3/pkg/storage/driver"
45 "helm.sh/helm/v3/pkg/time"
46 )
47
48
49
50
51
52 var Timestamper = time.Now
53
54 var (
55
56 errMissingChart = errors.New("no chart provided")
57
58 errMissingRelease = errors.New("no release provided")
59
60 errInvalidRevision = errors.New("invalid release revision")
61
62 errPending = errors.New("another operation (install/upgrade/rollback) is in progress")
63 )
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 var ValidName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
79
80
81 type Configuration struct {
82
83 RESTClientGetter RESTClientGetter
84
85
86 Releases *storage.Storage
87
88
89 KubeClient kube.Interface
90
91
92 RegistryClient *registry.Client
93
94
95 Capabilities *chartutil.Capabilities
96
97 Log func(string, ...interface{})
98 }
99
100
101
102
103
104
105
106 func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, interactWithRemote, enableDNS, hideSecret bool) ([]*release.Hook, *bytes.Buffer, string, error) {
107 hs := []*release.Hook{}
108 b := bytes.NewBuffer(nil)
109
110 caps, err := cfg.getCapabilities()
111 if err != nil {
112 return hs, b, "", err
113 }
114
115 if ch.Metadata.KubeVersion != "" {
116 if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) {
117 return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String())
118 }
119 }
120
121 var files map[string]string
122 var err2 error
123
124
125
126
127 if interactWithRemote && cfg.RESTClientGetter != nil {
128 restConfig, err := cfg.RESTClientGetter.ToRESTConfig()
129 if err != nil {
130 return hs, b, "", err
131 }
132 e := engine.New(restConfig)
133 e.EnableDNS = enableDNS
134 files, err2 = e.Render(ch, values)
135 } else {
136 var e engine.Engine
137 e.EnableDNS = enableDNS
138 files, err2 = e.Render(ch, values)
139 }
140
141 if err2 != nil {
142 return hs, b, "", err2
143 }
144
145
146
147
148
149
150 var notesBuffer bytes.Buffer
151 for k, v := range files {
152 if strings.HasSuffix(k, notesFileSuffix) {
153 if subNotes || (k == path.Join(ch.Name(), "templates", notesFileSuffix)) {
154
155 if notesBuffer.Len() > 0 {
156 notesBuffer.WriteString("\n")
157 }
158 notesBuffer.WriteString(v)
159 }
160 delete(files, k)
161 }
162 }
163 notes := notesBuffer.String()
164
165
166
167
168 hs, manifests, err := releaseutil.SortManifests(files, caps.APIVersions, releaseutil.InstallOrder)
169 if err != nil {
170
171
172
173
174
175 for name, content := range files {
176 if strings.TrimSpace(content) == "" {
177 continue
178 }
179 fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content)
180 }
181 return hs, b, "", err
182 }
183
184
185 fileWritten := make(map[string]bool)
186
187 if includeCrds {
188 for _, crd := range ch.CRDObjects() {
189 if outputDir == "" {
190 fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Filename, string(crd.File.Data[:]))
191 } else {
192 err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Filename])
193 if err != nil {
194 return hs, b, "", err
195 }
196 fileWritten[crd.Filename] = true
197 }
198 }
199 }
200
201 for _, m := range manifests {
202 if outputDir == "" {
203 if hideSecret && m.Head.Kind == "Secret" && m.Head.Version == "v1" {
204 fmt.Fprintf(b, "---\n# Source: %s\n# HIDDEN: The Secret output has been suppressed\n", m.Name)
205 } else {
206 fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content)
207 }
208 } else {
209 newDir := outputDir
210 if useReleaseName {
211 newDir = filepath.Join(outputDir, releaseName)
212 }
213
214
215
216
217 err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name])
218 if err != nil {
219 return hs, b, "", err
220 }
221 fileWritten[m.Name] = true
222 }
223 }
224
225 if pr != nil {
226 b, err = pr.Run(b)
227 if err != nil {
228 return hs, b, notes, errors.Wrap(err, "error while running post render on files")
229 }
230 }
231
232 return hs, b, notes, nil
233 }
234
235
236 type RESTClientGetter interface {
237 ToRESTConfig() (*rest.Config, error)
238 ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error)
239 ToRESTMapper() (meta.RESTMapper, error)
240 }
241
242
243 type DebugLog func(format string, v ...interface{})
244
245
246 func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
247 if cfg.Capabilities != nil {
248 return cfg.Capabilities, nil
249 }
250 dc, err := cfg.RESTClientGetter.ToDiscoveryClient()
251 if err != nil {
252 return nil, errors.Wrap(err, "could not get Kubernetes discovery client")
253 }
254
255 dc.Invalidate()
256 kubeVersion, err := dc.ServerVersion()
257 if err != nil {
258 return nil, errors.Wrap(err, "could not get server version from Kubernetes")
259 }
260
261
262
263
264
265 apiVersions, err := GetVersionSet(dc)
266 if err != nil {
267 if discovery.IsGroupDiscoveryFailedError(err) {
268 cfg.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err)
269 cfg.Log("WARNING: To fix this, kubectl delete apiservice <service-name>")
270 } else {
271 return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes")
272 }
273 }
274
275 cfg.Capabilities = &chartutil.Capabilities{
276 APIVersions: apiVersions,
277 KubeVersion: chartutil.KubeVersion{
278 Version: kubeVersion.GitVersion,
279 Major: kubeVersion.Major,
280 Minor: kubeVersion.Minor,
281 },
282 HelmVersion: chartutil.DefaultCapabilities.HelmVersion,
283 }
284 return cfg.Capabilities, nil
285 }
286
287
288 func (cfg *Configuration) KubernetesClientSet() (kubernetes.Interface, error) {
289 conf, err := cfg.RESTClientGetter.ToRESTConfig()
290 if err != nil {
291 return nil, errors.Wrap(err, "unable to generate config for kubernetes client")
292 }
293
294 return kubernetes.NewForConfig(conf)
295 }
296
297
298
299
300
301 func (cfg *Configuration) Now() time.Time {
302 return Timestamper()
303 }
304
305 func (cfg *Configuration) releaseContent(name string, version int) (*release.Release, error) {
306 if err := chartutil.ValidateReleaseName(name); err != nil {
307 return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name)
308 }
309
310 if version <= 0 {
311 return cfg.Releases.Last(name)
312 }
313
314 return cfg.Releases.Get(name, version)
315 }
316
317
318 func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.VersionSet, error) {
319 groups, resources, err := client.ServerGroupsAndResources()
320 if err != nil && !discovery.IsGroupDiscoveryFailedError(err) {
321 return chartutil.DefaultVersionSet, errors.Wrap(err, "could not get apiVersions from Kubernetes")
322 }
323
324
325
326
327
328 if len(groups) == 0 && len(resources) == 0 {
329 return chartutil.DefaultVersionSet, nil
330 }
331
332 versionMap := make(map[string]interface{})
333 versions := []string{}
334
335
336 for _, g := range groups {
337 for _, gv := range g.Versions {
338 versionMap[gv.GroupVersion] = struct{}{}
339 }
340 }
341
342
343 var id string
344 var ok bool
345 for _, r := range resources {
346 for _, rl := range r.APIResources {
347
348
349
350 id = path.Join(r.GroupVersion, rl.Kind)
351 if _, ok = versionMap[id]; !ok {
352 versionMap[id] = struct{}{}
353 }
354 }
355 }
356
357
358 for k := range versionMap {
359 versions = append(versions, k)
360 }
361
362 return chartutil.VersionSet(versions), nil
363 }
364
365
366 func (cfg *Configuration) recordRelease(r *release.Release) {
367 if err := cfg.Releases.Update(r); err != nil {
368 cfg.Log("warning: Failed to update release %s: %s", r.Name, err)
369 }
370 }
371
372
373 func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error {
374 kc := kube.New(getter)
375 kc.Log = log
376
377 lazyClient := &lazyClient{
378 namespace: namespace,
379 clientFn: kc.Factory.KubernetesClientSet,
380 }
381
382 var store *storage.Storage
383 switch helmDriver {
384 case "secret", "secrets", "":
385 d := driver.NewSecrets(newSecretClient(lazyClient))
386 d.Log = log
387 store = storage.Init(d)
388 case "configmap", "configmaps":
389 d := driver.NewConfigMaps(newConfigMapClient(lazyClient))
390 d.Log = log
391 store = storage.Init(d)
392 case "memory":
393 var d *driver.Memory
394 if cfg.Releases != nil {
395 if mem, ok := cfg.Releases.Driver.(*driver.Memory); ok {
396
397
398
399 d = mem
400 }
401 }
402 if d == nil {
403 d = driver.NewMemory()
404 }
405 d.SetNamespace(namespace)
406 store = storage.Init(d)
407 case "sql":
408 d, err := driver.NewSQL(
409 os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"),
410 log,
411 namespace,
412 )
413 if err != nil {
414 panic(fmt.Sprintf("Unable to instantiate SQL driver: %v", err))
415 }
416 store = storage.Init(d)
417 default:
418
419 panic("Unknown driver in HELM_DRIVER: " + helmDriver)
420 }
421
422 cfg.RESTClientGetter = getter
423 cfg.KubeClient = kc
424 cfg.Releases = store
425 cfg.Log = log
426
427 return nil
428 }
429
View as plain text