-
Notifications
You must be signed in to change notification settings - Fork 88
/
helm_v3.go
194 lines (168 loc) · 5.57 KB
/
helm_v3.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package base
import (
"bytes"
"fmt"
"os"
"regexp"
"sort"
"strings"
"time"
"github.com/pkg/errors"
"github.com/replicatedhq/kots/pkg/util"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
rspb "helm.sh/helm/v3/pkg/release"
helmtime "helm.sh/helm/v3/pkg/time"
k8syaml "sigs.k8s.io/yaml"
)
var (
HelmV3ManifestNameRegex = regexp.MustCompile("^# Source: (.+)")
)
const NamespaceTemplateConst = "repl{{ Namespace}}"
func renderHelmV3(chartName string, chartPath string, vals map[string]interface{}, renderOptions *RenderOptions) ([]BaseFile, []BaseFile, error) {
cfg := &action.Configuration{
Log: renderOptions.Log.Debug,
}
client := action.NewInstall(cfg)
client.DryRun = true
client.ReleaseName = chartName
client.Replace = true
client.ClientOnly = true
client.IncludeCRDs = true
client.Namespace = renderOptions.Namespace
if client.Namespace == "" {
client.Namespace = NamespaceTemplateConst
}
chartRequested, err := loader.Load(chartPath)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to load chart")
}
if req := chartRequested.Metadata.Dependencies; req != nil {
if err := action.CheckDependencies(chartRequested, req); err != nil {
return nil, nil, errors.Wrap(err, "failed dependency check")
}
}
rel, err := client.Run(chartRequested, vals)
if err != nil {
return nil, nil, util.ActionableError{
NoRetry: true,
Message: fmt.Sprintf("helm v3 render failed with error: %v", err),
}
}
coalescedValues, err := chartutil.CoalesceValues(rel.Chart, rel.Config)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to coalesce values")
}
valuesContent, err := k8syaml.Marshal(coalescedValues)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to marshal rendered values")
}
var manifests bytes.Buffer
fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest))
for _, m := range rel.Hooks {
fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest)
}
baseFiles := []BaseFile{}
additionalFiles := []BaseFile{
{
Path: "values.yaml",
Content: valuesContent,
},
}
splitManifests := splitManifests(manifests.String())
manifestName := ""
for _, manifest := range splitManifests {
submatch := HelmV3ManifestNameRegex.FindStringSubmatch(manifest)
if len(submatch) > 0 {
// multi-doc manifests will not have the Source comment so use the previous name
manifestName = strings.TrimPrefix(submatch[1], fmt.Sprintf("%s/", chartName))
}
if manifestName == "" {
// if the manifest name is empty im not sure what to do with the doc
continue
} else if strings.TrimSpace(manifest) == "" {
// filter out empty docs
continue
}
baseFiles = append(baseFiles, BaseFile{
Path: manifestName,
Content: []byte(manifest),
})
}
// Don't change the classic style rendering ie, picking all the files within charts, subdirs
// and do a single apply. This will not work for Native helm expects uniquely named image pullsecrets.
// helm maintains strict ownership of secretnames for each subcharts to add Release metadata for each chart
if !renderOptions.UseHelmInstall {
baseFiles = removeCommonPrefix(baseFiles)
}
// this secret should only be generated for installs that rely on us rendering yaml internally - not native helm installs
// those generate their own secret
if !renderOptions.UseHelmInstall {
if renderOptions.Namespace == "" {
rel.Namespace = namespace()
}
rel.Info.Status = rspb.StatusDeployed
// override deployed times to avoid spurious diffs
rel.Info.FirstDeployed, _ = helmtime.Parse(time.RFC3339, "1970-01-01T01:00:00")
rel.Info.LastDeployed, _ = helmtime.Parse(time.RFC3339, "1970-01-01T01:00:00")
helmReleaseSecretObj, err := newSecretsObject(rel)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to generate helm secret")
}
renderedSecret, err := k8syaml.Marshal(helmReleaseSecretObj)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to generate helm secret")
}
baseFiles = append(baseFiles, BaseFile{
Path: "chartHelmSecret.yaml",
Content: renderedSecret,
})
}
// insert namespace defined in the HelmChart spec
baseFiles, err = kustomizeHelmNamespace(baseFiles, renderOptions)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to insert helm namespace")
}
// ensure order
merged := mergeBaseFiles(baseFiles)
sort.Slice(merged, func(i, j int) bool {
return 0 > strings.Compare(merged[i].Path, merged[j].Path)
})
return merged, additionalFiles, nil
}
func mergeBaseFiles(baseFiles []BaseFile) []BaseFile {
merged := []BaseFile{}
found := map[string]int{}
for _, baseFile := range baseFiles {
index, ok := found[baseFile.Path]
if ok {
merged[index].Content = append(merged[index].Content, []byte("\n---\n")...)
merged[index].Content = append(merged[index].Content, baseFile.Content...)
} else {
found[baseFile.Path] = len(merged)
merged = append(merged, baseFile)
}
}
return merged
}
var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*")
func splitManifests(bigFile string) []string {
res := []string{}
// Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly.
bigFileTmp := strings.TrimSpace(bigFile)
docs := sep.Split(bigFileTmp, -1)
for _, d := range docs {
d = strings.TrimSpace(d) + "\n"
res = append(res, d)
}
return res
}
func namespace() string {
// this is really only useful when called via the ffi function from kotsadm
// because that namespace is not configurable otherwise
if os.Getenv("DEV_NAMESPACE") != "" {
return os.Getenv("DEV_NAMESPACE")
}
return util.PodNamespace
}