From 655a060a0c0c3eb370af2fdc5967db58e2f97f7f Mon Sep 17 00:00:00 2001 From: adohe Date: Tue, 20 Feb 2024 11:58:07 +0800 Subject: [PATCH] feat: add code runner for configuration code execution --- pkg/cmd/generate/run/fake/fake.go | 13 +++++ pkg/cmd/generate/run/run.go | 57 +++++++++++++++++++ pkg/cmd/generate/run/run_test.go | 23 ++++++++ pkg/cmd/generate/run/testdata/base/base.k | 55 ++++++++++++++++++ pkg/cmd/generate/run/testdata/prod/kcl.mod | 9 +++ pkg/cmd/generate/run/testdata/prod/main.k | 16 ++++++ pkg/cmd/generate/run/testdata/prod/stack.yaml | 2 + pkg/cmd/generate/run/testdata/project.yaml | 2 + 8 files changed, 177 insertions(+) create mode 100644 pkg/cmd/generate/run/fake/fake.go create mode 100644 pkg/cmd/generate/run/run.go create mode 100644 pkg/cmd/generate/run/run_test.go create mode 100644 pkg/cmd/generate/run/testdata/base/base.k create mode 100644 pkg/cmd/generate/run/testdata/prod/kcl.mod create mode 100644 pkg/cmd/generate/run/testdata/prod/main.k create mode 100644 pkg/cmd/generate/run/testdata/prod/stack.yaml create mode 100644 pkg/cmd/generate/run/testdata/project.yaml diff --git a/pkg/cmd/generate/run/fake/fake.go b/pkg/cmd/generate/run/fake/fake.go new file mode 100644 index 00000000..56c27c96 --- /dev/null +++ b/pkg/cmd/generate/run/fake/fake.go @@ -0,0 +1,13 @@ +package fake + +import "kusionstack.io/kusion/pkg/cmd/generate/run" + +var _ run.CodeRunner = &KPMRunner{} + +// KPMRunner is a fake code runner for testing purposes. +type KPMRunner struct{} + +// Run does nothing. +func (r *KPMRunner) Run(workDir string, arguments map[string]string) ([]byte, error) { + return nil, nil +} diff --git a/pkg/cmd/generate/run/run.go b/pkg/cmd/generate/run/run.go new file mode 100644 index 00000000..48de5358 --- /dev/null +++ b/pkg/cmd/generate/run/run.go @@ -0,0 +1,57 @@ +package run + +import ( + kcl "kcl-lang.io/kcl-go" + kclpkg "kcl-lang.io/kcl-go/pkg/kcl" + "kcl-lang.io/kpm/pkg/api" + "kcl-lang.io/kpm/pkg/opt" +) + +// CodeRunner compiles and runs the target DSL based configuration code +// and returns configuration data in plain format. +type CodeRunner interface { + Run(workingDir string, arguments map[string]string) ([]byte, error) +} + +// KPMRunner should implement the CodeRunner interface. +var _ CodeRunner = &KPMRunner{} + +// KPMRunner implements the CodeRunner interface. +type KPMRunner struct{} + +// Run calls KPM api to compile and run KCL based configuration code. +func (r *KPMRunner) Run(workDir string, arguments map[string]string) ([]byte, error) { + optList := buildKCLOptions(workDir, arguments) + result, err := api.RunWithOpts( + opt.WithKclOption(*kclpkg.NewOption().Merge(optList...)), + opt.WithNoSumCheck(true), + opt.WithLogWriter(nil), + ) + if err != nil { + return nil, err + } + + return []byte(result.GetRawYamlResult()), nil +} + +// buildKCLOptions returns list of KCL options. +func buildKCLOptions(workDir string, arguments map[string]string) []kcl.Option { + optList := make([]kcl.Option, 2) + + // build arguments option + for k, v := range arguments { + argStr := k + "=" + v + withOpt := kcl.WithOptions(argStr) + optList = append(optList, withOpt) + } + + // build workDir option + withOpt := kcl.WithWorkDir(workDir) + optList = append(optList, withOpt) + + // eliminate null values in the result + withOpt = kcl.WithDisableNone(true) + optList = append(optList, withOpt) + + return optList +} diff --git a/pkg/cmd/generate/run/run_test.go b/pkg/cmd/generate/run/run_test.go new file mode 100644 index 00000000..6568185e --- /dev/null +++ b/pkg/cmd/generate/run/run_test.go @@ -0,0 +1,23 @@ +package run + +import ( + "os" + "path/filepath" + "testing" +) + +func TestKPMRunnerRun(t *testing.T) { + currentPath, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %v", err) + } + workDir := filepath.Join(currentPath, "testdata/prod") + codeRunner := &KPMRunner{} + value, err := codeRunner.Run(workDir, nil) + if err != nil { + t.Fatalf("Failed to run configuration code: %v", err) + } + if len(value) == 0 { + t.Fatalf("Unexpected value output") + } +} diff --git a/pkg/cmd/generate/run/testdata/base/base.k b/pkg/cmd/generate/run/testdata/base/base.k new file mode 100644 index 00000000..249a71ab --- /dev/null +++ b/pkg/cmd/generate/run/testdata/base/base.k @@ -0,0 +1,55 @@ +import catalog.models.schema.v1 as ac +import catalog.models.schema.v1.workload as wl +import catalog.models.schema.v1.workload.container as c +import catalog.models.schema.v1.workload.container.probe as p + +# base.k declares reusable configurations for all stacks. +helloworld: ac.AppConfiguration { + workload: wl.Service { + containers: { + "nginx": c.Container { + image: "nginx:v1" + # Run the following command as defined + command: ["/bin/sh", "-c", "echo hi"] + # Extra arguments append to command defined above + args: ["/bin/sh", "-c", "echo hi"] + env: { + # An environment variable of name "env1" and value "VALUE" will be set + "env1": "VALUE" + # An environment variable of name "env2" and value of the key "key" in the + # secret named "sec-name" will be set. + "env2": "secret://sec-name/key" + } + # Run the command "/bin/sh -c echo hi", as defined above, in the directory "/tmp" + workingDir: "/tmp" + # Configure a HTTP readiness probe + readinessProbe: p.Probe { + probeHandler: p.Http { + url: "http://localhost:80" + } + initialDelaySeconds: 10 + } + } + } + # Set the replicas + replicas: 2 + } +} + +sampleapp: ac.AppConfiguration { + workload: wl.Service { + containers: { + "nginx": c.Container { + image: "nginx:v1" + # Run the following command as defined + command: ["/bin/sh", "-c", "echo hi"] + # Extra arguments append to command defined above + args: ["/bin/sh", "-c", "echo hi"] + # Run the command "/bin/sh -c echo hi", as defined above, in the directory "/tmp" + workingDir: "/tmp" + } + } + # Set the replicas + replicas: 2 + } +} \ No newline at end of file diff --git a/pkg/cmd/generate/run/testdata/prod/kcl.mod b/pkg/cmd/generate/run/testdata/prod/kcl.mod new file mode 100644 index 00000000..a006208a --- /dev/null +++ b/pkg/cmd/generate/run/testdata/prod/kcl.mod @@ -0,0 +1,9 @@ +[package] +name = "testdata" +version = "0.1.0" + +[dependencies] +catalog = { git = "https://github.com/KusionStack/catalog.git", tag = "0.1.2" } +[profile] +entries = ["../base/base.k", "main.k"] + diff --git a/pkg/cmd/generate/run/testdata/prod/main.k b/pkg/cmd/generate/run/testdata/prod/main.k new file mode 100644 index 00000000..0b29f5c2 --- /dev/null +++ b/pkg/cmd/generate/run/testdata/prod/main.k @@ -0,0 +1,16 @@ +import catalog.models.schema.v1 as ac + +# main.k declares customized configurations for prod stack. +helloworld: ac.AppConfiguration { + workload.containers.nginx: { + # prod stack has different image + image = "nginx:v2" + } +} + +sampleapp: ac.AppConfiguration { + workload.containers.nginx: { + # prod stack has different image + image = "nginx:v3" + } +} \ No newline at end of file diff --git a/pkg/cmd/generate/run/testdata/prod/stack.yaml b/pkg/cmd/generate/run/testdata/prod/stack.yaml new file mode 100644 index 00000000..55617733 --- /dev/null +++ b/pkg/cmd/generate/run/testdata/prod/stack.yaml @@ -0,0 +1,2 @@ +# The stack basic info +name: prod diff --git a/pkg/cmd/generate/run/testdata/project.yaml b/pkg/cmd/generate/run/testdata/project.yaml new file mode 100644 index 00000000..6c69f98f --- /dev/null +++ b/pkg/cmd/generate/run/testdata/project.yaml @@ -0,0 +1,2 @@ +# The project basic info +name: testdata