From 67f9bd97265ebb106abcdc8066583ac88913f2c0 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Thu, 16 Feb 2017 17:00:32 -0500 Subject: [PATCH 1/2] Support local template transformation in process Allow clients to process templates using the client, rather than the server. Allows process to be used for local files against a Kubernetes server. oc process --local -f template.json | kubectl create -f - --- pkg/cmd/cli/cmd/process.go | 76 ++++++++++++++++++++++++++++++++------ test/cmd/templates.sh | 12 ++++++ 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/pkg/cmd/cli/cmd/process.go b/pkg/cmd/cli/cmd/process.go index 7b938ca1e00d..b4e88c622a4f 100644 --- a/pkg/cmd/cli/cmd/process.go +++ b/pkg/cmd/cli/cmd/process.go @@ -3,14 +3,17 @@ package cmd import ( "fmt" "io" + "math/rand" "reflect" "strings" + "time" "github.com/spf13/cobra" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/kubectl" kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -18,6 +21,7 @@ import ( kerrors "k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/sets" + "github.com/openshift/origin/pkg/client" "github.com/openshift/origin/pkg/cmd/cli/describe" "github.com/openshift/origin/pkg/cmd/templates" cmdutil "github.com/openshift/origin/pkg/cmd/util" @@ -25,6 +29,8 @@ import ( "github.com/openshift/origin/pkg/generate/app" "github.com/openshift/origin/pkg/template" templateapi "github.com/openshift/origin/pkg/template/api" + templatevalidation "github.com/openshift/origin/pkg/template/api/validation" + "github.com/openshift/origin/pkg/template/generator" ) var ( @@ -36,12 +42,19 @@ var ( as well as metadata describing the template. The output of the process command is always a list of one or more resources. You may pipe the - output to the create command over STDIN (using the '-f -' option) or redirect it to a file.`) + output to the create command over STDIN (using the '-f -' option) or redirect it to a file. + + Process resolves the template on the server, but you may pass --local to parameterize the template + locally. When running locally be aware that the version of your client tools will determine what + template transformations are supported, rather than the server.`) processExample = templates.Examples(` # Convert template.json file into resource list and pass to create %[1]s process -f template.json | %[1]s create -f - + # Process a file locally instead of contacting the server + %[1]s process -f template.json --local -o yaml + # Process template while passing a user-defined label %[1]s process -f template.json -l name=mytemplate @@ -78,6 +91,7 @@ func NewCmdProcess(fullName string, f *clientcmd.Factory, in io.Reader, out, err cmd.Flags().StringArrayVarP(params, "param", "p", nil, "Specify a key-value pair (eg. -p FOO=BAR) to set/override a parameter value in the template.") cmd.Flags().StringArray("param-file", []string{}, "File containing template parameter values to set/override in the template.") cmd.MarkFlagFilename("param-file") + cmd.Flags().BoolP("local", "", false, "If true process the template locally instead of contacting the server.") cmd.Flags().BoolP("parameters", "", false, "If true, do not process but only print available parameters") cmd.Flags().StringP("labels", "l", "", "Label to set in all resources for this template") @@ -112,6 +126,7 @@ func RunProcess(f *clientcmd.Factory, in io.Reader, out, errout io.Writer, cmd * } } + local := kcmdutil.GetFlagBool(cmd, "local") if cmd.Flag("value").Changed || cmd.Flag("param").Changed { flagValues := getFlagStringArray(cmd, "param") cmdutil.WarnAboutCommaSeparation(errout, flagValues, "--param") @@ -149,18 +164,32 @@ func RunProcess(f *clientcmd.Factory, in io.Reader, out, errout io.Writer, cmd * return err } - mapper, typer := f.Object() - - client, _, err := f.Clients() - if err != nil { - return err - } - var ( objects []runtime.Object infos []*resource.Info + + mapper meta.RESTMapper + typer runtime.ObjectTyper + + client *client.Client + clientMappingFn resource.ClientMapperFunc ) + if local { + // TODO: Change f.Object() so that it can fall back to local RESTMapper safely (currently glog.Fatals) + mapper = registered.RESTMapper() + typer = kapi.Scheme + clientMappingFn = func(*meta.RESTMapping) (resource.RESTClient, error) { return nil, nil } + // client is deliberately left nil + } else { + client, _, err = f.Clients() + if err != nil { + return err + } + + mapper, typer = f.Object() + clientMappingFn = f.ClientForMapping + } mapping, err := mapper.RESTMapping(templateapi.Kind("Template")) if err != nil { return err @@ -184,6 +213,7 @@ func RunProcess(f *clientcmd.Factory, in io.Reader, out, errout io.Writer, cmd * if len(storedTemplate) == 0 { return fmt.Errorf("invalid value syntax %q", templateName) } + templateObj, err := client.Templates(sourceNamespace).Get(storedTemplate) if err != nil { if errors.IsNotFound(err) { @@ -194,7 +224,7 @@ func RunProcess(f *clientcmd.Factory, in io.Reader, out, errout io.Writer, cmd * templateObj.CreationTimestamp = unversioned.Now() infos = append(infos, &resource.Info{Object: templateObj}) } else { - infos, err = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()). + infos, err = resource.NewBuilder(mapper, typer, clientMappingFn, kapi.Codecs.UniversalDecoder()). NamespaceParam(namespace).RequireNamespace(). FilenameParam(explicit, &resource.FilenameOptions{Recursive: false, Filenames: []string{filename}}). Do(). @@ -249,9 +279,16 @@ func RunProcess(f *clientcmd.Factory, in io.Reader, out, errout io.Writer, cmd * return kerrors.NewAggregate(errs) } - resultObj, err := client.TemplateConfigs(namespace).Create(obj) - if err != nil { - return fmt.Errorf("error processing the template %q: %v\n", obj.Name, err) + resultObj := obj + if local { + if err := processTemplateLocally(obj); err != nil { + return err + } + } else { + resultObj, err = client.TemplateConfigs(namespace).Create(obj) + if err != nil { + return fmt.Errorf("error processing the template %q: %v\n", obj.Name, err) + } } outputFormat := kcmdutil.GetFlagString(cmd, "output") @@ -307,3 +344,18 @@ func injectUserVars(values app.Environment, t *templateapi.Template) []error { } return errors } + +// processTemplateLocally applies the same logic that a remote call would make but makes no +// connection to the server. +func processTemplateLocally(tpl *templateapi.Template) error { + if errs := templatevalidation.ValidateProcessedTemplate(tpl); len(errs) > 0 { + return errors.NewInvalid(templateapi.Kind("Template"), tpl.Name, errs) + } + processor := template.NewProcessor(map[string]generator.Generator{ + "expression": generator.NewExpressionValueGenerator(rand.New(rand.NewSource(time.Now().UnixNano()))), + }) + if errs := processor.Process(tpl); len(errs) > 0 { + return errors.NewInvalid(templateapi.Kind("Template"), tpl.Name, errs) + } + return nil +} diff --git a/test/cmd/templates.sh b/test/cmd/templates.sh index 9ea5abe06828..171e6ca9d5e9 100755 --- a/test/cmd/templates.sh +++ b/test/cmd/templates.sh @@ -46,6 +46,18 @@ guestbook_template="${OS_ROOT}/test/templates/testdata/guestbook.json" os::cmd::expect_success "oc process -f '${guestbook_template}' -l app=guestbook | oc create -f -" os::cmd::expect_success_and_text 'oc status' 'frontend-service' echo "template+config: ok" + +os::test::junit::declare_suite_start "cmd/templates/local-config" +# Processes the template locally +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' --local -l app=guestbook -o yaml" "app: guestbook" +# Processes the template locally and get the same output in YAML +new="$(mktemp -d)" +os::cmd::expect_success 'oc process -f "${guestbook_template}" --local -l app=guestbook -o yaml ADMIN_USERNAME=au ADMIN_PASSWORD=ap REDIS_PASSWORD=rp > "${new}/localtemplate"' +os::cmd::expect_success 'oc process -f "${guestbook_template}" -l app=guestbook -o yaml ADMIN_USERNAME=au ADMIN_PASSWORD=ap REDIS_PASSWORD=rp > "${new}/remotetemplate"' +os::cmd::expect_success 'diff "${new}/localtemplate" "${new}/remotetemplate"' +# Does not even try to hit the server +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' --local -l app=guestbook -o yaml --server 0.0.0.0:1" "app: guestbook" +echo "template+config+local: ok" os::test::junit::declare_suite_end os::test::junit::declare_suite_start "cmd/templates/parameters" From 8ee72b0ca8a875696e48a15f51915d1598aa2353 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Thu, 16 Feb 2017 17:13:25 -0500 Subject: [PATCH 2/2] generated: CLI docs and completions --- contrib/completions/bash/oc | 2 ++ contrib/completions/bash/openshift | 2 ++ contrib/completions/zsh/oc | 2 ++ contrib/completions/zsh/openshift | 2 ++ docs/generated/oc_by_example_content.adoc | 3 +++ docs/man/man1/oc-process.1 | 10 ++++++++++ docs/man/man1/openshift-cli-process.1 | 10 ++++++++++ 7 files changed, 31 insertions(+) diff --git a/contrib/completions/bash/oc b/contrib/completions/bash/oc index 0fbe5b72064c..decc1cce5345 100644 --- a/contrib/completions/bash/oc +++ b/contrib/completions/bash/oc @@ -12405,6 +12405,8 @@ _oc_process() flags+=("--labels=") two_word_flags+=("-l") local_nonpersistent_flags+=("--labels=") + flags+=("--local") + local_nonpersistent_flags+=("--local") flags+=("--output=") two_word_flags+=("-o") local_nonpersistent_flags+=("--output=") diff --git a/contrib/completions/bash/openshift b/contrib/completions/bash/openshift index 0ac3e49ff19f..857000c74efa 100644 --- a/contrib/completions/bash/openshift +++ b/contrib/completions/bash/openshift @@ -17514,6 +17514,8 @@ _openshift_cli_process() flags+=("--labels=") two_word_flags+=("-l") local_nonpersistent_flags+=("--labels=") + flags+=("--local") + local_nonpersistent_flags+=("--local") flags+=("--output=") two_word_flags+=("-o") local_nonpersistent_flags+=("--output=") diff --git a/contrib/completions/zsh/oc b/contrib/completions/zsh/oc index 61acb4a4f9bf..b57161dbaf3c 100644 --- a/contrib/completions/zsh/oc +++ b/contrib/completions/zsh/oc @@ -12553,6 +12553,8 @@ _oc_process() flags+=("--labels=") two_word_flags+=("-l") local_nonpersistent_flags+=("--labels=") + flags+=("--local") + local_nonpersistent_flags+=("--local") flags+=("--output=") two_word_flags+=("-o") local_nonpersistent_flags+=("--output=") diff --git a/contrib/completions/zsh/openshift b/contrib/completions/zsh/openshift index 413fb728598e..11cdcdb1f8e6 100644 --- a/contrib/completions/zsh/openshift +++ b/contrib/completions/zsh/openshift @@ -17662,6 +17662,8 @@ _openshift_cli_process() flags+=("--labels=") two_word_flags+=("-l") local_nonpersistent_flags+=("--labels=") + flags+=("--local") + local_nonpersistent_flags+=("--local") flags+=("--output=") two_word_flags+=("-o") local_nonpersistent_flags+=("--output=") diff --git a/docs/generated/oc_by_example_content.adoc b/docs/generated/oc_by_example_content.adoc index 666393b5915a..aa27306412e0 100644 --- a/docs/generated/oc_by_example_content.adoc +++ b/docs/generated/oc_by_example_content.adoc @@ -2115,6 +2115,9 @@ Process a template into list of resources # Convert template.json file into resource list and pass to create oc process -f template.json | oc create -f - + # Process a file locally instead of contacting the server + oc process -f template.json --local -o yaml + # Process template while passing a user-defined label oc process -f template.json -l name=mytemplate diff --git a/docs/man/man1/oc-process.1 b/docs/man/man1/oc-process.1 index 86e5e251b834..e343b7b76609 100644 --- a/docs/man/man1/oc-process.1 +++ b/docs/man/man1/oc-process.1 @@ -21,6 +21,9 @@ Templates allow parameterization of resources prior to being sent to the server .PP The output of the process command is always a list of one or more resources. You may pipe the output to the create command over STDIN (using the '\-f \-' option) or redirect it to a file. +.PP +Process resolves the template on the server, but you may pass \-\-local to parameterize the template locally. When running locally be aware that the version of your client tools will determine what template transformations are supported, rather than the server. + .SH OPTIONS .PP @@ -31,6 +34,10 @@ The output of the process command is always a list of one or more resources. You \fB\-l\fP, \fB\-\-labels\fP="" Label to set in all resources for this template +.PP +\fB\-\-local\fP=false + If true process the template locally instead of contacting the server. + .PP \fB\-o\fP, \fB\-\-output\fP="json" Output format. One of: describe|json|yaml|name|go\-template=...|go\-template\-file=...|jsonpath=...|jsonpath\-file=... @@ -143,6 +150,9 @@ The output of the process command is always a list of one or more resources. You # Convert template.json file into resource list and pass to create oc process \-f template.json | oc create \-f \- + # Process a file locally instead of contacting the server + oc process \-f template.json \-\-local \-o yaml + # Process template while passing a user\-defined label oc process \-f template.json \-l name=mytemplate diff --git a/docs/man/man1/openshift-cli-process.1 b/docs/man/man1/openshift-cli-process.1 index a349ff0ceaa0..ce4f075093f1 100644 --- a/docs/man/man1/openshift-cli-process.1 +++ b/docs/man/man1/openshift-cli-process.1 @@ -21,6 +21,9 @@ Templates allow parameterization of resources prior to being sent to the server .PP The output of the process command is always a list of one or more resources. You may pipe the output to the create command over STDIN (using the '\-f \-' option) or redirect it to a file. +.PP +Process resolves the template on the server, but you may pass \-\-local to parameterize the template locally. When running locally be aware that the version of your client tools will determine what template transformations are supported, rather than the server. + .SH OPTIONS .PP @@ -31,6 +34,10 @@ The output of the process command is always a list of one or more resources. You \fB\-l\fP, \fB\-\-labels\fP="" Label to set in all resources for this template +.PP +\fB\-\-local\fP=false + If true process the template locally instead of contacting the server. + .PP \fB\-o\fP, \fB\-\-output\fP="json" Output format. One of: describe|json|yaml|name|go\-template=...|go\-template\-file=...|jsonpath=...|jsonpath\-file=... @@ -143,6 +150,9 @@ The output of the process command is always a list of one or more resources. You # Convert template.json file into resource list and pass to create openshift cli process \-f template.json | openshift cli create \-f \- + # Process a file locally instead of contacting the server + openshift cli process \-f template.json \-\-local \-o yaml + # Process template while passing a user\-defined label openshift cli process \-f template.json \-l name=mytemplate