/
chart.go
332 lines (270 loc) · 10.6 KB
/
chart.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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
package model
import (
"fmt"
"reflect"
"sort"
"github.com/solo-io/skv2/codegen/model/values"
"github.com/iancoleman/strcase"
"github.com/solo-io/skv2/codegen/doc"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
)
type Chart struct {
Operators []Operator
// filter out the template based on its output name
FilterTemplate func(outPath string) bool
// outPath: content template map
CustomTemplates CustomTemplates
Values interface{}
// goes into the chart.yaml
Data Data
// inline string of custom _helpers.tpl which can be provided for the generated chart
HelpersTpl string
// Only generate the chart
ChartOnly bool
// if specified, generate reference docs for the chart values to the provided filename
ValuesReferenceDocs ValuesReferenceDocs
// if specificed, generate inline documentation for the values in chart's values.yaml files
ValuesInlineDocs *ValuesInlineDocs
// if specificed, values.schema.json will be generated with a JSON Schema that
// imposes structure on the values.yaml file
JsonSchema *JsonSchema
}
type ValuesReferenceDocs struct {
Title string
Filename string
}
type ValuesInlineDocs struct {
// if specified, inline field documentation comments will be wrapped at many characters
LineLengthLimit int
}
type JsonSchema struct {
// (Optional) will be called to override the default json schema mapping
// for the type. This is useful for types that also override default json/yaml
// serialization behaviour. It accepts the json schema as a map and is
// expected to return a value that can serialize to the json schema or null if
// there is no custom mapping for this type
CustomTypeMapper func(reflect.Type, map[string]interface{}) interface{}
}
type Operator struct {
Name string
// (Optional) To change the name referenced in the values file. If not specified a camelcase version of name is used
ValuesFileNameOverride string
// (Optional) For nesting operators in values API (e.g. a value of "agent" would place an operator at "agent.<operatorName>" )
ValuePath string
// deployment config
Deployment Deployment
// these populate the generated ClusterRole for the operator
ClusterRbac []rbacv1.PolicyRule
// these populate the generated Role for the operator
// key should be the k8s resource name (lower-case, plural version)
NamespaceRbac map[string][]rbacv1.PolicyRule
// if at least one port is defined, create a Service for it
Service Service
// Custom values to include at operator level
Values interface{}
// (Optional) If this operator should be applied to a namespace
// specified in a common value (e.g. "$Values.common.addonNamespace") specify the full value path here
NamespaceFromValuePath string
// Optional: if specified, the operator resources will be abled based on the
// condition specified in the enable statement.
//
// E.g: `and (.Values.operator.customValueA) (.Values.operator.customValueB)`
CustomEnableCondition string
}
func (o Operator) FormattedName() string {
formattedName := strcase.ToLowerCamel(o.Name)
if o.ValuesFileNameOverride != "" {
formattedName = strcase.ToLowerCamel(o.ValuesFileNameOverride)
}
return formattedName
}
// values for Deployment template
type Deployment struct {
// TODO support use of a DaemonSet instead of a Deployment
UseDaemonSet bool
Container
Strategy *appsv1.DeploymentStrategy
Sidecars []Sidecar
PodSecurityContext *corev1.PodSecurityContext
Volumes []corev1.Volume
ConditionalVolumes []ConditionalVolume
CustomPodLabels map[string]string
CustomPodAnnotations map[string]string
CustomDeploymentLabels map[string]string
CustomDeploymentAnnotations map[string]string
}
type ConditionalVolume struct {
Condition string
Volume corev1.Volume
}
// values for a container
type Container struct {
// not configurable via helm values
Args []string
VolumeMounts []corev1.VolumeMount
ConditionalVolumeMounts []ConditionalVolumeMount
ReadinessProbe *ReadinessProbe
LivenessProbe *corev1.Probe
Image Image
Env []corev1.EnvVar
Resources *corev1.ResourceRequirements
SecurityContext *corev1.SecurityContext
ContainerPorts []ContainerPort
// TemplateEnvVars renders environment variables that can use templated Helm values.
// At least 1 environment variable must be set via Env to use this.
TemplateEnvVars []TemplateEnvVar
}
type ConditionalVolumeMount struct {
Condition string
VolumeMount corev1.VolumeMount
}
// TemplateEnvVar corresponds to an environment variable that can use templated Helm values
type TemplateEnvVar struct {
// Condition for this environment variable to be rendered
// E.g. `and (.Values.operator.customValueA) (.Values.operator.customValueB)`
Condition string
// Name of the environment variable
// E.g. FOO_BAR
Name string
// Helm value
// E.g. {{ .Values.foo.bar }}
Value string
}
type ContainerPort struct {
Name string
Port string
}
type ReadinessProbe struct {
Exec []string // optional: if specified, the readiness probe will be an exec probe with the specified commands
Path string // Path to access on the HTTP server. Either specify Path and Port for httpGet probes, or specify Exec
Port string
Scheme string // optional scheme: HTTP or HTTPS ((kasunt): imo better to keep it as a non-enum field)
PeriodSeconds int
InitialDelaySeconds int
}
// sidecars require a container config and a unique name
type Sidecar struct {
Container
Service
ClusterRbac []rbacv1.PolicyRule
NamespaceRbac map[string][]rbacv1.PolicyRule
Volumes []corev1.Volume
Name string
EnableStatement string `json:"enableStatement,omitempty" yaml:"enableStatement,omitempty"` // Optional: if specified, the operator resources will be abled based on the condition specified in the enable statement.
ValuesPath string `json:"valuesPath,omitempty" yaml:"valuesPath,omitempty"` // Override for values path in generated yaml.
}
// values for struct template
type Service struct {
Type corev1.ServiceType
Ports []ServicePort
CustomLabels map[string]string
CustomAnnotations map[string]string
}
type ServicePort struct {
// The name of this port within the service.
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name" desc:"The name of this port within the service."`
// The default port that will be exposed by this service.
DefaultPort int32 `json:"port" protobuf:"varint,3,opt,name=port" desc:"The default port that will be exposed by this service."`
}
type Image = values.Image
// Helm chart dependency
type Dependency struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Repository string `json:"repository,omitempty"`
Condition string `json:"condition,omitempty"`
Alias string `json:"alias,omitempty"`
}
type Data struct {
ApiVersion string `json:"apiVersion,omitempty"`
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Home string `json:"home,omitempty"`
Icon string `json:"icon,omitempty"`
Sources []string `json:"sources,omitempty"`
Dependencies []Dependency `json:"dependencies,omitempty"`
}
func makeContainerDocs(c Container) values.UserContainerValues {
return values.UserContainerValues{
Image: c.Image,
Env: c.Env,
Resources: c.Resources,
SecurityContext: c.SecurityContext,
}
}
func (c Chart) BuildChartValues() values.UserHelmValues {
helmValues := values.UserHelmValues{CustomValues: c.Values}
for _, operator := range c.Operators {
servicePorts := map[string]uint32{}
for _, port := range operator.Service.Ports {
servicePorts[port.Name] = uint32(port.DefaultPort)
}
sidecars := map[string]values.UserContainerValues{}
for _, sidecar := range operator.Deployment.Sidecars {
// Note: We don't want to render docs for conditional sidecars
if sidecar.ValuesPath != "" {
continue
}
sidecars[strcase.ToLowerCamel(sidecar.Name)] = makeContainerDocs(sidecar.Container)
}
helmValues.Operators = append(helmValues.Operators, values.UserOperatorValues{
Name: operator.Name,
ValuesFileNameOverride: operator.ValuesFileNameOverride,
ValuePath: operator.ValuePath,
Values: values.UserValues{
UserContainerValues: makeContainerDocs(operator.Deployment.Container),
Sidecars: sidecars,
FloatingUserID: false,
RunAsUser: 10101,
ServiceType: operator.Service.Type,
ServicePorts: servicePorts,
},
CustomValues: operator.Values,
})
}
if c.ValuesInlineDocs != nil {
helmValues.ValuesInlineDocs = &values.UserValuesInlineDocs{
LineLengthLimit: c.ValuesInlineDocs.LineLengthLimit,
}
}
if c.JsonSchema != nil {
helmValues.JsonSchema.CustomTypeMapper = c.JsonSchema.CustomTypeMapper
}
return helmValues
}
func (c Chart) GenerateHelmDoc() string {
helmValues := c.BuildChartValues()
// generate documentation for custom values
helmValuesForDoc := doc.GenerateHelmValuesDoc(helmValues.CustomValues, "", "")
// generate documentation for operator values
for _, operatorWithValues := range helmValues.Operators {
name := operatorWithValues.FormattedName()
values := operatorWithValues.Values
// clear image tag so it doesn't show build time commit hashes
values.Image.Tag = ""
for name, container := range values.Sidecars {
container.Image.Tag = ""
values.Sidecars[name] = container
}
keyPath := name
if operatorWithValues.ValuePath != "" {
keyPath = fmt.Sprintf("%s.%s", operatorWithValues.ValuePath, name)
}
helmValuesForDoc = append(helmValuesForDoc, doc.GenerateHelmValuesDoc(operatorWithValues.CustomValues, keyPath, fmt.Sprintf("Configuration for the %s deployment.", name))...)
helmValuesForDoc = append(helmValuesForDoc, doc.GenerateHelmValuesDoc(values, keyPath, fmt.Sprintf("Configuration for the %s deployment.", name))...)
}
// alphabetize all values
sort.Slice(helmValuesForDoc, func(i, j int) bool {
if helmValuesForDoc[i].Key != helmValuesForDoc[j].Key {
return helmValuesForDoc[i].Key < helmValuesForDoc[j].Key
}
if helmValuesForDoc[i].Description != helmValuesForDoc[j].Description {
return helmValuesForDoc[i].Description < helmValuesForDoc[j].Description
}
return helmValuesForDoc[i].Type < helmValuesForDoc[j].Type
})
return helmValuesForDoc.ToMarkdown(c.ValuesReferenceDocs.Title)
}