1 package charts
2
3 import (
4 "bytes"
5 "errors"
6 "net/http"
7 "path"
8 "strings"
9
10 "github.com/linkerd/linkerd2/pkg/charts/static"
11 "github.com/linkerd/linkerd2/pkg/version"
12 "helm.sh/helm/v3/pkg/chart/loader"
13 "helm.sh/helm/v3/pkg/chartutil"
14 "helm.sh/helm/v3/pkg/engine"
15 "sigs.k8s.io/yaml"
16 )
17
18 const versionPlaceholder = "linkerdVersionValue"
19
20 var (
21
22
23 L5dPartials = []string{
24 "charts/partials/" + chartutil.ChartfileName,
25 "charts/partials/templates/_affinity.tpl",
26 "charts/partials/templates/_capabilities.tpl",
27 "charts/partials/templates/_debug.tpl",
28 "charts/partials/templates/_helpers.tpl",
29 "charts/partials/templates/_metadata.tpl",
30 "charts/partials/templates/_nodeselector.tpl",
31 "charts/partials/templates/_network-validator.tpl",
32 "charts/partials/templates/_proxy-config-ann.tpl",
33 "charts/partials/templates/_proxy-init.tpl",
34 "charts/partials/templates/_proxy.tpl",
35 "charts/partials/templates/_pull-secrets.tpl",
36 "charts/partials/templates/_resources.tpl",
37 "charts/partials/templates/_tolerations.tpl",
38 "charts/partials/templates/_trace.tpl",
39 "charts/partials/templates/_validate.tpl",
40 "charts/partials/templates/_volumes.tpl",
41 }
42 )
43
44
45 type Chart struct {
46 Name string
47 Dir string
48 Namespace string
49
50
51
52 RawValues []byte
53
54
55
56 Values map[string]any
57
58 Files []*loader.BufferedFile
59 Fs http.FileSystem
60 }
61
62 func (c *Chart) render(partialsFiles []*loader.BufferedFile) (bytes.Buffer, error) {
63 if err := FilesReader(c.Fs, c.Dir+"/", c.Files); err != nil {
64 return bytes.Buffer{}, err
65 }
66
67
68 if err := FilesReader(static.Templates, "", partialsFiles); err != nil {
69 return bytes.Buffer{}, err
70 }
71
72
73 chart, err := loader.LoadFiles(append(c.Files, partialsFiles...))
74 if err != nil {
75 return bytes.Buffer{}, err
76 }
77
78 releaseOptions := chartutil.ReleaseOptions{
79 Name: c.Name,
80 IsInstall: true,
81 IsUpgrade: false,
82 Namespace: c.Namespace,
83 }
84
85 if len(c.RawValues) > 0 {
86 if c.Values != nil {
87 return bytes.Buffer{}, errors.New("either RawValues or Values should be set, but not both")
88 }
89 err = yaml.Unmarshal(c.RawValues, &c.Values)
90 if err != nil {
91 return bytes.Buffer{}, err
92 }
93 }
94
95 valuesToRender, err := chartutil.ToRenderValues(chart, c.Values, releaseOptions, nil)
96 if err != nil {
97 return bytes.Buffer{}, err
98 }
99 release, _ := valuesToRender["Release"].(map[string]interface{})
100 release["Service"] = "CLI"
101
102 renderedTemplates, err := engine.Render(chart, valuesToRender)
103 if err != nil {
104 return bytes.Buffer{}, err
105 }
106
107
108 var buf bytes.Buffer
109 for _, tmpl := range c.Files {
110 t := path.Join(releaseOptions.Name, tmpl.Name)
111 if _, err := buf.WriteString(renderedTemplates[t]); err != nil {
112 return bytes.Buffer{}, err
113 }
114 }
115
116 return buf, nil
117 }
118
119
120 func (c *Chart) Render() (bytes.Buffer, error) {
121
122 l5dPartials := []*loader.BufferedFile{}
123 for _, template := range L5dPartials {
124 l5dPartials = append(l5dPartials, &loader.BufferedFile{
125 Name: template,
126 })
127 }
128
129 return c.render(l5dPartials)
130 }
131
132
133 func (c *Chart) RenderCNI() (bytes.Buffer, error) {
134 cniPartials := []*loader.BufferedFile{
135 {Name: "charts/partials/" + chartutil.ChartfileName},
136 {Name: "charts/partials/templates/_helpers.tpl"},
137 {Name: "charts/partials/templates/_metadata.tpl"},
138 {Name: "charts/partials/templates/_pull-secrets.tpl"},
139 {Name: "charts/partials/templates/_tolerations.tpl"},
140 {Name: "charts/partials/templates/_resources.tpl"},
141 }
142 return c.render(cniPartials)
143 }
144
145
146 func ReadFile(fs http.FileSystem, dir string, f *loader.BufferedFile) error {
147 filename := dir + f.Name
148 if dir == "" {
149 filename = filename[7:]
150 }
151 file, err := fs.Open(filename)
152 if err != nil {
153 return err
154 }
155 defer file.Close()
156
157 buf := new(bytes.Buffer)
158 if _, err := buf.ReadFrom(file); err != nil {
159 return err
160 }
161
162 f.Data = buf.Bytes()
163 return nil
164 }
165
166
167 func FilesReader(fs http.FileSystem, dir string, files []*loader.BufferedFile) error {
168 for _, f := range files {
169 if err := ReadFile(fs, dir, f); err != nil {
170 return err
171 }
172 }
173 return nil
174 }
175
176
177
178 func InsertVersion(data []byte) []byte {
179 dataWithVersion := strings.ReplaceAll(string(data), versionPlaceholder, version.Version)
180 return []byte(dataWithVersion)
181 }
182
183
184
185 func InsertVersionValues(values chartutil.Values) (chartutil.Values, error) {
186 raw, err := values.YAML()
187 if err != nil {
188 return nil, err
189 }
190 return chartutil.ReadValues(InsertVersion([]byte(raw)))
191 }
192
193
194 func OverrideFromFile(values map[string]interface{}, fs http.FileSystem, chartName, name string) (map[string]interface{}, error) {
195
196 valuesOverride := loader.BufferedFile{
197 Name: name,
198 }
199 if err := ReadFile(fs, chartName+"/", &valuesOverride); err != nil {
200 return nil, err
201 }
202
203 var valuesOverrideMap map[string]interface{}
204 err := yaml.Unmarshal(valuesOverride.Data, &valuesOverrideMap)
205 if err != nil {
206 return nil, err
207 }
208 return MergeMaps(valuesOverrideMap, values), nil
209 }
210
211
212
213
214
215 func MergeMaps(a, b map[string]interface{}) map[string]interface{} {
216 out := make(map[string]interface{}, len(a))
217 for k, v := range a {
218 out[k] = v
219 }
220 for k, v := range b {
221 if v, ok := v.(map[string]interface{}); ok {
222 if av, ok := out[k]; ok {
223 if av, ok := av.(map[string]interface{}); ok {
224 out[k] = MergeMaps(av, v)
225 continue
226 }
227 }
228 }
229 out[k] = v
230 }
231 return out
232 }
233
View as plain text