diff --git a/cmd/delete.go b/cmd/delete.go index 487d9b3d0ce4ff91d2453d23a838785347a0ddcb..ff7d60e68212be0b6b40042b5d09a6b6c982629c 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -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 } diff --git a/cmd/diff.go b/cmd/diff.go index e1cf8dc6b978b64767020a1c97c79868f4e53e68..14a057407a762ba637835482d71ce54f6a00c856 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -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 } diff --git a/cmd/root.go b/cmd/root.go index 9d188b6fef8ca987ac4d47bb6cc187f9dd115eac..33b920fe4d62f06aa6ec650115ed4357a34c1f50 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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 diff --git a/cmd/show.go b/cmd/show.go index 6e98a35e67135b2489afc8e200e6aca3155a8c70..acc0ae0f36bbe7fc852e3fede5091f42267b0721 100644 --- a/cmd/show.go +++ b/cmd/show.go @@ -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 } diff --git a/cmd/update.go b/cmd/update.go index c48696fe45bf2c3e7253d86c89dbc8500a7b855e..0efc1238befbcec3d525046c2faf2dc925078cdc 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -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 } diff --git a/cmd/validate.go b/cmd/validate.go index f44e9b4c0bf003dcd6070a21c6619e6da48c987b..dec8f6c6f8fa0cc27fa2a790615a7f7180afa991 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -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 diff --git a/template/expander.go b/template/expander.go new file mode 100644 index 0000000000000000000000000000000000000000..d414db8c6ab33db2e46396210ab3c9c13401c381 --- /dev/null +++ b/template/expander.go @@ -0,0 +1,122 @@ +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 +} diff --git a/template/resolver.go b/template/resolver.go new file mode 100644 index 0000000000000000000000000000000000000000..6c18c57474d16764d7e648d57d0912eefa528b8b --- /dev/null +++ b/template/resolver.go @@ -0,0 +1,53 @@ +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 +}