Skip to content

Commit c3093cf

Browse files
committed
Add kanvas-render for rendering kanvas.jsonnet.template to kanvas.yaml with some extvars
1 parent 4b8acd8 commit c3093cf

File tree

9 files changed

+273
-36
lines changed

9 files changed

+273
-36
lines changed

app/app.go

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ package app
33
import (
44
"fmt"
55
"os"
6+
"path/filepath"
7+
"strings"
68
"time"
79

10+
"github.com/goccy/go-yaml"
11+
812
"github.com/davinci-std/kanvas/plugin"
913

1014
"github.com/davinci-std/kanvas/interpreter"
@@ -13,11 +17,17 @@ import (
1317
)
1418

1519
type App struct {
16-
Config kanvas.Component
20+
Config Config
1721
Runtime *kanvas.Runtime
1822
Options kanvas.Options
1923
}
2024

25+
type Config struct {
26+
Raw []byte
27+
Path string
28+
kanvas.Component
29+
}
30+
2131
func New(opts kanvas.Options) (*App, error) {
2232
// ts is a timestamp in the format of YYYYMMDDHHMMSS
2333
now := time.Now()
@@ -28,23 +38,36 @@ func New(opts kanvas.Options) (*App, error) {
2838
}
2939
opts.TempDir = tempDir
3040

31-
c, err := kanvas.LoadConfig(opts)
41+
path, err := kanvas.DiscoverConfigFile(opts)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
file, err := kanvas.RenderOrReadFile(path)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
c, err := kanvas.LoadConfig(path, file)
3252
if err != nil {
3353
return nil, err
3454
}
3555

3656
r := kanvas.NewRuntime()
3757

3858
return &App{
39-
Config: *c,
59+
Config: Config{
60+
Raw: file,
61+
Path: path,
62+
Component: *c,
63+
},
4064
Runtime: r,
4165
Options: opts,
4266
}, nil
43-
4467
}
4568

4669
func (a *App) newWorkflow() (*kanvas.Workflow, error) {
47-
return kanvas.NewWorkflow(a.Config, a.Options)
70+
return kanvas.NewWorkflow(a.Config.Component, a.Options)
4871
}
4972

5073
// Diff shows the diff between the desired state and the current state
@@ -82,6 +105,29 @@ func (a *App) Export(format, dir, kanvasContainerImage string) error {
82105
return e.Export(format, dir, kanvasContainerImage)
83106
}
84107

108+
func (a *App) Render(dir string) error {
109+
path := filepath.Base(a.Config.Path)
110+
111+
const ext = ".template.jsonnet"
112+
if strings.HasSuffix(path, ext) {
113+
path = path[:len(path)-len(ext)]
114+
} else {
115+
path = path[:len(path)-len(filepath.Ext(path))]
116+
}
117+
path = filepath.Join(dir, path+".yaml")
118+
119+
yamlData, err := yaml.JSONToYAML(a.Config.Raw)
120+
if err != nil {
121+
return fmt.Errorf("unable to convert json to yaml: %w", err)
122+
}
123+
124+
if err := os.WriteFile(path, yamlData, 0644); err != nil {
125+
return fmt.Errorf("unable to write to %s: %w", path, err)
126+
}
127+
128+
return nil
129+
}
130+
85131
func (a *App) Output(format, op, target string) error {
86132
wf, err := a.newWorkflow()
87133
if err != nil {

cmd/root.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,24 @@ func Root() *cobra.Command {
9595
cmd.AddCommand(export)
9696
}
9797

98+
{
99+
var (
100+
renderDir string
101+
)
102+
render := &cobra.Command{
103+
Use: "export",
104+
Short: "Render the kanvas.template.jsonnet to kanvas.yaml",
105+
RunE: func(cmd *cobra.Command, args []string) error {
106+
return run(cmd, opts, func(a *app.App) error {
107+
cmd.SilenceUsage = true
108+
return a.Render(renderDir)
109+
})
110+
},
111+
}
112+
render.Flags().StringVarP(&renderDir, "dir", "d", "", "Writes the rendered kanvas.yaml to this directory")
113+
cmd.AddCommand(render)
114+
}
115+
98116
{
99117
var (
100118
target string

convention.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package kanvas
33
import (
44
"fmt"
55
"os"
6+
"strings"
67
)
78

89
const (
9-
defaultConfigFileBase = "kanvas"
10-
DefaultConfigFileYAML = defaultConfigFileBase + ".yaml"
11-
DefaultConfigFileJsonnet = defaultConfigFileBase + ".jsonnet"
10+
defaultConfigFileBase = "kanvas"
11+
DefaultConfigFileYAML = defaultConfigFileBase + ".yaml"
12+
DefaultConfigFileJsonnet = defaultConfigFileBase + ".jsonnet"
13+
DefaultConfigFileTemplateJsonnet = defaultConfigFileBase + ".template.jsonnet"
1214
)
1315

1416
// DiscoverConfigFile returns the path to the config file to use.
@@ -22,27 +24,27 @@ func DiscoverConfigFile(opts Options) (string, error) {
2224
return opts.ConfigFile, nil
2325
}
2426

25-
var yamlFound, jsonnetFound bool
27+
var (
28+
found []string
29+
)
2630

2731
if _, err := os.Stat(DefaultConfigFileYAML); err == nil {
28-
yamlFound = true
32+
found = append(found, DefaultConfigFileYAML)
2933
}
3034

3135
if _, err := os.Stat(DefaultConfigFileJsonnet); err == nil {
32-
jsonnetFound = true
36+
found = append(found, DefaultConfigFileJsonnet)
3337
}
3438

35-
if yamlFound && jsonnetFound {
36-
return "", fmt.Errorf("both %q and %q exist. Please specify the config file with --config-file", DefaultConfigFileYAML, DefaultConfigFileJsonnet)
39+
if _, err := os.Stat(DefaultConfigFileTemplateJsonnet); err == nil {
40+
found = append(found, DefaultConfigFileTemplateJsonnet)
3741
}
3842

39-
if yamlFound {
40-
return DefaultConfigFileYAML, nil
43+
if len(found) > 1 {
44+
return "", fmt.Errorf("%d config files %s found. Please specify the config file with --config-file", len(found), strings.Join(found, ", "))
45+
} else if len(found) == 0 {
46+
return "", fmt.Errorf("unable to find config file")
4147
}
4248

43-
if jsonnetFound {
44-
return DefaultConfigFileJsonnet, nil
45-
}
46-
47-
return "", fmt.Errorf("unable to find config file")
49+
return found[0], nil
4850
}

kanvas.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"os"
88
"path/filepath"
9+
"strings"
910

1011
"github.com/goccy/go-yaml"
1112
"github.com/google/go-jsonnet"
@@ -109,16 +110,23 @@ type Kubernetes struct {
109110
// The jsonnet file can be used to generate the yaml file.
110111
// If the file is a jsonnet file, it is compiled to json first.
111112
// The compiled json is then unmarshalled into the Component struct.
112-
func LoadConfig(opts Options) (*Component, error) {
113+
func LoadConfig(path string, file []byte) (*Component, error) {
113114
var (
114115
config Component
115116
)
116117

117-
path, err := DiscoverConfigFile(opts)
118-
if err != nil {
118+
if err := yaml.Unmarshal(file, &config); err != nil {
119119
return nil, err
120120
}
121121

122+
if config.Dir == "" {
123+
config.Dir = filepath.Dir(path)
124+
}
125+
126+
return &config, nil
127+
}
128+
129+
func RenderOrReadFile(path string) ([]byte, error) {
122130
file, err := os.ReadFile(path)
123131
if err != nil {
124132
return nil, err
@@ -127,6 +135,18 @@ func LoadConfig(opts Options) (*Component, error) {
127135
if filepath.Ext(path) == ".jsonnet" {
128136
vm := jsonnet.MakeVM()
129137

138+
if strings.Contains(filepath.Base(path), ".template.") {
139+
vm.ExtVar("template", "true")
140+
141+
if v := os.Getenv("GITHUB_REPOSITORY"); v != "" {
142+
splits := strings.Split(v, "/")
143+
vm.ExtVar("github_repo_owner", splits[0])
144+
vm.ExtVar("github_repo_name", splits[1])
145+
} else {
146+
return nil, errors.New(".template.jsonnet requires GITHUB_REPOSITORY to be set to OWNER/REPO_NAME for the template to access `std.extVar(\"github_repo_name\")` and `std.extVar(\"github_repo_owner\")`")
147+
}
148+
}
149+
130150
json, err := vm.EvaluateAnonymousSnippet(path, string(file))
131151
if err != nil {
132152
return nil, err
@@ -135,13 +155,5 @@ func LoadConfig(opts Options) (*Component, error) {
135155
file = []byte(json)
136156
}
137157

138-
if err := yaml.Unmarshal(file, &config); err != nil {
139-
return nil, err
140-
}
141-
142-
if config.Dir == "" {
143-
config.Dir = filepath.Dir(path)
144-
}
145-
146-
return &config, nil
158+
return file, nil
147159
}

kanvas_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package kanvas
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestRenderOrReadFile(t *testing.T) {
11+
v := os.Getenv("GITHUB_REPOSITORY")
12+
defer os.Setenv("GITHUB_REPOSITORY", v)
13+
14+
os.Setenv("GITHUB_REPOSITORY", "owner/repo")
15+
16+
f, err := RenderOrReadFile("testdata/render_or_read_file_test.template.jsonnet")
17+
require.NoError(t, err)
18+
19+
require.Equal(t, `{
20+
"my_repo_name": "repo-suffix1",
21+
"my_repo_owner": "owner-suffix2"
22+
}
23+
`, string(f))
24+
}
25+
26+
func TestRenderOrReadFileNoEnv(t *testing.T) {
27+
v := os.Getenv("GITHUB_REPOSITORY")
28+
defer os.Setenv("GITHUB_REPOSITORY", v)
29+
30+
os.Setenv("GITHUB_REPOSITORY", "")
31+
32+
_, err := RenderOrReadFile("testdata/render_or_read_file_test.template.jsonnet")
33+
require.Equal(t,
34+
".template.jsonnet requires GITHUB_REPOSITORY to be set to OWNER/REPO_NAME for the template to access `std.extVar(\"github_repo_name\")` and `std.extVar(\"github_repo_owner\")`",
35+
err.Error(),
36+
)
37+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
components: {
3+
product1: {
4+
appimage: {
5+
dir: "/containerimages/app",
6+
docker: {
7+
image: "myregistry/" + std.extVar("github_repo_owner") + "-" + std.extVar("github_repo_name"),
8+
}
9+
}
10+
}
11+
}
12+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
components:
2+
product1:
3+
appimage:
4+
dir: /containerimages/app
5+
docker:
6+
image: myregistry/myowner-myrepo

0 commit comments

Comments
 (0)