diff --git a/main.go b/main.go index 0163658..3574a24 100644 --- a/main.go +++ b/main.go @@ -5,14 +5,14 @@ import ( "encoding/json" "errors" "fmt" + "github.com/kairos-io/kairos-agent/v2/pkg/action" + "github.com/kairos-io/kairos-agent/v2/pkg/utils" "os" "path/filepath" "regexp" "runtime" "strings" - "github.com/kairos-io/kairos-agent/v2/pkg/utils" - "github.com/kairos-io/kairos-agent/v2/internal/agent" "github.com/kairos-io/kairos-agent/v2/internal/bus" "github.com/kairos-io/kairos-agent/v2/internal/common" @@ -367,6 +367,38 @@ enabled: true`, }, }, }, + { + Name: "render-template", + Usage: "Render a Go template", + Description: "Render a Go template with machine state and config as data context", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "file", + Aliases: []string{"f"}, + Required: true, + }, + }, + Action: func(c *cli.Context) error { + + config, err := agentConfig.Scan(collector.Directories(configScanDir...), collector.NoLogs, collector.StrictValidation(c.Bool("strict-validation"))) + if err != nil { + return err + } + + runtime, err := state.NewRuntime() + if err != nil { + return err + } + + result, err := action.RenderTemplate(c.String("file"), config, runtime) + if err != nil { + return err + } + + _, err = os.Stdout.Write(result) + return err + }, + }, { Name: "interactive-install", Description: ` diff --git a/pkg/action/render_template.go b/pkg/action/render_template.go new file mode 100644 index 0000000..d85204b --- /dev/null +++ b/pkg/action/render_template.go @@ -0,0 +1,60 @@ +/* +Copyright © 2022 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "bytes" + "github.com/Masterminds/sprig/v3" + "github.com/kairos-io/kairos-agent/v2/pkg/config" + "github.com/kairos-io/kairos-sdk/state" + "gopkg.in/yaml.v3" + "os" + "text/template" +) + +func RenderTemplate(path string, config *config.Config, runtime state.Runtime) ([]byte, error) { + // Marshal runtime to YAML then to Map so that it is consistent with the output of 'kairos-agent state' + var runtimeMap map[string]interface{} + err := yaml.Unmarshal([]byte(runtime.String()), &runtimeMap) + if err != nil { + return nil, err + } + + tpl, err := loadTemplateFile(path) + if err != nil { + return nil, err + } + + result := new(bytes.Buffer) + err = tpl.Execute(result, map[string]interface{}{ + "Config": config.Config, + "State": runtimeMap, + }) + if err != nil { + return nil, err + } + return result.Bytes(), nil +} + +func loadTemplateFile(path string) (*template.Template, error) { + tpl := template.New(path).Funcs(sprig.FuncMap()) + content, err := os.ReadFile(path) + if err != nil { + return nil, err + } + return tpl.Parse(string(content)) +} diff --git a/pkg/action/render_template_test.go b/pkg/action/render_template_test.go new file mode 100644 index 0000000..0deb582 --- /dev/null +++ b/pkg/action/render_template_test.go @@ -0,0 +1,38 @@ +package action + +import ( + "fmt" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" + "github.com/kairos-io/kairos-sdk/collector" + "github.com/kairos-io/kairos-sdk/state" + "gopkg.in/yaml.v3" + "os" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RenderTemplate action test", func() { + + It("renders the template with config and state", func() { + config := agentConfig.NewConfig() + config.Config = collector.Config{ + "testKey": "testValue", + } + runtime, err := state.NewRuntime() + + fmt.Println(os.Getwd()) + + result, err := RenderTemplate("../../tests/fixtures/template/test.yaml", config, runtime) + Expect(err).ToNot(HaveOccurred()) + Expect(result).ToNot(BeNil()) + Expect(len(result)).ToNot(BeZero()) + + var data map[string]string + err = yaml.Unmarshal(result, &data) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(HaveKeyWithValue("configTest", "TESTVALUE")) + Expect(data["stateTest"]).To(MatchRegexp("^[0-9a-f]{8}$")) + }) + +}) diff --git a/tests/fixtures/template/test.yaml b/tests/fixtures/template/test.yaml new file mode 100644 index 0000000..d107698 --- /dev/null +++ b/tests/fixtures/template/test.yaml @@ -0,0 +1,2 @@ +configTest: "{{.Config.testKey | upper}}" +stateTest: "{{.State.uuid | trunc 8}}" \ No newline at end of file