-
Notifications
You must be signed in to change notification settings - Fork 78
/
kcl_builder.go
284 lines (243 loc) · 6.33 KB
/
kcl_builder.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
package kcl
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
kcl "kcl-lang.io/kcl-go"
kclpkg "kcl-lang.io/kcl-go/pkg/kcl"
"kcl-lang.io/kcl-go/pkg/spec/gpyrpc"
"kcl-lang.io/kpm/pkg/api"
"kcl-lang.io/kpm/pkg/opt"
"kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/cmd/build/builders"
"kusionstack.io/kusion/pkg/cmd/build/builders/crd"
"kusionstack.io/kusion/pkg/cmd/build/builders/kcl/rest"
"kusionstack.io/kusion/pkg/log"
jsonutil "kusionstack.io/kusion/pkg/util/json"
"kusionstack.io/kusion/pkg/util/yaml"
)
type Builder struct{}
var (
_ builders.Builder = (*Builder)(nil)
enableRest bool
)
const (
MaxLogLength = 3751
IncludeSchemaTypePath = "include_schema_type_path"
)
func Init() error {
_, err := rest.New()
if err != nil {
return err
}
enableRest = true
return nil
}
func EnableRPC() bool {
return !enableRest
}
func (g *Builder) Build(o *builders.Options, _ *v1.Project, stack *v1.Stack) (*v1.Intent, error) {
compileResult, err := Run(o, stack)
if err != nil {
return nil, err
}
// convert Run result to i
i, err := KCLResult2Intent(compileResult.Documents)
if err != nil {
return nil, err
}
return i, nil
}
func Run(o *builders.Options, stack *v1.Stack) (*CompileResult, error) {
optList, err := BuildKCLOptions(o)
if err != nil {
return nil, err
}
log.Debugf("Compile filenames: %v", o.Filenames)
log.Debugf("Compile options: %s", jsonutil.MustMarshal2PrettyString(optList))
var result *kcl.KCLResultList
if o.IsKclPkg {
result, err = api.RunWithOpts(
opt.WithKclOption(*kclpkg.NewOption().Merge(optList...)),
opt.WithNoSumCheck(true),
)
} else {
// call kcl run
log.Debug("The current directory is not a KCL Package, use kcl run instead")
result, err = kcl.RunFiles(o.Filenames, optList...)
}
if err != nil {
return nil, err
}
compileResult := NewCompileResult(result)
// Append crd description to compiled result,
// workDir may omit empty if run in stack dir
err = appendCRDs(stack.Path, compileResult)
if err != nil {
return nil, err
}
return compileResult, err
}
func appendCRDs(workDir string, r *CompileResult) error {
if r == nil {
return nil
}
crdObjs, err := readCRDs(workDir)
if err != nil {
return err
}
if len(crdObjs) != 0 {
// Append to Documents
for _, obj := range crdObjs {
if doc, flag := obj.(map[string]interface{}); flag {
resource, err := k8sResource2ResourceMap(doc)
if err != nil {
return err
}
r.Documents = append(r.Documents, resource)
}
}
// Update RawYAMLResult
items := make([]interface{}, len(r.Documents))
for i, doc := range r.Documents {
items[i] = doc
}
r.RawYAMLResult = yaml.MergeToOneYAML(items...)
}
return nil
}
func readCRDs(workDir string) ([]interface{}, error) {
projectPath := path.Dir(workDir)
crdPath := path.Join(projectPath, crd.Directory)
_, err := os.Stat(crdPath)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
visitor := crd.NewVisitor(crdPath)
return visitor.Visit()
}
func BuildKCLOptions(o *builders.Options) ([]kcl.Option, error) {
optList := make([]kcl.Option, 0)
settings := o.Settings
workDir := o.WorkDir
arguments := o.Arguments
// build settings option
for _, setting := range settings {
if workDir != "" {
setting = filepath.Join(workDir, setting)
}
settingOptions := kcl.WithSettings(setting)
if settingOptions.Err != nil {
return nil, settingOptions.Err
}
optList = append(optList, settingOptions)
}
// build arguments option
for k, v := range arguments {
argStr := k + "=" + v
withOpt := kcl.WithOptions(argStr)
if withOpt.Err != nil {
return nil, withOpt.Err
}
optList = append(optList, withOpt)
}
// build workDir option
withOpt := kcl.WithWorkDir(workDir)
if withOpt.Err != nil {
return nil, withOpt.Err
}
optList = append(optList, withOpt)
// eliminate null values in the result
withOpt = kcl.WithDisableNone(true)
if withOpt.Err != nil {
return nil, withOpt.Err
}
optList = append(optList, withOpt)
if arguments[IncludeSchemaTypePath] == "true" {
withOpt = kcl.WithIncludeSchemaTypePath(true)
if withOpt.Err != nil {
return nil, withOpt.Err
}
optList = append(optList, withOpt)
}
return optList, nil
}
func normResult(resp *gpyrpc.ExecProgram_Result) (*CompileResult, error) {
var result CompileResult
if strings.TrimSpace(resp.JsonResult) == "" {
return &result, nil
}
var mList []map[string]interface{}
if err := json.Unmarshal([]byte(resp.JsonResult), &mList); err != nil {
return nil, err
}
if len(mList) == 0 {
return nil, fmt.Errorf("normResult: invalid result: %s", resp.JsonResult)
}
var kclResults []kcl.KCLResult
for _, m := range mList {
if len(m) != 0 {
kclResults = append(kclResults, m)
}
}
return &CompileResult{
Documents: kclResults,
}, nil
}
// CompileUsingCmd simply call kcl cmd
func CompileUsingCmd(sourceKclFiles []string, targetFile string, args map[string]string, settings []string) (string, string, error) {
kclArgs := []string{
genKclArgs(args, settings), "-n", "-o", targetFile,
}
kclArgs = append(kclArgs, sourceKclFiles...)
cmd := exec.Command(kclAppPath, kclArgs...)
cmd.Env = os.Environ()
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
return stdout.String(), stderr.String(), err
}
func genKclArgs(args map[string]string, settings []string) string {
kclArgs := ""
for key, value := range args {
kclArgs += fmt.Sprintf("-D %s=%s ", key, value)
}
if len(settings) > 0 {
kclArgs += fmt.Sprintf("-Y %s ", strings.Join(settings, " "))
}
return kclArgs
}
func Overwrite(fileName string, overrides []string) (bool, error) {
return kcl.OverrideFile(fileName, overrides, []string{})
}
func KCLResult2Intent(kclResults []kcl.KCLResult) (*v1.Intent, error) {
resources := make([]v1.Resource, len(kclResults))
for i, result := range kclResults {
// Marshal kcl result to bytes
bytes, err := json.Marshal(result)
if err != nil {
return nil, err
}
msg := string(bytes)
if len(msg) > MaxLogLength {
msg = msg[0:MaxLogLength]
}
log.Infof("convert kcl result to resource: %s", msg)
// Parse json data as models.Resource
var item v1.Resource
if err = json.Unmarshal(bytes, &item); err != nil {
return nil, err
}
resources[i] = item
}
return &v1.Intent{Resources: resources}, nil
}