feat: add vars and extend template funcs

This commit is contained in:
Kevin Franklin Kim
2024-09-24 13:41:16 +02:00
parent e811d29af8
commit e215c0dc32
23 changed files with 384 additions and 65 deletions

20
go.mod
View File

@@ -6,6 +6,8 @@ replace github.com/miracl/conflate v1.2.1 => github.com/runz0rd/conflate v1.2.2-
require (
github.com/1Password/connect-sdk-go v1.5.3
github.com/BurntSushi/toml v1.4.0
github.com/Masterminds/sprig/v3 v3.3.0
github.com/alecthomas/chroma v0.10.0
github.com/miracl/conflate v1.2.1
github.com/pkg/errors v0.9.1
@@ -18,13 +20,16 @@ require (
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.31.1
sigs.k8s.io/yaml v1.4.0
)
require (
atomicgo.dev/cursor v0.2.0 // indirect
atomicgo.dev/keyboard v0.2.9 // indirect
atomicgo.dev/schedule v0.1.0 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
@@ -33,16 +38,22 @@ require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
@@ -52,10 +63,11 @@ require (
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.uber.org/atomic v1.10.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/apimachinery v0.31.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect

34
go.sum
View File

@@ -6,6 +6,8 @@ atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/1Password/connect-sdk-go v1.5.3 h1:KyjJ+kCKj6BwB2Y8tPM1Ixg5uIS6HsB0uWA8U38p/Uk=
github.com/1Password/connect-sdk-go v1.5.3/go.mod h1:5rSymY4oIYtS4G3t0oMkGAXBeoYiukV3vkqlnEjIDJs=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -22,6 +24,12 @@ github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
@@ -34,6 +42,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -54,6 +64,8 @@ github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQ
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -77,6 +89,10 @@ github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -109,8 +125,12 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -149,6 +169,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -185,22 +207,22 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=

View File

@@ -11,6 +11,8 @@ type Config struct {
// Version of the schema
Version string `yaml:"version,omitempty"`
// Global values to be injected into all squadron values
Vars map[string]any `yaml:"vars,omitempty"`
// Global values to be injected into all squadron values
Global map[string]any `yaml:"global,omitempty"`
// Global builds that can be referenced as dependencies
Builds map[string]Build `yaml:"builds,omitempty"`

View File

@@ -26,7 +26,7 @@ type Unit struct {
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (u *Unit) ValuesYAML(global map[string]any) ([]byte, error) {
func (u *Unit) ValuesYAML(global, vars map[string]any) ([]byte, error) {
values := u.Values
if values == nil {
values = map[string]any{}
@@ -36,6 +36,11 @@ func (u *Unit) ValuesYAML(global map[string]any) ([]byte, error) {
values["global"] = global
}
}
if vars != nil {
if _, ok := values["vars"]; !ok {
values["vars"] = vars
}
}
return yamlv2.Marshal(values)
}
@@ -69,9 +74,9 @@ func (u *Unit) Push(ctx context.Context, squadron, unit string, args []string) (
return "", nil
}
func (u *Unit) Template(ctx context.Context, name, squadron, unit, namespace string, global map[string]any, helmArgs []string) ([]byte, error) {
func (u *Unit) Template(ctx context.Context, name, squadron, unit, namespace string, global, vars map[string]any, helmArgs []string) ([]byte, error) {
var ret bytes.Buffer
valueBytes, err := u.ValuesYAML(global)
valueBytes, err := u.ValuesYAML(global, vars)
if err != nil {
return nil, err
}

View File

@@ -1,12 +1,5 @@
package template
func defaultValue(value string, def any) any {
if value == "" {
return def
}
return value
}
func defaultIndexValue(v map[string]any, index string, def any) any {
var ok bool
if _, ok = v[index]; ok {

View File

@@ -3,9 +3,14 @@ package template
import (
"bytes"
"context"
"encoding/json"
"os"
"strings"
"github.com/BurntSushi/toml"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
goyaml "sigs.k8s.io/yaml/goyaml.v3"
)
func file(ctx context.Context, templateVars any, errorOnMissing bool) func(v string) (string, error) {
@@ -27,3 +32,131 @@ func file(ctx context.Context, templateVars any, errorOnMissing bool) func(v str
return string(bytes.TrimSpace(renderedBytes)), nil
}
}
// toYAML takes an interface, marshals it to yaml, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func toYAML(v any) string {
data, err := yaml.Marshal(v)
if err != nil {
// Swallow errors inside of a template.
return ""
}
return strings.TrimSuffix(string(data), "\n")
}
func toYAMLPretty(v any) string {
var data bytes.Buffer
encoder := goyaml.NewEncoder(&data)
encoder.SetIndent(2)
err := encoder.Encode(v)
if err != nil {
// Swallow errors inside of a template.
return ""
}
return strings.TrimSuffix(data.String(), "\n")
}
// fromYAML converts a YAML document into a map[string]any.
//
// This is not a general-purpose YAML parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into
// m["Error"] in the returned map.
func fromYAML(str string) map[string]any {
m := map[string]any{}
if err := yaml.Unmarshal([]byte(str), &m); err != nil {
m["Error"] = err.Error()
}
return m
}
// fromYAMLArray converts a YAML array into a []any.
//
// This is not a general-purpose YAML parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string as
// the first and only item in the returned array.
func fromYAMLArray(str string) []any {
a := []any{}
if err := yaml.Unmarshal([]byte(str), &a); err != nil {
a = []any{err.Error()}
}
return a
}
// toTOML takes an interface, marshals it to toml, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func toTOML(v any) string {
b := bytes.NewBuffer(nil)
e := toml.NewEncoder(b)
err := e.Encode(v)
if err != nil {
return err.Error()
}
return b.String()
}
// fromTOML converts a TOML document into a map[string]any.
//
// This is not a general-purpose TOML parser, and will not parse all valid
// TOML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into
// m["Error"] in the returned map.
func fromTOML(str string) map[string]any {
m := make(map[string]any)
if err := toml.Unmarshal([]byte(str), &m); err != nil {
m["Error"] = err.Error()
}
return m
}
// toJSON takes an interface, marshals it to json, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func toJSON(v any) string {
data, err := json.Marshal(v)
if err != nil {
// Swallow errors inside of a template.
return ""
}
return string(data)
}
// fromJSON converts a JSON document into a map[string]any.
//
// This is not a general-purpose JSON parser, and will not parse all valid
// JSON documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into
// m["Error"] in the returned map.
func fromJSON(str string) map[string]any {
m := make(map[string]any)
if err := json.Unmarshal([]byte(str), &m); err != nil {
m["Error"] = err.Error()
}
return m
}
// fromJSONArray converts a JSON array into a []any.
//
// This is not a general-purpose JSON parser, and will not parse all valid
// JSON documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string as
// the first and only item in the returned array.
func fromJSONArray(str string) []any {
a := []any{}
if err := json.Unmarshal([]byte(str), &a); err != nil {
a = []any{err.Error()}
}
return a
}

View File

@@ -8,11 +8,3 @@ func indent(spaces int, v string) string {
pad := strings.Repeat(" ", spaces)
return strings.ReplaceAll(v, "\n", "\n"+pad)
}
func quote(v string) string {
return "'" + v + "'"
}
func replace(old, new, v string) string {
return strings.ReplaceAll(v, old, new)
}

View File

@@ -4,24 +4,38 @@ import (
"bytes"
"context"
"text/template"
"github.com/Masterminds/sprig/v3"
)
func ExecuteFileTemplate(ctx context.Context, text string, templateVars any, errorOnMissing bool) ([]byte, error) {
funcMap := template.FuncMap{
"env": env,
"envDefault": envDefault,
"op": onePassword(ctx, templateVars, errorOnMissing),
"opDoc": onePasswordDocument(ctx, templateVars, errorOnMissing),
"base64": base64,
"b64enc": base64,
"default": defaultValue,
"defaultIndex": defaultIndexValue,
"indent": indent,
"replace": replace,
"file": file(ctx, templateVars, errorOnMissing),
"git": git(ctx),
"quote": quote,
}
funcMap := sprig.TxtFuncMap()
delete(funcMap, "env")
delete(funcMap, "expandenv")
funcMap["env"] = env
funcMap["envDefault"] = envDefault
// deprecated
funcMap["indent"] = indent
funcMap["base64"] = base64
funcMap["defaultIndex"] = defaultIndexValue
funcMap["op"] = onePassword(ctx, templateVars, errorOnMissing)
funcMap["git"] = git(ctx)
funcMap["opDoc"] = onePasswordDocument(ctx, templateVars, errorOnMissing)
funcMap["file"] = file(ctx, templateVars, errorOnMissing)
funcMap["toToml"] = toTOML
funcMap["fromToml"] = fromTOML
funcMap["toYaml"] = toYAML
funcMap["toYamlPretty"] = toYAMLPretty
funcMap["fromYaml"] = fromYAML
funcMap["fromYamlArray"] = fromYAMLArray
funcMap["toJson"] = toJSON
funcMap["fromJson"] = fromJSON
funcMap["fromJsonArray"] = fromJSONArray
tpl, err := template.New("squadron").Delims("<% ", " %>").Funcs(funcMap).Parse(text)
if err != nil {
return nil, err

View File

@@ -157,6 +157,10 @@ func (sq *Squadron) RenderConfig(ctx context.Context) error {
util.ToSnakeCaseKeys(value)
tv.Add("Global", value)
}
if value, ok := vars["vars"]; ok {
util.ToSnakeCaseKeys(value)
tv.Add("Vars", value)
}
if value, ok := vars["squadron"]; ok {
util.ToSnakeCaseKeys(value)
tv.Add("Squadron", value)
@@ -190,6 +194,10 @@ func (sq *Squadron) RenderConfig(ctx context.Context) error {
util.ToSnakeCaseKeys(value)
tv.Add("Global", value)
}
if value, ok := vars["vars"]; ok {
util.ToSnakeCaseKeys(value)
tv.Add("Vars", value)
}
if value, ok := vars["squadron"]; ok {
util.ToSnakeCaseKeys(value)
tv.Add("Squadron", value)
@@ -330,7 +338,7 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) e
if err != nil {
return err
}
valueBytes, err := v.ValuesYAML(sq.c.Global)
valueBytes, err := v.ValuesYAML(sq.c.Global, sq.c.Vars)
if err != nil {
return err
}
@@ -557,7 +565,7 @@ func (sq *Squadron) Up(ctx context.Context, helmArgs []string, username, version
if err != nil {
return err
}
valueBytes, err := v.ValuesYAML(sq.c.Global)
valueBytes, err := v.ValuesYAML(sq.c.Global, sq.c.Vars)
if err != nil {
return err
}
@@ -625,7 +633,7 @@ func (sq *Squadron) Template(ctx context.Context, helmArgs []string, parallel in
}
pterm.Debug.Printfln("running helm template for chart: %s", name)
out, err := v.Template(gctx, name, key, k, namespace, sq.c.Global, helmArgs)
out, err := v.Template(gctx, name, key, k, namespace, sq.c.Global, sq.c.Vars, helmArgs)
if err != nil {
return err
}

View File

@@ -39,6 +39,10 @@ func TestConfigSimpleSnapshot(t *testing.T) {
name: "global",
files: []string{"squadron.yaml", "squadron.override.yaml"},
},
{
name: "vars",
files: []string{"squadron.yaml", "squadron.override.yaml"},
},
{
name: "template",
files: []string{"squadron.yaml"},
@@ -89,7 +93,7 @@ func config(t *testing.T, name string, files []string, squadronName string, unit
{
out, err := sq.Template(ctx, nil, 1)
require.NoError(t, err, "failed to render template")
require.NoError(t, err)
testutils.Snapshot(t, path.Join("testdata", name, "snapshop-template.yaml"), out)
}
}

View File

@@ -15,7 +15,7 @@ squadron:
- baz=baz
file: Dockerfile
tag: nightly
image: docker.mycompany.com/mycomapny/frontend
image: storefinder/frontend
values:
image:
repository: <% .Squadron.storefinder.frontend.builds.default.image %>

View File

@@ -15,8 +15,8 @@ squadron:
- baz=baz
file: Dockerfile
tag: nightly
image: docker.mycompany.com/mycomapny/frontend
image: storefinder/frontend
values:
image:
repository: docker.mycompany.com/mycomapny/frontend
repository: storefinder/frontend
tag: nightly

View File

@@ -43,7 +43,7 @@ spec:
spec:
containers:
- name: storefinder-frontend
image: 'docker.mycompany.com/mycomapny/frontend:nightly'
image: 'storefinder/frontend:nightly'
ports:
- name: http
protocol: TCP

View File

@@ -8,7 +8,7 @@ squadron:
default:
tag: latest
file: Dockerfile
image: docker.mycompany.com/mycomapny/frontend
image: storefinder/frontend
build_arg:
- "foo=foo"
- "bar=bar"
@@ -22,7 +22,7 @@ squadron:
default:
tag: latest
file: Dockerfile
image: docker.mycompany.com/mycomapny/backend
image: storefinder/backend
build_arg:
- "foo=foo"
- "bar=bar"

View File

@@ -1,5 +1,6 @@
version: "2.0"
global:
enabled: true
host: mycompany.com
squadron:
storefinder:
@@ -11,7 +12,7 @@ squadron:
builds:
default:
tag: latest
image: docker.mycompany.com/mycomapny/frontend-admin
image: storefinder/backend
values:
image:
repository: <% .Squadron.storefinder.backend.builds.default.image %>
@@ -24,10 +25,10 @@ squadron:
builds:
default:
tag: latest
image: docker.mycompany.com/mycomapny/frontend
image: storefinder/frontend
values:
env:
BASE64: <% base64 "1234567890" %>
BASE64: <% "1234567890" | b64enc %>
DEFAULT_INDEX_VALUE: <% defaultIndex .Global "notexists" "fallback" %>
DEFAULT_VALUE: <% "" | default "fallback" %>
ENV: <% env "SHELL" %>

View File

@@ -1,5 +1,6 @@
version: "2.0"
global:
enabled: true
host: mycompany.com
squadron:
storefinder:
@@ -11,10 +12,10 @@ squadron:
builds:
default:
tag: latest
image: docker.mycompany.com/mycomapny/frontend-admin
image: storefinder/backend
values:
image:
repository: docker.mycompany.com/mycomapny/frontend-admin
repository: storefinder/backend
tag: latest
frontend:
chart:
@@ -24,7 +25,7 @@ squadron:
builds:
default:
tag: latest
image: docker.mycompany.com/mycomapny/frontend
image: storefinder/frontend
values:
env:
BASE64: MTIzNDU2Nzg5MA==
@@ -33,7 +34,7 @@ squadron:
ENV: /bin/zsh
GLOBAL: mycompany.com
image:
repository: docker.mycompany.com/mycomapny/frontend
repository: storefinder/frontend
tag: latest
values: |
foo: bar

View File

@@ -43,7 +43,7 @@ spec:
spec:
containers:
- name: storefinder-backend
image: 'docker.mycompany.com/mycomapny/frontend-admin:latest'
image: 'storefinder/backend:latest'
ports:
- name: http
protocol: TCP
@@ -93,7 +93,7 @@ spec:
spec:
containers:
- name: storefinder-frontend
image: 'docker.mycompany.com/mycomapny/frontend:latest'
image: 'storefinder/frontend:latest'
ports:
- name: http
protocol: TCP

View File

@@ -2,6 +2,7 @@ version: "2.0"
global:
host: mycompany.com
enabled: true
squadron:
storefinder:
@@ -10,7 +11,7 @@ squadron:
builds:
default:
tag: latest
image: docker.mycompany.com/mycomapny/frontend
image: storefinder/frontend
values:
image:
tag: <% .Squadron.storefinder.frontend.builds.default.tag %>
@@ -18,7 +19,7 @@ squadron:
env:
ENV: <% env "SHELL" %>
GLOBAL: <% .Global.host %>
BASE64: <% base64 "1234567890" %>
BASE64: <% "1234567890" | b64enc %>
DEFAULT_VALUE: <% "" | default "fallback" %>
DEFAULT_INDEX_VALUE: <% defaultIndex .Global "notexists" "fallback" %>
# ONE_PASSWORD: <% op "ACCOUNT_NAME" "UUID" "FIELD" %>
@@ -31,7 +32,7 @@ squadron:
builds:
default:
tag: latest
image: docker.mycompany.com/mycomapny/frontend-admin
image: storefinder/backend
values:
image:
tag: <% .Squadron.storefinder.backend.builds.default.tag %>

View File

@@ -0,0 +1,26 @@
version: "2.0"
vars:
bar:
- one
- two
baz: null
foo: two
squadron:
storefinder:
backend:
chart:
name: backend
repository: file://<% env "PROJECT_ROOT" %>/_examples/common/charts/backend
version: 0.0.1
builds:
default:
tag: latest
image: storefinder/backend
values:
env:
bar: <% .Vars.bar %>
baz: <% .Vars.baz %>
foo: <% .Vars.foo %>
image:
repository: <% .Squadron.storefinder.backend.builds.default.image %>
tag: <% .Squadron.storefinder.backend.builds.default.tag %>

26
testdata/vars/snapshop-config.yaml vendored Normal file
View File

@@ -0,0 +1,26 @@
version: "2.0"
vars:
bar:
- one
- two
baz: null
foo: two
squadron:
storefinder:
backend:
chart:
name: backend
repository: file://./_examples/common/charts/backend
version: 0.0.1
builds:
default:
tag: latest
image: storefinder/backend
values:
env:
bar: [one two]
baz: <no value>
foo: two
image:
repository: storefinder/backend
tag: latest

50
testdata/vars/snapshop-template.yaml vendored Normal file
View File

@@ -0,0 +1,50 @@
---
# Source: backend/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: storefinder-backend
labels:
app.kubernetes.io/name: storefinder-backend
app.kubernetes.io/component: backend
app.kubernetes.io/managed-by: Helm
helm.sh/chart: 'backend-0.0.1'
namespace: default
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: storefinder-backend
app.kubernetes.io/component: backend
ports:
- name: http
port: 80
---
# Source: backend/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: storefinder-backend
labels:
app.kubernetes.io/name: storefinder-backend
app.kubernetes.io/component: backend
app.kubernetes.io/managed-by: Helm
helm.sh/chart: 'backend-0.0.1'
namespace: default
spec:
selector:
matchLabels:
app.kubernetes.io/name: storefinder-backend
app.kubernetes.io/component: backend
template:
metadata:
labels:
app.kubernetes.io/name: storefinder-backend
app.kubernetes.io/component: backend
spec:
containers:
- name: storefinder-backend
image: 'storefinder/backend:latest'
ports:
- name: http
protocol: TCP
containerPort: 80

6
testdata/vars/squadron.override.yaml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: "2.0"
vars:
foo: "two"
bar: ["two"]
baz: ~

23
testdata/vars/squadron.yaml vendored Normal file
View File

@@ -0,0 +1,23 @@
version: "2.0"
vars:
foo: "one"
bar: ["one"]
baz: "one"
squadron:
storefinder:
backend:
chart: <% env "PROJECT_ROOT" %>/_examples/common/charts/backend
builds:
default:
tag: latest
image: storefinder/backend
values:
image:
tag: <% .Squadron.storefinder.backend.builds.default.tag %>
repository: <% .Squadron.storefinder.backend.builds.default.image %>
env:
foo: <% .Vars.foo %>
bar: <% .Vars.bar %>
baz: <% .Vars.baz %>