-
Notifications
You must be signed in to change notification settings - Fork 88
/
config.go
144 lines (125 loc) · 5.89 KB
/
config.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
package config
import (
"bytes"
"github.com/pkg/errors"
kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
"github.com/replicatedhq/kots/kotskinds/multitype"
"github.com/replicatedhq/kots/pkg/base"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/template"
"github.com/replicatedhq/kots/pkg/util"
yaml "github.com/replicatedhq/yaml/v3"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/client-go/kubernetes/scheme"
)
func TemplateConfig(log *logger.Logger, configSpecData string, configValuesData string, licenseData string, localRegistry template.LocalRegistry) (string, error) {
return templateConfig(log, configSpecData, configValuesData, licenseData, localRegistry, MarshalConfig)
}
func TemplateConfigObjects(configSpec *kotsv1beta1.Config, configValues map[string]template.ItemValue, license *kotsv1beta1.License, localRegistry template.LocalRegistry, info *template.VersionInfo) (*kotsv1beta1.Config, error) {
templatedString, err := templateConfigObjects(configSpec, configValues, license, localRegistry, info, MarshalConfig)
if err != nil {
return nil, errors.Wrap(err, "failed to template config")
}
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, gvk, err := decode([]byte(templatedString), nil, nil) // TODO fix decode of boolstrings
if err != nil {
return nil, errors.Wrap(err, "failed to decode config data")
}
if gvk.Group != "kots.io" || gvk.Version != "v1beta1" || gvk.Kind != "Config" {
return nil, errors.Errorf("expected Config, but found %s/%s/%s", gvk.Group, gvk.Version, gvk.Kind)
}
config := obj.(*kotsv1beta1.Config)
return config, nil
}
func templateConfigObjects(configSpec *kotsv1beta1.Config, configValues map[string]template.ItemValue, license *kotsv1beta1.License, localRegistry template.LocalRegistry, info *template.VersionInfo, marshalFunc func(config *kotsv1beta1.Config) (string, error)) (string, error) {
builder, configVals, err := template.NewBuilder(configSpec.Spec.Groups, configValues, localRegistry, nil, license, info)
if err != nil {
return "", errors.Wrap(err, "failed to create config context")
}
ApplyValuesToConfig(configSpec, configVals)
configDocWithData, err := marshalFunc(configSpec)
if err != nil {
return "", errors.Wrap(err, "failed to marshal config")
}
rendered, err := builder.RenderTemplate("config", string(configDocWithData))
if err != nil {
return "", errors.Wrap(err, "failed to render config template")
}
return rendered, nil
}
func templateConfig(log *logger.Logger, configSpecData string, configValuesData string, licenseData string, localRegistry template.LocalRegistry, marshalFunc func(config *kotsv1beta1.Config) (string, error)) (string, error) {
// This function will
// 1. unmarshal config
// 2. replace all item values with values that already exist
// 3. evaluate the dependency graph for config values (template function chaining)
// 4. re-marshal it (with an unlimited line length)
// 5. put new config yaml through templating engine
// This process will re-order items and discard comments, so it should not be saved.
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, gvk, err := decode([]byte(licenseData), nil, nil)
if err != nil {
return "", errors.Wrap(err, "failed to decode license data")
}
if gvk.Group != "kots.io" || gvk.Version != "v1beta1" || gvk.Kind != "License" {
return "", errors.Errorf("expected License, but found %s/%s/%s", gvk.Group, gvk.Version, gvk.Kind)
}
license := obj.(*kotsv1beta1.License)
obj, gvk, err = decode([]byte(configSpecData), nil, nil) // TODO fix decode of boolstrings
if err != nil {
return "", errors.Wrap(err, "failed to decode config data")
}
if gvk.Group != "kots.io" || gvk.Version != "v1beta1" || gvk.Kind != "Config" {
return "", errors.Errorf("expected Config, but found %s/%s/%s", gvk.Group, gvk.Version, gvk.Kind)
}
config := obj.(*kotsv1beta1.Config)
// get template context from config values
templateContext, err := base.UnmarshalConfigValuesContent([]byte(configValuesData))
if err != nil {
log.Error(err)
templateContext = map[string]template.ItemValue{}
}
return templateConfigObjects(config, templateContext, license, localRegistry, &template.VersionInfo{}, marshalFunc)
}
func ApplyValuesToConfig(config *kotsv1beta1.Config, values map[string]template.ItemValue) {
for idxG, g := range config.Spec.Groups {
for idxI, i := range g.Items {
value, ok := values[i.Name]
if ok {
config.Spec.Groups[idxG].Items[idxI].Value = multitype.FromString(value.ValueStr())
config.Spec.Groups[idxG].Items[idxI].Default = multitype.FromString(value.DefaultStr())
}
for idxC, c := range i.Items {
value, ok := values[c.Name]
if ok {
config.Spec.Groups[idxG].Items[idxI].Items[idxC].Value = multitype.FromString(value.ValueStr())
config.Spec.Groups[idxG].Items[idxI].Items[idxC].Default = multitype.FromString(value.DefaultStr())
}
}
}
}
}
// MarshalConfig runs the same code path as the k8s json->yaml serializer, but uses a different yaml library for those parts
// first, the object is marshalled to json
// second, the json is unmarshalled to an object as yaml
// third, the object is marshalled as yaml
func MarshalConfig(config *kotsv1beta1.Config) (string, error) {
s := json.NewSerializerWithOptions(
json.DefaultMetaFactory,
scheme.Scheme,
scheme.Scheme,
json.SerializerOptions{Yaml: false, Pretty: true, Strict: false},
)
var marshalledJSON bytes.Buffer
if err := s.Encode(config, &marshalledJSON); err != nil {
return "", errors.Wrap(err, "failed to marshal config as json")
}
var unmarshalledYAML interface{}
if err := yaml.Unmarshal(marshalledJSON.Bytes(), &unmarshalledYAML); err != nil {
return "", errors.Wrap(err, "failed to unmarshal config as yaml")
}
marshalledYAML, err := util.MarshalIndent(2, unmarshalledYAML)
if err != nil {
return "", errors.Wrap(err, "failed to marshal config as yaml")
}
return string(marshalledYAML), nil
}