Skip to content
Snippets Groups Projects
Commit 86c5ad02 authored by Alex Clemmer's avatar Alex Clemmer
Browse files

Add a template expander abstraction

This commit will introduce the `template.Expander` abstraction, which is
meant to abstract over an invocation of the Jsonnet VM. Specifically, it
provides facilities for users to provide (e.g.) Jpaths, ext vars, and so
on.

The main justification for this change is:

* We need a common way for the `pkg` and `cmd` packages to interact with
  the Jsonnet VM.
* The `utils` package is already too much of a catch-all.
* It's easier to think about an invocation of the Jsonnet VM when we
  additionally encapsulate the parameters we pass to it on every
  invocation in kubecfg.
parent 83799252
No related branches found
No related tags found
No related merge requests found
......@@ -47,7 +47,12 @@ var deleteCmd = &cobra.Command{
return err
}
objs, err := readObjs(cmd, args)
vm, err := newExpander(cmd)
if err != nil {
return err
}
objs, err := vm.Expand(args)
if err != nil {
return err
}
......
......@@ -52,7 +52,12 @@ var diffCmd = &cobra.Command{
return err
}
objs, err := readObjs(cmd, args)
vm, err := newExpander(cmd)
if err != nil {
return err
}
objs, err := vm.Expand(args)
if err != nil {
return err
}
......
......@@ -21,21 +21,18 @@ import (
goflag "flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
jsonnet "github.com/strickyak/jsonnet_cgo"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
"github.com/ksonnet/kubecfg/template"
"github.com/ksonnet/kubecfg/utils"
// Register auth plugins
......@@ -155,176 +152,49 @@ func (f *logFormatter) Format(e *log.Entry) ([]byte, error) {
return buf.Bytes(), nil
}
// JsonnetVM constructs a new jsonnet.VM, according to command line
// flags
func JsonnetVM(cmd *cobra.Command) (*jsonnet.VM, error) {
vm := jsonnet.Make()
func newExpander(cmd *cobra.Command) (*template.Expander, error) {
flags := cmd.Flags()
spec := template.Expander{}
var err error
jpath := os.Getenv("KUBECFG_JPATH")
for _, p := range filepath.SplitList(jpath) {
log.Debugln("Adding jsonnet search path", p)
vm.JpathAdd(p)
}
spec.EnvJPath = filepath.SplitList(os.Getenv("KUBECFG_JPATH"))
jpath, err := flags.GetString(flagJpath)
if err != nil {
return nil, err
}
for _, p := range filepath.SplitList(jpath) {
log.Debugln("Adding jsonnet search path", p)
vm.JpathAdd(p)
}
spec.FlagJpath = filepath.SplitList(jpath)
extvars, err := flags.GetStringSlice(flagExtVar)
spec.ExtVars, err = flags.GetStringSlice(flagExtVar)
if err != nil {
return nil, err
}
for _, extvar := range extvars {
kv := strings.SplitN(extvar, "=", 2)
switch len(kv) {
case 1:
v, present := os.LookupEnv(kv[0])
if present {
vm.ExtVar(kv[0], v)
} else {
return nil, fmt.Errorf("Missing environment variable: %s", kv[0])
}
case 2:
vm.ExtVar(kv[0], kv[1])
}
}
extvarfiles, err := flags.GetStringSlice(flagExtVarFile)
if err != nil {
return nil, err
}
for _, extvar := range extvarfiles {
kv := strings.SplitN(extvar, "=", 2)
if len(kv) != 2 {
return nil, fmt.Errorf("Failed to parse %s: missing '=' in %s", flagExtVarFile, extvar)
}
v, err := ioutil.ReadFile(kv[1])
if err != nil {
return nil, err
}
vm.ExtVar(kv[0], string(v))
}
tlavars, err := flags.GetStringSlice(flagTlaVar)
spec.ExtVarFiles, err = flags.GetStringSlice(flagExtVarFile)
if err != nil {
return nil, err
}
for _, tlavar := range tlavars {
kv := strings.SplitN(tlavar, "=", 2)
switch len(kv) {
case 1:
v, present := os.LookupEnv(kv[0])
if present {
vm.TlaVar(kv[0], v)
} else {
return nil, fmt.Errorf("Missing environment variable: %s", kv[0])
}
case 2:
vm.TlaVar(kv[0], kv[1])
}
}
tlavarfiles, err := flags.GetStringSlice(flagTlaVarFile)
spec.TlaVars, err = flags.GetStringSlice(flagTlaVar)
if err != nil {
return nil, err
}
for _, tlavar := range tlavarfiles {
kv := strings.SplitN(tlavar, "=", 2)
if len(kv) != 2 {
return nil, fmt.Errorf("Failed to parse %s: missing '=' in %s", flagTlaVarFile, tlavar)
}
v, err := ioutil.ReadFile(kv[1])
if err != nil {
return nil, err
}
vm.TlaVar(kv[0], string(v))
}
resolver, err := buildResolver(cmd)
spec.TlaVarFiles, err = flags.GetStringSlice(flagTlaVarFile)
if err != nil {
return nil, err
}
utils.RegisterNativeFuncs(vm, resolver)
return vm, nil
}
func buildResolver(cmd *cobra.Command) (utils.Resolver, error) {
flags := cmd.Flags()
resolver, err := flags.GetString(flagResolver)
if err != nil {
return nil, err
}
failAction, err := flags.GetString(flagResolvFail)
spec.Resolver, err = flags.GetString(flagResolver)
if err != nil {
return nil, err
}
ret := resolverErrorWrapper{}
switch failAction {
case "ignore":
ret.OnErr = func(error) error { return nil }
case "warn":
ret.OnErr = func(err error) error {
log.Warning(err.Error())
return nil
}
case "error":
ret.OnErr = func(err error) error { return err }
default:
return nil, fmt.Errorf("Bad value for --%s: %s", flagResolvFail, failAction)
}
switch resolver {
case "noop":
ret.Inner = utils.NewIdentityResolver()
case "registry":
ret.Inner = utils.NewRegistryResolver(&http.Client{
Transport: utils.NewAuthTransport(http.DefaultTransport),
})
default:
return nil, fmt.Errorf("Bad value for --%s: %s", flagResolver, resolver)
}
return &ret, nil
}
type resolverErrorWrapper struct {
Inner utils.Resolver
OnErr func(error) error
}
func (r *resolverErrorWrapper) Resolve(image *utils.ImageName) error {
err := r.Inner.Resolve(image)
if err != nil {
err = r.OnErr(err)
}
return err
}
func readObjs(cmd *cobra.Command, paths []string) ([]*unstructured.Unstructured, error) {
vm, err := JsonnetVM(cmd)
spec.FailAction, err = flags.GetString(flagResolvFail)
if err != nil {
return nil, err
}
defer vm.Destroy()
res := []*unstructured.Unstructured{}
for _, path := range paths {
objs, err := utils.Read(vm, path)
if err != nil {
return nil, fmt.Errorf("Error reading %s: %v", path, err)
}
res = append(res, utils.FlattenToV1(objs)...)
}
return res, nil
return &spec, nil
}
// For debugging
......
......@@ -39,7 +39,12 @@ var showCmd = &cobra.Command{
flags := cmd.Flags()
out := cmd.OutOrStdout()
objs, err := readObjs(cmd, args)
vm, err := newExpander(cmd)
if err != nil {
return err
}
objs, err := vm.Expand(args)
if err != nil {
return err
}
......
......@@ -89,7 +89,12 @@ var updateCmd = &cobra.Command{
return err
}
objs, err := readObjs(cmd, args)
vm, err := newExpander(cmd)
if err != nil {
return err
}
objs, err := vm.Expand(args)
if err != nil {
return err
}
......
......@@ -32,10 +32,16 @@ var validateCmd = &cobra.Command{
Use: "validate",
Short: "Compare generated manifest against server OpenAPI spec",
RunE: func(cmd *cobra.Command, args []string) error {
objs, err := readObjs(cmd, args)
vm, err := newExpander(cmd)
if err != nil {
return err
}
objs, err := vm.Expand(args)
if err != nil {
return err
}
_, disco, err := restClientPool(cmd)
if err != nil {
return err
......
package template
import (
"fmt"
"io/ioutil"
"os"
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/ksonnet/kubecfg/utils"
log "github.com/sirupsen/logrus"
jsonnet "github.com/strickyak/jsonnet_cgo"
)
type Expander struct {
EnvJPath []string
FlagJpath []string
ExtVars []string
ExtVarFiles []string
TlaVars []string
TlaVarFiles []string
Resolver string
FailAction string
}
func (spec *Expander) Expand(paths []string) ([]*unstructured.Unstructured, error) {
vm, err := spec.jsonnetVM()
if err != nil {
return nil, err
}
defer vm.Destroy()
res := []*unstructured.Unstructured{}
for _, path := range paths {
objs, err := utils.Read(vm, path)
if err != nil {
return nil, fmt.Errorf("Error reading %s: %v", path, err)
}
res = append(res, utils.FlattenToV1(objs)...)
}
return res, nil
}
// JsonnetVM constructs a new jsonnet.VM, according to command line
// flags
func (spec *Expander) jsonnetVM() (*jsonnet.VM, error) {
vm := jsonnet.Make()
for _, p := range spec.EnvJPath {
log.Debugln("Adding jsonnet search path", p)
vm.JpathAdd(p)
}
for _, p := range spec.FlagJpath {
log.Debugln("Adding jsonnet search path", p)
vm.JpathAdd(p)
}
for _, extvar := range spec.ExtVars {
kv := strings.SplitN(extvar, "=", 2)
switch len(kv) {
case 1:
v, present := os.LookupEnv(kv[0])
if present {
vm.ExtVar(kv[0], v)
} else {
return nil, fmt.Errorf("Missing environment variable: %s", kv[0])
}
case 2:
vm.ExtVar(kv[0], kv[1])
}
}
for _, extvar := range spec.ExtVarFiles {
kv := strings.SplitN(extvar, "=", 2)
if len(kv) != 2 {
return nil, fmt.Errorf("Failed to parse ext var files: missing '=' in %s", extvar)
}
v, err := ioutil.ReadFile(kv[1])
if err != nil {
return nil, err
}
vm.ExtVar(kv[0], string(v))
}
for _, tlavar := range spec.TlaVars {
kv := strings.SplitN(tlavar, "=", 2)
switch len(kv) {
case 1:
v, present := os.LookupEnv(kv[0])
if present {
vm.TlaVar(kv[0], v)
} else {
return nil, fmt.Errorf("Missing environment variable: %s", kv[0])
}
case 2:
vm.TlaVar(kv[0], kv[1])
}
}
for _, tlavar := range spec.TlaVarFiles {
kv := strings.SplitN(tlavar, "=", 2)
if len(kv) != 2 {
return nil, fmt.Errorf("Failed to parse tla var files: missing '=' in %s", tlavar)
}
v, err := ioutil.ReadFile(kv[1])
if err != nil {
return nil, err
}
vm.TlaVar(kv[0], string(v))
}
resolver, err := spec.buildResolver()
if err != nil {
return nil, err
}
utils.RegisterNativeFuncs(vm, resolver)
return vm, nil
}
package template
import (
"fmt"
"net/http"
"github.com/ksonnet/kubecfg/utils"
log "github.com/sirupsen/logrus"
)
func (spec *Expander) buildResolver() (utils.Resolver, error) {
ret := resolverErrorWrapper{}
switch spec.FailAction {
case "ignore":
ret.OnErr = func(error) error { return nil }
case "warn":
ret.OnErr = func(err error) error {
log.Warning(err.Error())
return nil
}
case "error":
ret.OnErr = func(err error) error { return err }
default:
return nil, fmt.Errorf("Unknown resolve failure type: %s", spec.FailAction)
}
switch spec.Resolver {
case "noop":
ret.Inner = utils.NewIdentityResolver()
case "registry":
ret.Inner = utils.NewRegistryResolver(&http.Client{
Transport: utils.NewAuthTransport(http.DefaultTransport),
})
default:
return nil, fmt.Errorf("Unknown resolver type: %s", spec.Resolver)
}
return &ret, nil
}
type resolverErrorWrapper struct {
Inner utils.Resolver
OnErr func(error) error
}
func (r *resolverErrorWrapper) Resolve(image *utils.ImageName) error {
err := r.Inner.Resolve(image)
if err != nil {
err = r.OnErr(err)
}
return err
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment