Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(helm): Make name format configurable #381

Merged
merged 5 commits into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions cmd/tk/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"text/template"

"github.com/Masterminds/sprig/v3"
"github.com/go-clix/cli"

"github.com/grafana/tanka/pkg/tanka"
Expand Down Expand Up @@ -39,12 +40,6 @@ func exportCmd() *cli.Command {
vars := workflowFlags(cmd.Flags())
getJsonnetOpts := jsonnetFlags(cmd.Flags())

templateFuncMap := template.FuncMap{
"lower": func(s string) string {
return strings.ToLower(s)
},
}

cmd.Run = func(cmd *cli.Command, args []string) error {
// dir must be empty
to := args[1]
Expand All @@ -61,7 +56,9 @@ func exportCmd() *cli.Command {
// Replace all os.path separators in string with BelRune for creating subfolders
replacedFormat := strings.Replace(*format, string(os.PathSeparator), BelRune, -1)

tmpl, err := template.New("").Funcs(templateFuncMap).Parse(replacedFormat)
tmpl, err := template.New("").
Funcs(sprig.TxtFuncMap()). // register Masterminds/sprig
Parse(replacedFormat) // parse template
if err != nil {
return fmt.Errorf("Parsing name format: %s", err)
}
Expand Down
9 changes: 6 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@ go 1.12

require (
github.com/Masterminds/semver v1.4.2
github.com/Masterminds/sprig/v3 v3.1.0
github.com/fatih/color v1.9.0
github.com/fatih/structs v1.1.0
github.com/go-clix/cli v0.1.1
github.com/gobwas/glob v0.2.3
github.com/google/go-cmp v0.5.2-0.20200818193711-d2fcc899bdc2
github.com/google/go-jsonnet v0.16.1-0.20200908152747-b70cbd441a39
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/karrick/godirwalk v1.15.5
github.com/pkg/errors v0.8.1
github.com/posener/complete v1.2.3
github.com/spf13/pflag v1.0.5
github.com/stretchr/objx v0.2.0
github.com/stretchr/testify v1.4.0
github.com/stretchr/testify v1.5.1
github.com/thoas/go-funk v0.4.0
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
gopkg.in/yaml.v2 v2.2.8
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652
k8s.io/apimachinery v0.18.3
sigs.k8s.io/yaml v1.2.0
Expand Down
31 changes: 26 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y=
github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
Expand Down Expand Up @@ -35,14 +41,12 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.2-0.20200818193711-d2fcc899bdc2 h1:CZtx9gNen+kr3PuC/JQff3n1pJbgpy7Wr3hzjnupqdw=
github.com/google/go-cmp v0.5.2-0.20200818193711-d2fcc899bdc2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-jsonnet v0.15.1-0.20200331184325-4f4aa80dd785 h1:+dlQ7fPoeAqO0U9V+94golo/rW1/V2Pn+v8aPp3ljRM=
github.com/google/go-jsonnet v0.15.1-0.20200331184325-4f4aa80dd785/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
github.com/google/go-jsonnet v0.16.1-0.20200908152747-b70cbd441a39 h1:noLRnY1ESguFGDPxXvIcESe2rG63f+ZSbSGYfVa6iHo=
github.com/google/go-jsonnet v0.16.1-0.20200908152747-b70cbd441a39/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
Expand All @@ -52,6 +56,12 @@ github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uP
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
Expand All @@ -70,6 +80,10 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/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=
Expand All @@ -91,20 +105,25 @@ github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXq
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/thoas/go-funk v0.4.0 h1:KBaa5NL7NMtsFlQaD8nQMbDt1wuM+OOaNQyYNYQFhVo=
github.com/thoas/go-funk v0.4.0/go.mod h1:mlR+dHGb+4YgXkf13rkQTuzrneeHANxOm6+ZnEV9HsA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
Expand Down Expand Up @@ -141,6 +160,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652 h1:VKvJ/mQ4BgCjZUDggYFxTe0qv9jPMHsZPD4Xt91Y5H4=
gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk=
Expand Down
35 changes: 0 additions & 35 deletions pkg/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"io/ioutil"
"os"
"os/exec"
"strings"

"github.com/grafana/tanka/pkg/kubernetes/manifest"
)
Expand All @@ -24,40 +23,6 @@ type Helm interface {
Template(name, chart string, opts TemplateOpts) (manifest.List, error)
}

type TemplateOpts struct {
// Values to pass to Helm using --values
Values map[string]interface{}

// Kubernetes api versions used for Capabilities.APIVersions
APIVersions []string
// IncludeCRDs specifies whether CustomResourceDefinitions are included in
// the template output
IncludeCRDs bool
// Namespace scope for this request
Namespace string
}

// Flags returns all options apart from Values as their respective `helm
// template` flag equivalent
func (t TemplateOpts) Flags() []string {
var flags []string

if t.APIVersions != nil {
value := strings.Join(t.APIVersions, ",")
flags = append(flags, "--api-versions="+value)
}

if t.IncludeCRDs {
flags = append(flags, "--include-crds")
}

if t.Namespace != "" {
flags = append(flags, "--namespace="+t.Namespace)
}

return flags
}

// PullOpts are additional, non-required options for Helm.Pull
type PullOpts struct {
Opts
Expand Down
143 changes: 143 additions & 0 deletions pkg/helm/jsonnet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package helm

import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"text/template"

"github.com/Masterminds/sprig/v3"
"github.com/google/go-jsonnet"
"github.com/google/go-jsonnet/ast"
"github.com/grafana/tanka/pkg/kubernetes/manifest"
)

// DefaultNameFormat to use when no nameFormat is supplied
const DefaultNameFormat = `{{ print .kind "_" .metadata.name | snakecase }}`

// JsonnetOpts are additional properties the consumer of the native func might
// pass.
type JsonnetOpts struct {
TemplateOpts

// CalledFrom is the file that calls helmTemplate. This is used to find the
// vendored chart relative to this file
CalledFrom string `json:"calledFrom"`
// NameTemplate is used to create the keys in the resulting map
NameFormat string `json:"nameFormat"`
}

// NativeFunc returns a jsonnet native function that provides the same
// functionality as `Helm.Template` of this package. Charts are required to be
// present on the local filesystem, at a relative location to the file that
// calls `helm.template()` / `std.native('helmTemplate')`. This guarantees
// hermeticity
func NativeFunc(h Helm) *jsonnet.NativeFunction {
return &jsonnet.NativeFunction{
Name: "helmTemplate",
// Similar to `helm template [NAME] [CHART] [flags]` except 'conf' is a
// bit more elaborate and chart is a local path
Params: ast.Identifiers{"name", "chart", "opts"},
Func: func(data []interface{}) (interface{}, error) {
name, ok := data[0].(string)
if !ok {
return nil, fmt.Errorf("First argument 'name' must be of 'string' type, got '%T' instead", data[0])
}

chartpath, ok := data[1].(string)
if !ok {
return nil, fmt.Errorf("Second argument 'chart' must be of 'string' type, got '%T' instead", data[1])
}

// TODO: validate data[2] actually follows the struct scheme
opts, err := parseOpts(data[2])
if err != nil {
return "", err
}

// resolve the Chart relative to the caller
callerDir := filepath.Dir(opts.CalledFrom)
chart := filepath.Join(callerDir, chartpath)
if _, err := os.Stat(chart); err != nil {
// TODO: add website link for explanation
return nil, fmt.Errorf("helmTemplate: Failed to find a Chart at '%s': %s", chart, err)
}

// render resources
list, err := h.Template(name, chart, opts.TemplateOpts)
if err != nil {
return nil, err
}

// convert list to map
out, err := listAsMap(list, opts.NameFormat)
if err != nil {
return nil, err
}

return out, nil
},
}
}

func parseOpts(data interface{}) (*JsonnetOpts, error) {
c, err := json.Marshal(data)
if err != nil {
return nil, err
}
var opts JsonnetOpts
if err := json.Unmarshal(c, &opts); err != nil {
return nil, err
}

// Charts are only allowed at relative paths. Use conf.CalledFrom to find the callers directory
if opts.CalledFrom == "" {
// TODO: rephrase and move lengthy explanation to website
return nil, fmt.Errorf("helmTemplate: 'opts.calledFrom' is unset or empty.\nTanka must know where helmTemplate was called from to resolve the Helm Chart relative to that.\n")
}

return &opts, nil
}

func listAsMap(list manifest.List, nameFormat string) (map[string]interface{}, error) {
if nameFormat == "" {
nameFormat = DefaultNameFormat
}

tmpl, err := template.New("").
Funcs(sprig.TxtFuncMap()).
Parse(nameFormat)
if err != nil {
return nil, fmt.Errorf("Parsing name format: %w", err)
}

out := make(map[string]interface{})
for _, m := range list {
var buf bytes.Buffer
if err := tmpl.Execute(&buf, m); err != nil {
return nil, err
}
name := buf.String()

if _, ok := out[name]; ok {
return nil, ErrorDuplicateName{name: name, format: nameFormat}
}
out[name] = map[string]interface{}(m)
}

return out, nil
}

// ErrorDuplicateName means two resources share the same name using the given
// nameFormat.
type ErrorDuplicateName struct {
name string
format string
}

func (e ErrorDuplicateName) Error() string {
// TODO: explain on website
return fmt.Sprintf("Two resources share the same name '%s'. Please adapt the name template '%s'", e.name, e.format)
}
Loading