diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index f59428539d6b..a84c2026923b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1949,6 +1949,11 @@ "Comment": "0.2.2", "Rev": "3433f3ea46d9f8019119e7dd41274e112a2359a9" }, + { + "ImportPath": "github.com/joho/godotenv", + "Comment": "v1-10-g4ed1339", + "Rev": "4ed13390c0acd2ff4e371e64d8b97c8954138243" + }, { "ImportPath": "github.com/jonboulle/clockwork", "Rev": "3f831b65b61282ba6bece21b91beea2edc4c887a" diff --git a/contrib/completions/bash/oc b/contrib/completions/bash/oc index 5a6f14c583cc..d99821676b0c 100644 --- a/contrib/completions/bash/oc +++ b/contrib/completions/bash/oc @@ -10432,6 +10432,10 @@ _oc_new-app() flags+=("--env=") two_word_flags+=("-e") local_nonpersistent_flags+=("--env=") + flags+=("--env-file=") + flags_with_completion+=("--env-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--env-file=") flags+=("--file=") flags_with_completion+=("--file") flags_completion+=("__handle_filename_extension_flag yaml|yml|json") @@ -10466,6 +10470,10 @@ _oc_new-app() flags+=("--param=") two_word_flags+=("-p") local_nonpersistent_flags+=("--param=") + flags+=("--param-file=") + flags_with_completion+=("--param-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--param-file=") flags+=("--search") flags+=("-S") local_nonpersistent_flags+=("--search") @@ -10538,6 +10546,10 @@ _oc_new-build() flags+=("--env=") two_word_flags+=("-e") local_nonpersistent_flags+=("--env=") + flags+=("--env-file=") + flags_with_completion+=("--env-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--env-file=") flags+=("--image-stream=") two_word_flags+=("-i") local_nonpersistent_flags+=("--image-stream=") @@ -11510,6 +11522,10 @@ _oc_process() flags+=("--param=") two_word_flags+=("-p") local_nonpersistent_flags+=("--param=") + flags+=("--param-file=") + flags_with_completion+=("--param-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--param-file=") flags+=("--parameters") local_nonpersistent_flags+=("--parameters") flags+=("--raw") diff --git a/contrib/completions/bash/openshift b/contrib/completions/bash/openshift index 45c694bbbd54..a96cd37e7dbd 100644 --- a/contrib/completions/bash/openshift +++ b/contrib/completions/bash/openshift @@ -15080,6 +15080,10 @@ _openshift_cli_new-app() flags+=("--env=") two_word_flags+=("-e") local_nonpersistent_flags+=("--env=") + flags+=("--env-file=") + flags_with_completion+=("--env-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--env-file=") flags+=("--file=") flags_with_completion+=("--file") flags_completion+=("__handle_filename_extension_flag yaml|yml|json") @@ -15114,6 +15118,10 @@ _openshift_cli_new-app() flags+=("--param=") two_word_flags+=("-p") local_nonpersistent_flags+=("--param=") + flags+=("--param-file=") + flags_with_completion+=("--param-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--param-file=") flags+=("--search") flags+=("-S") local_nonpersistent_flags+=("--search") @@ -15187,6 +15195,10 @@ _openshift_cli_new-build() flags+=("--env=") two_word_flags+=("-e") local_nonpersistent_flags+=("--env=") + flags+=("--env-file=") + flags_with_completion+=("--env-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--env-file=") flags+=("--image-stream=") two_word_flags+=("-i") local_nonpersistent_flags+=("--image-stream=") @@ -16174,6 +16186,10 @@ _openshift_cli_process() flags+=("--param=") two_word_flags+=("-p") local_nonpersistent_flags+=("--param=") + flags+=("--param-file=") + flags_with_completion+=("--param-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--param-file=") flags+=("--parameters") local_nonpersistent_flags+=("--parameters") flags+=("--raw") diff --git a/contrib/completions/zsh/oc b/contrib/completions/zsh/oc index 267c3726c4d8..d2e96504db9e 100644 --- a/contrib/completions/zsh/oc +++ b/contrib/completions/zsh/oc @@ -10593,6 +10593,10 @@ _oc_new-app() flags+=("--env=") two_word_flags+=("-e") local_nonpersistent_flags+=("--env=") + flags+=("--env-file=") + flags_with_completion+=("--env-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--env-file=") flags+=("--file=") flags_with_completion+=("--file") flags_completion+=("__handle_filename_extension_flag yaml|yml|json") @@ -10627,6 +10631,10 @@ _oc_new-app() flags+=("--param=") two_word_flags+=("-p") local_nonpersistent_flags+=("--param=") + flags+=("--param-file=") + flags_with_completion+=("--param-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--param-file=") flags+=("--search") flags+=("-S") local_nonpersistent_flags+=("--search") @@ -10699,6 +10707,10 @@ _oc_new-build() flags+=("--env=") two_word_flags+=("-e") local_nonpersistent_flags+=("--env=") + flags+=("--env-file=") + flags_with_completion+=("--env-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--env-file=") flags+=("--image-stream=") two_word_flags+=("-i") local_nonpersistent_flags+=("--image-stream=") @@ -11671,6 +11683,10 @@ _oc_process() flags+=("--param=") two_word_flags+=("-p") local_nonpersistent_flags+=("--param=") + flags+=("--param-file=") + flags_with_completion+=("--param-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--param-file=") flags+=("--parameters") local_nonpersistent_flags+=("--parameters") flags+=("--raw") diff --git a/contrib/completions/zsh/openshift b/contrib/completions/zsh/openshift index b329ba75330d..1514364fd490 100644 --- a/contrib/completions/zsh/openshift +++ b/contrib/completions/zsh/openshift @@ -15241,6 +15241,10 @@ _openshift_cli_new-app() flags+=("--env=") two_word_flags+=("-e") local_nonpersistent_flags+=("--env=") + flags+=("--env-file=") + flags_with_completion+=("--env-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--env-file=") flags+=("--file=") flags_with_completion+=("--file") flags_completion+=("__handle_filename_extension_flag yaml|yml|json") @@ -15275,6 +15279,10 @@ _openshift_cli_new-app() flags+=("--param=") two_word_flags+=("-p") local_nonpersistent_flags+=("--param=") + flags+=("--param-file=") + flags_with_completion+=("--param-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--param-file=") flags+=("--search") flags+=("-S") local_nonpersistent_flags+=("--search") @@ -15348,6 +15356,10 @@ _openshift_cli_new-build() flags+=("--env=") two_word_flags+=("-e") local_nonpersistent_flags+=("--env=") + flags+=("--env-file=") + flags_with_completion+=("--env-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--env-file=") flags+=("--image-stream=") two_word_flags+=("-i") local_nonpersistent_flags+=("--image-stream=") @@ -16335,6 +16347,10 @@ _openshift_cli_process() flags+=("--param=") two_word_flags+=("-p") local_nonpersistent_flags+=("--param=") + flags+=("--param-file=") + flags_with_completion+=("--param-file") + flags_completion+=("_filedir") + local_nonpersistent_flags+=("--param-file=") flags+=("--parameters") local_nonpersistent_flags+=("--parameters") flags+=("--raw") diff --git a/docs/man/man1/oc-new-app.1 b/docs/man/man1/oc-new-app.1 index 190002b140f6..bc14c0b616bd 100644 --- a/docs/man/man1/oc-new-app.1 +++ b/docs/man/man1/oc-new-app.1 @@ -58,6 +58,10 @@ If you provide source code, a new build will be automatically triggered. You can \fB\-e\fP, \fB\-\-env\fP=[] Specify a key\-value pair for an environment variable to set into each container. +.PP +\fB\-\-env\-file\fP=[] + File containing key\-value pairs of environment variables to set into each container. + .PP \fB\-f\fP, \fB\-\-file\fP=[] Path to a template file to use for the app. @@ -110,6 +114,10 @@ If you provide source code, a new build will be automatically triggered. You can \fB\-p\fP, \fB\-\-param\fP=[] Specify a key\-value pair (e.g., \-p FOO=BAR) to set/override a parameter value in the template. +.PP +\fB\-\-param\-file\fP=[] + File containing parameter values to set/override in the template. + .PP \fB\-S\fP, \fB\-\-search\fP=false Search all templates, image streams, and Docker images that match the arguments provided. diff --git a/docs/man/man1/oc-new-build.1 b/docs/man/man1/oc-new-build.1 index ff6d83bb0bdb..06def1f313d6 100644 --- a/docs/man/man1/oc-new-build.1 +++ b/docs/man/man1/oc-new-build.1 @@ -66,6 +66,10 @@ Once the build configuration is created a new build will be automatically trigge \fB\-e\fP, \fB\-\-env\fP=[] Specify a key\-value pair for an environment variable to set into resulting image. +.PP +\fB\-\-env\-file\fP=[] + File containing key\-value pairs of environment variables to set into each container. + .PP \fB\-\-image\fP=[] Name of an image stream to to use as a builder. (deprecated) diff --git a/docs/man/man1/oc-process.1 b/docs/man/man1/oc-process.1 index eb6cee027b2b..9583021d6fa7 100644 --- a/docs/man/man1/oc-process.1 +++ b/docs/man/man1/oc-process.1 @@ -43,6 +43,10 @@ The output of the process command is always a list of one or more resources. You \fB\-p\fP, \fB\-\-param\fP=[] Specify a key\-value pair (eg. \-p FOO=BAR) to set/override a parameter value in the template. +.PP +\fB\-\-param\-file\fP=[] + File containing template parameter values to set/override in the template. + .PP \fB\-\-parameters\fP=false Do not process but only print available parameters diff --git a/docs/man/man1/openshift-cli-new-app.1 b/docs/man/man1/openshift-cli-new-app.1 index bf5df47c1581..0f60c2068532 100644 --- a/docs/man/man1/openshift-cli-new-app.1 +++ b/docs/man/man1/openshift-cli-new-app.1 @@ -58,6 +58,10 @@ If you provide source code, a new build will be automatically triggered. You can \fB\-e\fP, \fB\-\-env\fP=[] Specify a key\-value pair for an environment variable to set into each container. +.PP +\fB\-\-env\-file\fP=[] + File containing key\-value pairs of environment variables to set into each container. + .PP \fB\-f\fP, \fB\-\-file\fP=[] Path to a template file to use for the app. @@ -110,6 +114,10 @@ If you provide source code, a new build will be automatically triggered. You can \fB\-p\fP, \fB\-\-param\fP=[] Specify a key\-value pair (e.g., \-p FOO=BAR) to set/override a parameter value in the template. +.PP +\fB\-\-param\-file\fP=[] + File containing parameter values to set/override in the template. + .PP \fB\-S\fP, \fB\-\-search\fP=false Search all templates, image streams, and Docker images that match the arguments provided. diff --git a/docs/man/man1/openshift-cli-new-build.1 b/docs/man/man1/openshift-cli-new-build.1 index 891dc4ceff19..3b247250df1b 100644 --- a/docs/man/man1/openshift-cli-new-build.1 +++ b/docs/man/man1/openshift-cli-new-build.1 @@ -66,6 +66,10 @@ Once the build configuration is created a new build will be automatically trigge \fB\-e\fP, \fB\-\-env\fP=[] Specify a key\-value pair for an environment variable to set into resulting image. +.PP +\fB\-\-env\-file\fP=[] + File containing key\-value pairs of environment variables to set into each container. + .PP \fB\-\-image\fP=[] Name of an image stream to to use as a builder. (deprecated) diff --git a/docs/man/man1/openshift-cli-process.1 b/docs/man/man1/openshift-cli-process.1 index 973ca4154b1e..55bc975268c7 100644 --- a/docs/man/man1/openshift-cli-process.1 +++ b/docs/man/man1/openshift-cli-process.1 @@ -43,6 +43,10 @@ The output of the process command is always a list of one or more resources. You \fB\-p\fP, \fB\-\-param\fP=[] Specify a key\-value pair (eg. \-p FOO=BAR) to set/override a parameter value in the template. +.PP +\fB\-\-param\-file\fP=[] + File containing template parameter values to set/override in the template. + .PP \fB\-\-parameters\fP=false Do not process but only print available parameters diff --git a/pkg/cmd/cli/cli.go b/pkg/cmd/cli/cli.go index b0a194fe9c95..36cb6c3ec37c 100644 --- a/pkg/cmd/cli/cli.go +++ b/pkg/cmd/cli/cli.go @@ -95,7 +95,7 @@ func NewCommandCLI(name, fullName string, in io.Reader, out, errout io.Writer) * cmd.NewCmdTypes(fullName, f, out), loginCmd, cmd.NewCmdRequestProject(cmd.RequestProjectRecommendedCommandName, fullName, f, out, errout), - cmd.NewCmdNewApplication(cmd.NewAppRecommendedCommandName, fullName, f, out, errout), + cmd.NewCmdNewApplication(cmd.NewAppRecommendedCommandName, fullName, f, in, out, errout), cmd.NewCmdStatus(cmd.StatusRecommendedName, fullName, fullName+" "+cmd.StatusRecommendedName, f, out), cmd.NewCmdProject(fullName+" project", f, out), cmd.NewCmdProjects(fullName, f, out), @@ -156,7 +156,7 @@ func NewCommandCLI(name, fullName string, in io.Reader, out, errout io.Writer) * cmd.NewCmdReplace(fullName, f, out), cmd.NewCmdApply(fullName, f, out), cmd.NewCmdPatch(fullName, f, out), - cmd.NewCmdProcess(fullName, f, out, errout), + cmd.NewCmdProcess(fullName, f, in, out, errout), cmd.NewCmdExport(fullName, f, in, out), cmd.NewCmdExtract(fullName, f, in, out, errout), observe.NewCmdObserve(fullName, f, out, errout), diff --git a/pkg/cmd/cli/cmd/dockerbuild/dockerbuild.go b/pkg/cmd/cli/cmd/dockerbuild/dockerbuild.go index e0cd22fded1d..e7b95f53f38b 100644 --- a/pkg/cmd/cli/cmd/dockerbuild/dockerbuild.go +++ b/pkg/cmd/cli/cmd/dockerbuild/dockerbuild.go @@ -20,6 +20,7 @@ import ( "github.com/openshift/origin/pkg/cmd/templates" cmdutil "github.com/openshift/origin/pkg/cmd/util" "github.com/openshift/origin/pkg/cmd/util/clientcmd" + "github.com/openshift/origin/pkg/generate/app" ) var ( @@ -55,7 +56,7 @@ type DockerbuildOptions struct { DockerfilePath string AllowPull bool Keyring credentialprovider.DockerKeyring - Arguments cmdutil.Environment + Arguments app.Environment } func NewCmdDockerbuild(fullName string, f *clientcmd.Factory, out, errOut io.Writer) *cobra.Command { @@ -97,7 +98,7 @@ func (o *DockerbuildOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, if len(paths) != 2 { return kcmdutil.UsageError(cmd, "the directory to build and tag must be specified") } - o.Arguments, _, _ = cmdutil.ParseEnvironmentArguments(envArgs) + o.Arguments, _, _ = app.ParseEnvironment(envArgs...) o.Directory = paths[0] o.Tag = paths[1] if len(o.DockerfilePath) == 0 { diff --git a/pkg/cmd/cli/cmd/newapp.go b/pkg/cmd/cli/cmd/newapp.go index 9be6bb5347d2..fd451bbec785 100644 --- a/pkg/cmd/cli/cmd/newapp.go +++ b/pkg/cmd/cli/cmd/newapp.go @@ -45,10 +45,6 @@ import ( // NewAppRecommendedCommandName is the recommended command name. const NewAppRecommendedCommandName = "new-app" -type usage interface { - UsageError(baseName string) string -} - var ( newAppLong = templates.LongDesc(` Create a new application by specifying source code, templates, and/or images @@ -129,6 +125,7 @@ type NewAppOptions struct { CommandPath string CommandName string + In io.Reader Out, ErrOut io.Writer Output string PrintObject func(obj runtime.Object) error @@ -136,7 +133,7 @@ type NewAppOptions struct { } // NewCmdNewApplication implements the OpenShift cli new-app command. -func NewCmdNewApplication(name, baseName string, f *clientcmd.Factory, out, errout io.Writer) *cobra.Command { +func NewCmdNewApplication(name, baseName string, f *clientcmd.Factory, in io.Reader, out, errout io.Writer) *cobra.Command { config := newcmd.NewAppConfig() config.Deploy = true o := &NewAppOptions{Config: config} @@ -148,7 +145,7 @@ func NewCmdNewApplication(name, baseName string, f *clientcmd.Factory, out, erro Example: fmt.Sprintf(newAppExample, baseName, name), SuggestFor: []string{"app", "application"}, Run: func(c *cobra.Command, args []string) { - kcmdutil.CheckErr(o.Complete(baseName, name, f, c, args, out, errout)) + kcmdutil.CheckErr(o.Complete(baseName, name, f, c, args, in, out, errout)) err := o.RunNewApp() if err == cmdutil.ErrExit { os.Exit(1) @@ -168,8 +165,12 @@ func NewCmdNewApplication(name, baseName string, f *clientcmd.Factory, out, erro cmd.Flags().StringSliceVarP(&config.TemplateFiles, "file", "f", config.TemplateFiles, "Path to a template file to use for the app.") cmd.MarkFlagFilename("file", "yaml", "yml", "json") cmd.Flags().StringArrayVarP(&config.TemplateParameters, "param", "p", config.TemplateParameters, "Specify a key-value pair (e.g., -p FOO=BAR) to set/override a parameter value in the template.") + cmd.Flags().StringArrayVar(&config.TemplateParameterFiles, "param-file", config.TemplateParameterFiles, "File containing parameter values to set/override in the template.") + cmd.MarkFlagFilename("param-file") cmd.Flags().StringSliceVar(&config.Groups, "group", config.Groups, "Indicate components that should be grouped together as +.") cmd.Flags().StringArrayVarP(&config.Environment, "env", "e", config.Environment, "Specify a key-value pair for an environment variable to set into each container.") + cmd.Flags().StringArrayVar(&config.EnvironmentFiles, "env-file", config.EnvironmentFiles, "File containing key-value pairs of environment variables to set into each container.") + cmd.MarkFlagFilename("env-file") cmd.Flags().StringVar(&config.Name, "name", "", "Set name to use for generated application artifacts") cmd.Flags().Var(&config.Strategy, "strategy", "Specify the build strategy to use if you don't want to detect (docker|pipeline|source).") cmd.Flags().StringP("labels", "l", "", "Label to set in all resources for this application.") @@ -188,12 +189,14 @@ func NewCmdNewApplication(name, baseName string, f *clientcmd.Factory, out, erro } // Complete sets any default behavior for the command -func (o *NewAppOptions) Complete(baseName, name string, f *clientcmd.Factory, c *cobra.Command, args []string, out, errout io.Writer) error { +func (o *NewAppOptions) Complete(baseName, name string, f *clientcmd.Factory, c *cobra.Command, args []string, in io.Reader, out, errout io.Writer) error { + o.In = in o.Out = out o.ErrOut = errout o.Output = kcmdutil.GetFlagString(c, "output") // Only output="" should print descriptions of intermediate steps. Everything // else should print only some specific output (json, yaml, go-template, ...) + o.Config.In = o.In if len(o.Output) == 0 { o.Config.Out = o.Out } else { diff --git a/pkg/cmd/cli/cmd/newapp_test.go b/pkg/cmd/cli/cmd/newapp_test.go index d7a1827a65c5..ae431b8db409 100644 --- a/pkg/cmd/cli/cmd/newapp_test.go +++ b/pkg/cmd/cli/cmd/newapp_test.go @@ -112,7 +112,7 @@ func TestNewAppDefaultFlags(t *testing.T) { }, } - cmd := NewCmdNewApplication("oc", NewAppRecommendedCommandName, nil, nil, nil) + cmd := NewCmdNewApplication("oc", NewAppRecommendedCommandName, nil, nil, nil, nil) for _, v := range tests { f := cmd.Flag(v.flagName) diff --git a/pkg/cmd/cli/cmd/newbuild.go b/pkg/cmd/cli/cmd/newbuild.go index 55585e8f4069..e72cea15ecb1 100644 --- a/pkg/cmd/cli/cmd/newbuild.go +++ b/pkg/cmd/cli/cmd/newbuild.go @@ -90,6 +90,7 @@ type NewBuildOptions struct { CommandPath string CommandName string + In io.Reader Out, ErrOut io.Writer Output string PrintObject func(obj runtime.Object) error @@ -129,6 +130,8 @@ func NewCmdNewBuild(name, baseName string, f *clientcmd.Factory, in io.Reader, o cmd.Flags().StringVar(&config.To, "to", "", "Push built images to this image stream tag (or Docker image repository if --to-docker is set).") cmd.Flags().BoolVar(&config.OutputDocker, "to-docker", false, "Have the build output push to a Docker repository.") cmd.Flags().StringArrayVarP(&config.Environment, "env", "e", config.Environment, "Specify a key-value pair for an environment variable to set into resulting image.") + cmd.Flags().StringArrayVar(&config.EnvironmentFiles, "env-file", config.EnvironmentFiles, "File containing key-value pairs of environment variables to set into each container.") + cmd.MarkFlagFilename("env-file") cmd.Flags().Var(&config.Strategy, "strategy", "Specify the build strategy to use if you don't want to detect (docker|pipeline|source).") cmd.Flags().StringVarP(&config.Dockerfile, "dockerfile", "D", "", "Specify the contents of a Dockerfile to build directly, implies --strategy=docker. Pass '-' to read from STDIN.") cmd.Flags().BoolVar(&config.BinaryBuild, "binary", false, "Instead of expecting a source URL, set the build to expect binary contents. Will disable triggers.") @@ -148,11 +151,13 @@ func NewCmdNewBuild(name, baseName string, f *clientcmd.Factory, in io.Reader, o // Complete sets any default behavior for the command func (o *NewBuildOptions) Complete(baseName, commandName string, f *clientcmd.Factory, c *cobra.Command, args []string, out, errout io.Writer, in io.Reader) error { + o.In = in o.Out = out o.ErrOut = errout o.Output = kcmdutil.GetFlagString(c, "output") // Only output="" should print descriptions of intermediate steps. Everything // else should print only some specific output (json, yaml, go-template, ...) + o.Config.In = in if len(o.Output) == 0 { o.Config.Out = o.Out } else { diff --git a/pkg/cmd/cli/cmd/process.go b/pkg/cmd/cli/cmd/process.go index 830d4087f070..f85cdb758d14 100644 --- a/pkg/cmd/cli/cmd/process.go +++ b/pkg/cmd/cli/cmd/process.go @@ -22,6 +22,7 @@ import ( "github.com/openshift/origin/pkg/cmd/templates" cmdutil "github.com/openshift/origin/pkg/cmd/util" "github.com/openshift/origin/pkg/cmd/util/clientcmd" + "github.com/openshift/origin/pkg/generate/app" "github.com/openshift/origin/pkg/template" templateapi "github.com/openshift/origin/pkg/template/api" ) @@ -58,14 +59,14 @@ var ( ) // NewCmdProcess implements the OpenShift cli process command -func NewCmdProcess(fullName string, f *clientcmd.Factory, out, errout io.Writer) *cobra.Command { +func NewCmdProcess(fullName string, f *clientcmd.Factory, in io.Reader, out, errout io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "process (TEMPLATE | -f FILENAME) [-v=KEY=VALUE]", Short: "Process a template into list of resources", Long: processLong, Example: fmt.Sprintf(processExample, fullName), Run: func(cmd *cobra.Command, args []string) { - err := RunProcess(f, out, errout, cmd, args) + err := RunProcess(f, in, out, errout, cmd, args) kcmdutil.CheckErr(err) }, } @@ -75,6 +76,8 @@ func NewCmdProcess(fullName string, f *clientcmd.Factory, out, errout io.Writer) cmd.Flags().MarkDeprecated("value", "Use -p, --param instead.") cmd.Flags().MarkHidden("value") 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("parameters", "", false, "Do not process but only print available parameters") cmd.Flags().StringP("labels", "l", "", "Label to set in all resources for this template") @@ -95,13 +98,13 @@ func NewCmdProcess(fullName string, f *clientcmd.Factory, out, errout io.Writer) } // RunProcess contains all the necessary functionality for the OpenShift cli process command -func RunProcess(f *clientcmd.Factory, out, errout io.Writer, cmd *cobra.Command, args []string) error { - templateName, valueArgs := "", []string{} +func RunProcess(f *clientcmd.Factory, in io.Reader, out, errout io.Writer, cmd *cobra.Command, args []string) error { + templateName, templateParams := "", []string{} for _, s := range args { isValue := strings.Contains(s, "=") switch { case isValue: - valueArgs = append(valueArgs, s) + templateParams = append(templateParams, s) case !isValue && len(templateName) == 0: templateName = s case !isValue && len(templateName) > 0: @@ -109,33 +112,23 @@ func RunProcess(f *clientcmd.Factory, out, errout io.Writer, cmd *cobra.Command, } } - keys := sets.NewString() - duplicatedKeys := sets.NewString() - - var flagValues []string if cmd.Flag("value").Changed || cmd.Flag("param").Changed { - flagValues = getFlagStringArray(cmd, "param") + flagValues := getFlagStringArray(cmd, "param") cmdutil.WarnAboutCommaSeparation(errout, flagValues, "--param") + templateParams = append(templateParams, flagValues...) } - for _, value := range flagValues { - key := strings.Split(value, "=")[0] - if keys.Has(key) { - duplicatedKeys.Insert(key) - } - keys.Insert(key) - } - - for _, value := range valueArgs { - key := strings.Split(value, "=")[0] - if keys.Has(key) { + duplicatedKeys := sets.NewString() + params, paramErr := app.ParseAndCombineEnvironment(templateParams, getFlagStringArray(cmd, "param-file"), in, func(key, file string) error { + if file == "" { duplicatedKeys.Insert(key) + } else { + fmt.Fprintf(errout, "warning: Template parameter %q already defined, ignoring value from file %q", key, file) } - keys.Insert(key) - } - + return nil + }) if len(duplicatedKeys) != 0 { - return kcmdutil.UsageError(cmd, fmt.Sprintf("The following values were provided more than once: %s", strings.Join(duplicatedKeys.List(), ", "))) + return kcmdutil.UsageError(cmd, fmt.Sprintf("The following parameters were provided more than once: %s", strings.Join(duplicatedKeys.List(), ", "))) } filename := kcmdutil.GetFlagString(cmd, "filename") @@ -248,16 +241,11 @@ func RunProcess(f *clientcmd.Factory, out, errout io.Writer, cmd *cobra.Command, } } - // Override the parameter values for the current template parameters - // when user specifies --param - if cmd.Flag("value").Changed || cmd.Flag("param").Changed { - values := getFlagStringArray(cmd, "param") - if errs := injectUserVars(values, obj); errs != nil { - return kerrors.NewAggregate(errs) - } + // Raise parameter parsing errors here after we had chance to return UsageErrors first + if paramErr != nil { + return paramErr } - - if errs := injectUserVars(valueArgs, obj); errs != nil { + if errs := injectUserVars(params, obj); errs != nil { return kerrors.NewAggregate(errs) } @@ -307,19 +295,14 @@ func RunProcess(f *clientcmd.Factory, out, errout io.Writer, cmd *cobra.Command, } // injectUserVars injects user specified variables into the Template -func injectUserVars(values []string, t *templateapi.Template) []error { +func injectUserVars(values app.Environment, t *templateapi.Template) []error { var errors []error - for _, keypair := range values { - p := strings.SplitN(keypair, "=", 2) - if len(p) != 2 { - errors = append(errors, fmt.Errorf("invalid parameter assignment in %q: %q\n", t.Name, keypair)) + for param, val := range values { + if v := template.GetParameterByName(t, param); v != nil { + v.Value = val + v.Generate = "" } else { - if v := template.GetParameterByName(t, p[0]); v != nil { - v.Value = p[1] - v.Generate = "" - } else { - errors = append(errors, fmt.Errorf("unknown parameter name %q\n", p[0])) - } + errors = append(errors, fmt.Errorf("unknown parameter name %q\n", param)) } } return errors diff --git a/pkg/cmd/cli/cmd/process_test.go b/pkg/cmd/cli/cmd/process_test.go index 42b5812a4276..32491b9cf329 100644 --- a/pkg/cmd/cli/cmd/process_test.go +++ b/pkg/cmd/cli/cmd/process_test.go @@ -13,9 +13,11 @@ func TestInjectUserVars(t *testing.T) { {Name: "parameter_foo_bar_2", Value: "value_foo_bar_2"}}, } oldParameterNum := len(template.Parameters) - testParam := []string{"parameter_foo_bar_exist=value_foo_bar_exist_new", - "parameter_foo_bar_no_exist=value_foo_bar_no_exist", - "parameter_foo_bar_error=value_foo_bar_error=value_foo_bar_error"} + testParam := map[string]string{ + "parameter_foo_bar_exist": "value_foo_bar_exist_new", + "parameter_foo_bar_no_exist": "value_foo_bar_no_exist", + "parameter_foo_bar_error": "value_foo_bar_error=value_foo_bar_error", + } errors := injectUserVars(testParam, template) if len(errors) != 2 { diff --git a/pkg/cmd/util/env.go b/pkg/cmd/util/env.go index b904b84cc874..8652532490ac 100644 --- a/pkg/cmd/util/env.go +++ b/pkg/cmd/util/env.go @@ -39,8 +39,6 @@ func GetEnv(key string) (string, bool) { return val, true } -type Environment map[string]string - var argumentEnvironment = regexp.MustCompile("(?ms)^(.+)\\=(.*)$") var validArgumentEnvironment = regexp.MustCompile("(?ms)^(\\w+)\\=(.*)$") @@ -72,25 +70,6 @@ func SplitEnvironmentFromResources(args []string) (resources, envArgs []string, return resources, envArgs, true } -func ParseEnvironmentArguments(s []string) (Environment, []string, []error) { - errs := []error{} - duplicates := []string{} - env := make(Environment) - for _, s := range s { - switch matches := validArgumentEnvironment.FindStringSubmatch(s); len(matches) { - case 3: - k, v := matches[1], matches[2] - if exist, ok := env[k]; ok { - duplicates = append(duplicates, fmt.Sprintf("%s=%s", k, exist)) - } - env[k] = v - default: - errs = append(errs, fmt.Errorf("environment variables must be of the form key=value: %s", s)) - } - } - return env, duplicates, errs -} - // ParseEnv parses the list of environment variables into kubernetes EnvVar func ParseEnv(spec []string, defaultReader io.Reader) ([]kapi.EnvVar, []string, error) { env := []kapi.EnvVar{} diff --git a/pkg/generate/app/cmd/newapp.go b/pkg/generate/app/cmd/newapp.go index 1648c1d1fcfe..8847d2178624 100644 --- a/pkg/generate/app/cmd/newapp.go +++ b/pkg/generate/app/cmd/newapp.go @@ -54,6 +54,9 @@ type GenerationInputs struct { Environment []string Labels map[string]string + TemplateParameterFiles []string + EnvironmentFiles []string + AddEnvironmentToBuild bool InsecureRegistry bool @@ -100,6 +103,7 @@ type AppConfig struct { AsList bool DryRun bool + In io.Reader Out io.Writer ErrOut io.Writer @@ -115,16 +119,6 @@ type AppConfig struct { OriginNamespace string } -// UsageError is an interface for printing usage errors -type UsageError interface { - UsageError(commandName string) string -} - -// TODO: replace with upstream converting [1]error to error -type errlist interface { - Errors() []error -} - type ErrRequiresExplicitAccess struct { Match app.ComponentMatch Input app.GeneratorInput @@ -579,27 +573,35 @@ func (c *AppConfig) RunQuery() (*QueryResult, error) { }, nil } -func (c *AppConfig) validate() (cmdutil.Environment, cmdutil.Environment, error) { - var errs []error - - env, duplicateEnv, envErrs := cmdutil.ParseEnvironmentArguments(c.Environment) - for _, s := range duplicateEnv { - fmt.Fprintf(c.ErrOut, "warning: The environment variable %q was overwritten", s) +func (c *AppConfig) validate() (app.Environment, app.Environment, error) { + env, err := app.ParseAndCombineEnvironment(c.Environment, c.EnvironmentFiles, c.In, func(key, file string) error { + if file == "" { + fmt.Fprintf(c.ErrOut, "warning: Environment variable %q was overwritten\n", key) + } else { + fmt.Fprintf(c.ErrOut, "warning: Environment variable %q already defined, ignoring value from file %q\n", key, file) + } + return nil + }) + if err != nil { + return nil, nil, err } - errs = append(errs, envErrs...) - - params, duplicateParams, paramsErrs := cmdutil.ParseEnvironmentArguments(c.TemplateParameters) - for _, s := range duplicateParams { - fmt.Fprintf(c.ErrOut, "warning: The template parameter %q was overwritten", s) + params, err := app.ParseAndCombineEnvironment(c.TemplateParameters, c.TemplateParameterFiles, c.In, func(key, file string) error { + if file == "" { + fmt.Fprintf(c.ErrOut, "warning: Template parameter %q was overwritten\n", key) + } else { + fmt.Fprintf(c.ErrOut, "warning: Template parameter %q already defined, ignoring value from file %q\n", key, file) + } + return nil + }) + if err != nil { + return nil, nil, err } - errs = append(errs, paramsErrs...) - - return env, params, kutilerrors.NewAggregate(errs) + return env, params, nil } // Run executes the provided config to generate objects. func (c *AppConfig) Run() (*AppResult, error) { - environment, parameters, err := c.validate() + env, parameters, err := c.validate() if err != nil { return nil, err } @@ -645,8 +647,6 @@ func (c *AppConfig) Run() (*AppResult, error) { return nil, errors.New("only one component with source can be used when specifying an output image reference") } - env := app.Environment(environment) - // identify if there are installable components in the input provided by the user installables, name, err := c.installComponents(components, env) if err != nil { @@ -680,7 +680,7 @@ func (c *AppConfig) Run() (*AppResult, error) { objects = app.AddServices(objects, false) - templateName, templateObjects, err := c.buildTemplates(components.TemplateComponentRefs(), app.Environment(parameters), app.Environment(environment)) + templateName, templateObjects, err := c.buildTemplates(components.TemplateComponentRefs(), parameters, env) if err != nil { return nil, err } diff --git a/pkg/generate/app/env.go b/pkg/generate/app/env.go index 9293dc22a33f..67f946a4a176 100644 --- a/pkg/generate/app/env.go +++ b/pkg/generate/app/env.go @@ -1,18 +1,25 @@ package app import ( + "fmt" + "io" + "io/ioutil" + "os" "sort" "strings" + "github.com/joho/godotenv" + cmdutil "github.com/openshift/origin/pkg/cmd/util" kapi "k8s.io/kubernetes/pkg/api" ) // Environment holds environment variables for new-app type Environment map[string]string -// ParseEnvironment converts the provided strings in key=value form into environment -// entries. -func ParseEnvironment(vals ...string) Environment { +// ParseEnvironmentAllowEmpty converts the provided strings in key=value form +// into environment entries. In case there's no equals sign in a string, it's +// considered as a key with empty value. +func ParseEnvironmentAllowEmpty(vals ...string) Environment { env := make(Environment) for _, s := range vals { if i := strings.Index(s, "="); i == -1 { @@ -24,6 +31,30 @@ func ParseEnvironment(vals ...string) Environment { return env } +// ParseEnvironment takes a slice of strings in key=value format and transforms +// them into a map. List of duplicate keys is returned in the second return +// value. +func ParseEnvironment(vals ...string) (Environment, []string, []error) { + errs := []error{} + duplicates := []string{} + env := make(Environment) + for _, s := range vals { + valid := cmdutil.IsValidEnvironmentArgument(s) + p := strings.SplitN(s, "=", 2) + if !valid || len(p) != 2 { + errs = append(errs, fmt.Errorf("invalid parameter assignment in %q", s)) + continue + } + key, val := p[0], p[1] + if _, exists := env[key]; exists { + duplicates = append(duplicates, key) + continue + } + env[key] = val + } + return env, duplicates, errs +} + // NewEnvironment returns a new set of environment variables based on all // the provided environment variables func NewEnvironment(envs ...map[string]string) Environment { @@ -44,6 +75,22 @@ func (e Environment) Add(envs ...map[string]string) { } } +// AddIfNotPresent adds the environment variables to the current environment. +// In case of key conflict the old value is kept. Conflicting keys are returned +// as a slice. +func (e Environment) AddIfNotPresent(more Environment) []string { + duplicates := []string{} + for k, v := range more { + if _, exists := e[k]; exists { + duplicates = append(duplicates, k) + } else { + e[k] = v + } + } + + return duplicates +} + // List sorts and returns all the environment variables func (e Environment) List() []kapi.EnvVar { env := []kapi.EnvVar{} @@ -82,3 +129,85 @@ func JoinEnvironment(a, b []kapi.EnvVar) (out []kapi.EnvVar) { } return out } + +// LoadEnvironmentFile accepts filename of a file containing key=value pairs +// and puts these pairs into a map. If filename is "-" the file contents are +// read from the stdin argument, provided it is not nil. +func LoadEnvironmentFile(filename string, stdin io.Reader) (Environment, error) { + errorFilename := filename + + if filename == "-" && stdin != nil { + //once https://github.com/joho/godotenv/pull/20 is merged we can get rid of using tempfile + temp, err := ioutil.TempFile("", "origin-env-stdin") + if err != nil { + return nil, fmt.Errorf("Cannot create temporary file: %s", err) + } + + filename = temp.Name() + errorFilename = "stdin" + defer os.Remove(filename) + + if _, err = io.Copy(temp, stdin); err != nil { + return nil, fmt.Errorf("Cannot write to temporary file %q: %s", filename, err) + } + temp.Close() + } + + // godotenv successfuly returns empty map when given path to a directory, + // remove this once https://github.com/joho/godotenv/pull/22 is merged + if info, err := os.Stat(filename); err == nil && info.IsDir() { + return nil, fmt.Errorf("Cannot read varaiables from %q: is a directory", filename) + } else if err != nil { + return nil, fmt.Errorf("Cannot stat %q: %s", filename, err) + } + + env, err := godotenv.Read(filename) + if err != nil { + return nil, fmt.Errorf("Cannot read variables from file %q: %s", errorFilename, err) + } + for k, v := range env { + if !cmdutil.IsValidEnvironmentArgument(fmt.Sprintf("%s=%s", k, v)) { + return nil, fmt.Errorf("invalid parameter assignment in %s=%s", k, v) + } + } + return env, nil +} + +// ParseAndCombineEnvironment parses key=value records from slice of strings +// (typically obtained from the command line) and from given files and combines +// them into single map. Key=value pairs from the envs slice have precedence +// over those read from file. +// +// The dupfn function is called for all duplicate keys that encountered. If the +// function returns an error this error is returned by +// ParseAndCombineEnvironment. +// +// If a file is "-" the file contents will be read from argument stdin (unless +// it's nil). +func ParseAndCombineEnvironment(envs []string, filenames []string, stdin io.Reader, dupfn func(string, string) error) (Environment, error) { + vars, duplicates, errs := ParseEnvironment(envs...) + if len(errs) > 0 { + return nil, errs[0] + } + for _, s := range duplicates { + if err := dupfn(s, ""); err != nil { + return nil, err + } + } + + for _, fname := range filenames { + fileVars, err := LoadEnvironmentFile(fname, stdin) + if err != nil { + return nil, err + } + + duplicates = vars.AddIfNotPresent(fileVars) + for _, s := range duplicates { + if err := dupfn(s, fname); err != nil { + return nil, err + } + } + } + + return vars, nil +} diff --git a/pkg/generate/dockercompose/generate.go b/pkg/generate/dockercompose/generate.go index 05f771f7c4c1..e5710a5f9847 100644 --- a/pkg/generate/dockercompose/generate.go +++ b/pkg/generate/dockercompose/generate.go @@ -280,7 +280,7 @@ func Generate(paths ...string) (*templateapi.Template, error) { if len(v.WorkingDir) > 0 { c.WorkingDir = v.WorkingDir } - c.Env = append(c.Env, app.ParseEnvironment(v.Environment.Slice()...).List()...) + c.Env = append(c.Env, app.ParseEnvironmentAllowEmpty(v.Environment.Slice()...).List()...) if uid, err := strconv.Atoi(v.User); err == nil { uid64 := int64(uid) if c.SecurityContext == nil { diff --git a/test/cmd/newapp.sh b/test/cmd/newapp.sh index ec4c9af4bf4f..8485b1853c83 100755 --- a/test/cmd/newapp.sh +++ b/test/cmd/newapp.sh @@ -82,6 +82,34 @@ os::cmd::expect_success_and_not_text 'oc new-app ruby-helloworld-sample --param os::cmd::expect_success_and_text 'oc new-app php PASS=one,two=three -o yaml' 'value: one,two=three' os::cmd::expect_success_and_not_text 'oc new-app php PASS=one,two=three -o yaml' 'no longer accepts comma-separated list' +# check that we can populate template parameters from file +param_file="${OS_ROOT}/test/testdata/test-cmd-newapp-params.env" +os::cmd::expect_success_and_text "oc new-app ruby-helloworld-sample --param-file ${param_file} -o jsonpath='{.items[?(@.kind==\"DeploymentConfig\")].spec.template.spec.containers[0].env[?(@.name==\"MYSQL_PASSWORD\")].value}'" 'thisisapassword' +os::cmd::expect_success_and_text "oc new-app ruby-helloworld-sample --param-file ${param_file} --param MYSQL_PASSWORD=otherpass -o jsonpath='{.items[?(@.kind==\"DeploymentConfig\")].spec.template.spec.containers[0].env[?(@.name==\"MYSQL_PASSWORD\")].value}'" 'otherpass' +os::cmd::expect_success_and_text "oc new-app ruby-helloworld-sample --param-file ${param_file} --param MYSQL_PASSWORD=otherpass -o yaml" 'ignoring value from file' +os::cmd::expect_success_and_text "cat ${param_file} | oc new-app ruby-helloworld-sample --param-file - -o jsonpath='{.items[?(@.kind==\"DeploymentConfig\")].spec.template.spec.containers[0].env[?(@.name==\"MYSQL_PASSWORD\")].value}'" 'thisisapassword' + +os::cmd::expect_failure_and_text "oc new-app ruby-helloworld-sample --param-file does/not/exist" 'no such file or directory' +os::cmd::expect_failure_and_text "oc new-app ruby-helloworld-sample --param-file test/testdata" 'is a directory' +os::cmd::expect_success "oc new-app ruby-helloworld-sample --param-file /dev/null -o yaml" +os::cmd::expect_success "oc new-app ruby-helloworld-sample --param-file /dev/null --param-file ${param_file} -o yaml" +os::cmd::expect_failure_and_text "echo 'fo%(o=bar' | oc new-app ruby-helloworld-sample --param-file -" 'invalid parameter assignment' +os::cmd::expect_failure_and_text "echo 'S P A C E S=test' | oc new-app ruby-helloworld-sample --param-file -" 'invalid parameter assignment' + +# check that we can set environment variables from env file +env_file="${OS_ROOT}/test/testdata/test-cmd-newapp-env.env" +os::cmd::expect_success_and_text "oc new-app php --env-file ${env_file} -o jsonpath='{.items[?(@.kind==\"DeploymentConfig\")].spec.template.spec.containers[0].env[?(@.name==\"SOME_VAR\")].value}'" 'envvarfromfile' +os::cmd::expect_success_and_text "oc new-app php --env-file ${env_file} --env SOME_VAR=fromcmdline -o jsonpath='{.items[?(@.kind==\"DeploymentConfig\")].spec.template.spec.containers[0].env[?(@.name==\"SOME_VAR\")].value}'" 'fromcmdline' +os::cmd::expect_success_and_text "oc new-app php --env-file ${env_file} --env SOME_VAR=fromcmdline -o yaml" 'ignoring value from file' +os::cmd::expect_success_and_text "cat ${env_file} | oc new-app php --env-file - -o jsonpath='{.items[?(@.kind==\"DeploymentConfig\")].spec.template.spec.containers[0].env[?(@.name==\"SOME_VAR\")].value}'" 'envvarfromfile' + +os::cmd::expect_failure_and_text "oc new-app php --env-file does/not/exist" 'no such file or directory' +os::cmd::expect_failure_and_text "oc new-app php --env-file test/testdata" 'is a directory' +os::cmd::expect_success "oc new-app php --env-file /dev/null -o yaml" +os::cmd::expect_success "oc new-app php --env-file /dev/null --env-file ${env_file} -o yaml" +os::cmd::expect_failure_and_text "echo 'fo%(o=bar' | oc new-app php --env-file -" 'invalid parameter assignment' +os::cmd::expect_failure_and_text "echo 'S P A C E S=test' | oc new-app php --env-file -" 'invalid parameter assignment' + # new-build # check that env vars are not split on commas and warning is printed where they previously have os::cmd::expect_success_and_text 'oc new-build --binary php --env X=Y,Z=W -o yaml' 'value: Y,Z=W' @@ -92,6 +120,19 @@ os::cmd::expect_success_and_not_text 'oc new-build --binary php --env X=Y -o yam os::cmd::expect_success_and_text 'oc new-build --binary php X=Y,Z=W -o yaml' 'value: Y,Z=W' os::cmd::expect_success_and_not_text 'oc new-build --binary php X=Y,Z=W -o yaml' 'no longer accepts comma-separated list' +# new-build - load envvars from file +os::cmd::expect_success_and_text "oc new-build --binary php --env-file ${env_file} -o jsonpath='{.items[?(@.kind==\"BuildConfig\")].spec.strategy.sourceStrategy.env[?(@.name==\"SOME_VAR\")].value}'" 'envvarfromfile' +os::cmd::expect_success_and_text "oc new-build --binary php --env-file ${env_file} --env SOME_VAR=fromcmdline -o jsonpath='{.items[?(@.kind==\"BuildConfig\")].spec.strategy.sourceStrategy.env[?(@.name==\"SOME_VAR\")].value}'" 'fromcmdline' +os::cmd::expect_success_and_text "oc new-build --binary php --env-file ${env_file} --env SOME_VAR=fromcmdline -o yaml" 'ignoring value from file' +os::cmd::expect_success_and_text "cat ${env_file} | oc new-build --binary php --env-file ${env_file} -o jsonpath='{.items[?(@.kind==\"BuildConfig\")].spec.strategy.sourceStrategy.env[?(@.name==\"SOME_VAR\")].value}'" 'envvarfromfile' + +os::cmd::expect_failure_and_text "oc new-build --binary php --env-file does/not/exist" 'no such file or directory' +os::cmd::expect_failure_and_text "oc new-build --binary php --env-file test/testdata" 'is a directory' +os::cmd::expect_success "oc new-build --binary php --env-file /dev/null -o yaml" +os::cmd::expect_success "oc new-build --binary php --env-file /dev/null --env-file ${env_file} -o yaml" +os::cmd::expect_failure_and_text "echo 'fo%(o=bar' | oc new-build --binary php --env-file -" 'invalid parameter assignment' +os::cmd::expect_failure_and_text "echo 'S P A C E S=test' | oc new-build --binary php --env-file -" 'invalid parameter assignment' + # verify we can create from a template when some objects in the template declare an app label # the app label will not be applied to any objects in the template. os::cmd::expect_success_and_not_text 'oc new-app -f test/testdata/template-with-app-label.json -o yaml' 'app: ruby-helloworld-sample' diff --git a/test/cmd/process.sh b/test/cmd/process.sh deleted file mode 100755 index bd495bf0ebe0..000000000000 --- a/test/cmd/process.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/bash -source "$(dirname "${BASH_SOURCE}")/../../hack/lib/init.sh" -trap os::test::junit::reconcile_output EXIT - -# Cleanup cluster resources created by this test -( - set +e - oc delete all,templates --all - oc delete user someval - exit 0 -) &>/dev/null - -os::test::junit::declare_suite_start "cmd/process" -# This test validates oc process - -# fail to process two templates by name -os::cmd::expect_failure_and_text 'oc process name1 name2' 'template name must be specified only once' -# fail to pass a filename or template by name -os::cmd::expect_failure_and_text 'oc process' 'Must pass a filename or name of stored template' -# can't ask for parameters and try process the template (include tests for deprecated -v/--value) -os::cmd::expect_failure_and_text 'oc process template-name --parameters --value=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-value' -os::cmd::expect_failure_and_text 'oc process template-name --parameters -v someval' '\-\-parameters flag does not process the template, can.t be used with \-\-value' -os::cmd::expect_failure_and_text 'oc process template-name --parameters --param=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-param' -os::cmd::expect_failure_and_text 'oc process template-name --parameters -p someval' '\-\-parameters flag does not process the template, can.t be used with \-\-param' -os::cmd::expect_failure_and_text 'oc process template-name --parameters --labels=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-labels' -os::cmd::expect_failure_and_text 'oc process template-name --parameters -l someval' '\-\-parameters flag does not process the template, can.t be used with \-\-labels' -os::cmd::expect_failure_and_text 'oc process template-name --parameters --output=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-output' -os::cmd::expect_failure_and_text 'oc process template-name --parameters -o someval' '\-\-parameters flag does not process the template, can.t be used with \-\-output' -os::cmd::expect_failure_and_text 'oc process template-name --parameters --output-version=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-output-version' -os::cmd::expect_failure_and_text 'oc process template-name --parameters --raw' '\-\-parameters flag does not process the template, can.t be used with \-\-raw' -os::cmd::expect_failure_and_text 'oc process template-name --parameters --template=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-template' -os::cmd::expect_failure_and_text 'oc process template-name --parameters -t someval' '\-\-parameters flag does not process the template, can.t be used with \-\-template' - -# providing a value more than once should fail (include tests for deprecated -v/--value) -os::cmd::expect_failure_and_text 'oc process template-name key=value key=value' 'provided more than once: key' -os::cmd::expect_failure_and_text 'oc process template-name --value=key=value --value=key=value' 'provided more than once: key' -os::cmd::expect_failure_and_text 'oc process template-name --param=key=value --param=key=value' 'provided more than once: key' -os::cmd::expect_failure_and_text 'oc process template-name key=value --value=key=value' 'provided more than once: key' -os::cmd::expect_failure_and_text 'oc process template-name key=value --param=key=value' 'provided more than once: key' -os::cmd::expect_failure_and_text 'oc process template-name key=value other=foo --value=key=value --value=other=baz' 'provided more than once: key, other' -os::cmd::expect_failure_and_text 'oc process template-name key=value other=foo --param=key=value --param=other=baz' 'provided more than once: key, other' - -required_params="${OS_ROOT}/test/testdata/template_required_params.yaml" - -# providing something other than a template is not OK -os::cmd::expect_failure_and_text "oc process -f '${OS_ROOT}/test/testdata/basic-users-binding.json'" 'not a valid Template but' - -# not providing required parameter should fail -os::cmd::expect_failure_and_text "oc process -f '${required_params}'" 'parameter required_param is required and must be specified' -# not providing an optional param is OK -os::cmd::expect_success "oc process -f '${required_params}' --value=required_param=someval" -os::cmd::expect_success "oc process -f '${required_params}' -v required_param=someval" -os::cmd::expect_success "oc process -f '${required_params}' --param=required_param=someval" -os::cmd::expect_success "oc process -f '${required_params}' -p required_param=someval | oc create -f -" -# parameters with multiple equal signs are OK -os::cmd::expect_success "oc process -f '${required_params}' required_param=someval=moreval | oc create -f -" -os::cmd::expect_success "oc process -f '${required_params}' -v required_param=someval=moreval2 | oc create -f -" -os::cmd::expect_success "oc process -f '${required_params}' -p required_param=someval=moreval3 | oc create -f -" -# we should have overwritten the template param -os::cmd::expect_success_and_text 'oc get user someval -o jsonpath={.Name}' 'someval' -os::cmd::expect_success_and_text 'oc get user someval=moreval -o jsonpath={.Name}' 'someval=moreval' -os::cmd::expect_success_and_text 'oc get user someval=moreval2 -o jsonpath={.Name}' 'someval=moreval2' -os::cmd::expect_success_and_text 'oc get user someval=moreval3 -o jsonpath={.Name}' 'someval=moreval3' -# providing a value not in the template should fail -os::cmd::expect_failure_and_text "oc process -f '${required_params}' --value=required_param=someval --value=other_param=otherval" 'unknown parameter name "other_param"' -os::cmd::expect_failure_and_text "oc process -f '${required_params}' --param=required_param=someval --param=other_param=otherval" 'unknown parameter name "other_param"' -# failure on values fails the entire call -os::cmd::expect_failure_and_text "oc process -f '${required_params}' --value=required_param=someval --value=optional_param" 'invalid parameter assignment in' -os::cmd::expect_failure_and_text "oc process -f '${required_params}' --param=required_param=someval --param=optional_param" 'invalid parameter assignment in' -# failure on labels fails the entire call -os::cmd::expect_failure_and_text "oc process -f '${required_params}' --value=required_param=someval --labels======" 'error parsing labels' -os::cmd::expect_failure_and_text "oc process -f '${required_params}' --param=required_param=someval --labels======" 'error parsing labels' - -# values are not split on commas, required parameter is not recognized -os::cmd::expect_failure_and_text "oc process -f '${required_params}' --value=optional_param=a,required_param=b" 'parameter required_param is required and must be specified' -os::cmd::expect_failure_and_text "oc process -f '${required_params}' --param=optional_param=a,required_param=b" 'parameter required_param is required and must be specified' -# warning is printed iff --value/--param looks like two k-v pairs separated by comma -os::cmd::expect_success_and_text "oc process -f '${required_params}' --value=required_param=a,b=c,d" 'no longer accepts comma-separated list' -os::cmd::expect_success_and_not_text "oc process -f '${required_params}' --value=required_param=a_b_c_d" 'no longer accepts comma-separated list' -os::cmd::expect_success_and_not_text "oc process -f '${required_params}' --value=required_param=a,b,c,d" 'no longer accepts comma-separated list' -os::cmd::expect_success_and_text "oc process -f '${required_params}' --param=required_param=a,b=c,d" 'no longer accepts comma-separated list' -os::cmd::expect_success_and_not_text "oc process -f '${required_params}' --param=required_param=a_b_c_d" 'no longer accepts comma-separated list' -os::cmd::expect_success_and_not_text "oc process -f '${required_params}' --param=required_param=a,b,c,d" 'no longer accepts comma-separated list' -# warning is not printed for template values passed as positional arguments -os::cmd::expect_success_and_not_text "oc process -f '${required_params}' required_param=a,b=c,d" 'no longer accepts comma-separated list' - -# set template parameter to contents of file -os::cmd::expect_success_and_text "oc process -f '${required_params}' --value=required_param='`cat ${OS_ROOT}/test/testdata/multiline.txt`'" 'also,with=commas' -os::cmd::expect_success_and_text "oc process -f '${required_params}' --param=required_param='`cat ${OS_ROOT}/test/testdata/multiline.txt`'" 'also,with=commas' - - -echo "process: ok" -os::test::junit::declare_suite_end \ No newline at end of file diff --git a/test/cmd/templates.sh b/test/cmd/templates.sh index 45080c538a68..da8e8ef8368d 100755 --- a/test/cmd/templates.sh +++ b/test/cmd/templates.sh @@ -8,6 +8,7 @@ trap os::test::junit::reconcile_output EXIT oc delete all,templates --all oc delete template/ruby-helloworld-sample -n openshift oc delete project test-template-project + oc delete user someval someval=moreval someval=moreval2 someval=moreval3 exit 0 ) &>/dev/null @@ -41,25 +42,49 @@ echo "templates: ok" os::test::junit::declare_suite_end os::test::junit::declare_suite_start "cmd/templates/config" -os::cmd::expect_success 'oc process -f test/templates/testdata/guestbook.json -l app=guestbook | oc create -f -' +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_end os::test::junit::declare_suite_start "cmd/templates/parameters" +guestbook_params="${OS_ROOT}/test/templates/testdata/guestbook.env" # Individually specified parameter values are honored -os::cmd::expect_success_and_text 'oc process -f test/templates/testdata/guestbook.json -p ADMIN_USERNAME=myuser -p ADMIN_PASSWORD=mypassword' '"myuser"' -os::cmd::expect_success_and_text 'oc process -f test/templates/testdata/guestbook.json -p ADMIN_USERNAME=myuser -p ADMIN_PASSWORD=mypassword' '"mypassword"' +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' -p ADMIN_USERNAME=myuser -p ADMIN_PASSWORD=mypassword" '"myuser"' +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' -p ADMIN_USERNAME=myuser -p ADMIN_PASSWORD=mypassword" '"mypassword"' # Argument values are honored -os::cmd::expect_success_and_text 'oc process ADMIN_USERNAME=myuser ADMIN_PASSWORD=mypassword -f test/templates/testdata/guestbook.json' '"myuser"' -os::cmd::expect_success_and_text 'oc process -f test/templates/testdata/guestbook.json ADMIN_USERNAME=myuser ADMIN_PASSWORD=mypassword' '"mypassword"' +os::cmd::expect_success_and_text "oc process ADMIN_USERNAME=myuser ADMIN_PASSWORD=mypassword -f '${guestbook_template}'" '"myuser"' +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' ADMIN_USERNAME=myuser ADMIN_PASSWORD=mypassword" '"mypassword"' # Argument values with commas are honored os::cmd::expect_success 'oc create -f examples/sample-app/application-template-stibuild.json' os::cmd::expect_success_and_text 'oc process ruby-helloworld-sample MYSQL_USER=myself MYSQL_PASSWORD=my,1%pa=s' '"myself"' os::cmd::expect_success_and_text 'oc process MYSQL_USER=myself MYSQL_PASSWORD=my,1%pa=s ruby-helloworld-sample' '"my,1%pa=s"' os::cmd::expect_success_and_text 'oc process ruby-helloworld-sample -p MYSQL_USER=myself -p MYSQL_PASSWORD=my,1%pa=s' '"myself"' os::cmd::expect_success_and_text 'oc process -p MYSQL_USER=myself -p MYSQL_PASSWORD=my,1%pa=s ruby-helloworld-sample' '"my,1%pa=s"' +# Argument values can be read from file +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' --param-file='${guestbook_params}'" '"root"' +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' --param-file='${guestbook_params}'" '"adminpass"' +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' --param-file='${guestbook_params}'" '"redispass"' +# Argument values can be read from stdin +os::cmd::expect_success_and_text "cat '${guestbook_params}' | oc process -f '${guestbook_template}' --param-file=-" '"root"' +os::cmd::expect_success_and_text "cat '${guestbook_params}' | oc process -f '${guestbook_template}' --param-file=-" '"adminpass"' +os::cmd::expect_success_and_text "cat '${guestbook_params}' | oc process -f '${guestbook_template}' --param-file=-" '"redispass"' +# Argument values from command line have precedence over those from file +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' --param-file='${guestbook_params}' -p ADMIN_USERNAME=myuser" 'ignoring value from file' +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' --param-file='${guestbook_params}' -p ADMIN_USERNAME=myuser" '"myuser"' +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' --param-file='${guestbook_params}' -p ADMIN_PASSWORD=mypassword" '"mypassword"' +os::cmd::expect_success_and_text "oc process -f '${guestbook_template}' --param-file='${guestbook_params}' -p REDIS_PASSWORD=rrr" '"rrr"' +# Set template parameters from parameter file with multiline values +os::cmd::expect_success_and_text "oc process -f test/testdata/template_required_params.yaml --param-file=test/testdata/template_required_params.env -o yaml" 'first$' os::cmd::expect_success 'oc delete template ruby-helloworld-sample' +# Parameter file failure cases +os::cmd::expect_failure_and_text "oc process -f test/testdata/template_required_params.yaml --param-file=does/not/exist" 'no such file or directory' +os::cmd::expect_failure_and_text "oc process -f test/testdata/template_required_params.yaml --param-file=test/testdata" 'is a directory' +os::cmd::expect_failure_and_text "oc process -f test/testdata/template_required_params.yaml --param-file=/dev/null" 'parameter required_param is required and must be specified' +os::cmd::expect_success "oc process -f '${guestbook_template}' --param-file=/dev/null --param-file='${guestbook_params}'" +os::cmd::expect_failure_and_text "echo 'fo%(o=bar' | oc process -f test/testdata/template_required_params.yaml --param-file=-" 'invalid parameter assignment' +os::cmd::expect_failure_and_text "echo 'S P A C E S=test' | oc process -f test/testdata/template_required_params.yaml --param-file=-" 'invalid parameter assignment' echo "template+parameters: ok" os::test::junit::declare_suite_end @@ -92,6 +117,7 @@ os::cmd::expect_success 'oc create -f examples/sample-app/application-template-d os::cmd::expect_success 'oc policy add-role-to-user admin test-user' new="$(mktemp -d)/tempconfig" os::cmd::expect_success "oc config view --raw > ${new}" +old="${KUBECONFIG}" export KUBECONFIG=${new} os::cmd::expect_success 'oc login -u test-user -p password' os::cmd::expect_success 'oc new-project test-template-project' @@ -102,7 +128,81 @@ os::cmd::expect_success 'oc process template/ruby-helloworld-sample' os::cmd::expect_success 'oc process templates/ruby-helloworld-sample' os::cmd::expect_success 'oc process openshift//ruby-helloworld-sample' os::cmd::expect_success 'oc process openshift/template/ruby-helloworld-sample' +export KUBECONFIG=${old} echo "processing templates in different namespace: ok" os::test::junit::declare_suite_end +os::test::junit::declare_suite_start "cmd/templates/process" +# This test validates oc process +# fail to process two templates by name +os::cmd::expect_failure_and_text 'oc process name1 name2' 'template name must be specified only once' +# fail to pass a filename or template by name +os::cmd::expect_failure_and_text 'oc process' 'Must pass a filename or name of stored template' +# can't ask for parameters and try process the template (include tests for deprecated -v/--value) +os::cmd::expect_failure_and_text 'oc process template-name --parameters --value=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-value' +os::cmd::expect_failure_and_text 'oc process template-name --parameters -v someval' '\-\-parameters flag does not process the template, can.t be used with \-\-value' +os::cmd::expect_failure_and_text 'oc process template-name --parameters --param=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-param' +os::cmd::expect_failure_and_text 'oc process template-name --parameters -p someval' '\-\-parameters flag does not process the template, can.t be used with \-\-param' +os::cmd::expect_failure_and_text 'oc process template-name --parameters --labels=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-labels' +os::cmd::expect_failure_and_text 'oc process template-name --parameters -l someval' '\-\-parameters flag does not process the template, can.t be used with \-\-labels' +os::cmd::expect_failure_and_text 'oc process template-name --parameters --output=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-output' +os::cmd::expect_failure_and_text 'oc process template-name --parameters -o someval' '\-\-parameters flag does not process the template, can.t be used with \-\-output' +os::cmd::expect_failure_and_text 'oc process template-name --parameters --output-version=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-output-version' +os::cmd::expect_failure_and_text 'oc process template-name --parameters --raw' '\-\-parameters flag does not process the template, can.t be used with \-\-raw' +os::cmd::expect_failure_and_text 'oc process template-name --parameters --template=someval' '\-\-parameters flag does not process the template, can.t be used with \-\-template' +os::cmd::expect_failure_and_text 'oc process template-name --parameters -t someval' '\-\-parameters flag does not process the template, can.t be used with \-\-template' +# providing a value more than once should fail (include tests for deprecated -v/--value) +os::cmd::expect_failure_and_text 'oc process template-name key=value key=value' 'provided more than once: key' +os::cmd::expect_failure_and_text 'oc process template-name --value=key=value --value=key=value' 'provided more than once: key' +os::cmd::expect_failure_and_text 'oc process template-name --param=key=value --param=key=value' 'provided more than once: key' +os::cmd::expect_failure_and_text 'oc process template-name key=value --value=key=value' 'provided more than once: key' +os::cmd::expect_failure_and_text 'oc process template-name key=value --param=key=value' 'provided more than once: key' +os::cmd::expect_failure_and_text 'oc process template-name key=value other=foo --value=key=value --value=other=baz' 'provided more than once: key, other' +os::cmd::expect_failure_and_text 'oc process template-name key=value other=foo --param=key=value --param=other=baz' 'provided more than once: key, other' +required_params="${OS_ROOT}/test/testdata/template_required_params.yaml" +# providing something other than a template is not OK +os::cmd::expect_failure_and_text "oc process -f '${OS_ROOT}/test/testdata/basic-users-binding.json'" 'not a valid Template but' +# not providing required parameter should fail +os::cmd::expect_failure_and_text "oc process -f '${required_params}'" 'parameter required_param is required and must be specified' +# not providing an optional param is OK +os::cmd::expect_success "oc process -f '${required_params}' --value=required_param=someval" +os::cmd::expect_success "oc process -f '${required_params}' -v required_param=someval" +os::cmd::expect_success "oc process -f '${required_params}' --param=required_param=someval" +os::cmd::expect_success "oc process -f '${required_params}' -p required_param=someval | oc create -f -" +# parameters with multiple equal signs are OK +os::cmd::expect_success "oc process -f '${required_params}' required_param=someval=moreval | oc create -f -" +os::cmd::expect_success "oc process -f '${required_params}' -v required_param=someval=moreval2 | oc create -f -" +os::cmd::expect_success "oc process -f '${required_params}' -p required_param=someval=moreval3 | oc create -f -" +# we should have overwritten the template param +os::cmd::expect_success_and_text 'oc get user someval -o jsonpath={.Name}' 'someval' +os::cmd::expect_success_and_text 'oc get user someval=moreval -o jsonpath={.Name}' 'someval=moreval' +os::cmd::expect_success_and_text 'oc get user someval=moreval2 -o jsonpath={.Name}' 'someval=moreval2' +os::cmd::expect_success_and_text 'oc get user someval=moreval3 -o jsonpath={.Name}' 'someval=moreval3' +# providing a value not in the template should fail +os::cmd::expect_failure_and_text "oc process -f '${required_params}' --value=required_param=someval --value=other_param=otherval" 'unknown parameter name "other_param"' +os::cmd::expect_failure_and_text "oc process -f '${required_params}' --param=required_param=someval --param=other_param=otherval" 'unknown parameter name "other_param"' +# failure on values fails the entire call +os::cmd::expect_failure_and_text "oc process -f '${required_params}' --value=required_param=someval --value=optional_param" 'invalid parameter assignment in' +os::cmd::expect_failure_and_text "oc process -f '${required_params}' --param=required_param=someval --param=optional_param" 'invalid parameter assignment in' +# failure on labels fails the entire call +os::cmd::expect_failure_and_text "oc process -f '${required_params}' --value=required_param=someval --labels======" 'error parsing labels' +os::cmd::expect_failure_and_text "oc process -f '${required_params}' --param=required_param=someval --labels======" 'error parsing labels' +# values are not split on commas, required parameter is not recognized +os::cmd::expect_failure_and_text "oc process -f '${required_params}' --value=optional_param=a,required_param=b" 'parameter required_param is required and must be specified' +os::cmd::expect_failure_and_text "oc process -f '${required_params}' --param=optional_param=a,required_param=b" 'parameter required_param is required and must be specified' +# warning is printed iff --value/--param looks like two k-v pairs separated by comma +os::cmd::expect_success_and_text "oc process -f '${required_params}' --value=required_param=a,b=c,d" 'no longer accepts comma-separated list' +os::cmd::expect_success_and_not_text "oc process -f '${required_params}' --value=required_param=a_b_c_d" 'no longer accepts comma-separated list' +os::cmd::expect_success_and_not_text "oc process -f '${required_params}' --value=required_param=a,b,c,d" 'no longer accepts comma-separated list' +os::cmd::expect_success_and_text "oc process -f '${required_params}' --param=required_param=a,b=c,d" 'no longer accepts comma-separated list' +os::cmd::expect_success_and_not_text "oc process -f '${required_params}' --param=required_param=a_b_c_d" 'no longer accepts comma-separated list' +os::cmd::expect_success_and_not_text "oc process -f '${required_params}' --param=required_param=a,b,c,d" 'no longer accepts comma-separated list' +# warning is not printed for template values passed as positional arguments +os::cmd::expect_success_and_not_text "oc process -f '${required_params}' required_param=a,b=c,d" 'no longer accepts comma-separated list' +# set template parameter to contents of file +os::cmd::expect_success_and_text "oc process -f '${required_params}' --value=required_param='`cat ${OS_ROOT}/test/testdata/multiline.txt`'" 'also,with=commas' +os::cmd::expect_success_and_text "oc process -f '${required_params}' --param=required_param='`cat ${OS_ROOT}/test/testdata/multiline.txt`'" 'also,with=commas' +echo "process: ok" +os::test::junit::declare_suite_end + os::test::junit::declare_suite_end diff --git a/test/templates/testdata/guestbook.env b/test/templates/testdata/guestbook.env new file mode 100644 index 000000000000..a0e9433e03d9 --- /dev/null +++ b/test/templates/testdata/guestbook.env @@ -0,0 +1,3 @@ +ADMIN_USERNAME=root +ADMIN_PASSWORD="adminpass" +REDIS_PASSWORD='redispass' diff --git a/test/testdata/template_required_params.env b/test/testdata/template_required_params.env new file mode 100644 index 000000000000..0a900a8ecba1 --- /dev/null +++ b/test/testdata/template_required_params.env @@ -0,0 +1,2 @@ +required_param="first\nsecond" +optional_param=foo diff --git a/test/testdata/test-cmd-newapp-env.env b/test/testdata/test-cmd-newapp-env.env new file mode 100644 index 000000000000..3c9297102060 --- /dev/null +++ b/test/testdata/test-cmd-newapp-env.env @@ -0,0 +1 @@ +SOME_VAR=envvarfromfile diff --git a/test/testdata/test-cmd-newapp-params.env b/test/testdata/test-cmd-newapp-params.env new file mode 100644 index 000000000000..4186ef2c69de --- /dev/null +++ b/test/testdata/test-cmd-newapp-params.env @@ -0,0 +1,2 @@ +MYSQL_USER='mysql' +MYSQL_PASSWORD="thisisapassword" diff --git a/vendor/github.com/joho/godotenv/.gitignore b/vendor/github.com/joho/godotenv/.gitignore new file mode 100644 index 000000000000..e43b0f988953 --- /dev/null +++ b/vendor/github.com/joho/godotenv/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/vendor/github.com/joho/godotenv/LICENCE b/vendor/github.com/joho/godotenv/LICENCE new file mode 100644 index 000000000000..e7ddd51be903 --- /dev/null +++ b/vendor/github.com/joho/godotenv/LICENCE @@ -0,0 +1,23 @@ +Copyright (c) 2013 John Barton + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/github.com/joho/godotenv/README.md b/vendor/github.com/joho/godotenv/README.md new file mode 100644 index 000000000000..05c47e6f5e64 --- /dev/null +++ b/vendor/github.com/joho/godotenv/README.md @@ -0,0 +1,127 @@ +# GoDotEnv [![wercker status](https://app.wercker.com/status/507594c2ec7e60f19403a568dfea0f78 "wercker status")](https://app.wercker.com/project/bykey/507594c2ec7e60f19403a568dfea0f78) + +A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file) + +From the original Library: + +> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables. +> +> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped. + +It can be used as a library (for loading in env for your own daemons etc) or as a bin command. + +There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows. + +## Installation + +As a library + +```shell +go get github.com/joho/godotenv +``` + +or if you want to use it as a bin command +```shell +go get github.com/joho/godotenv/cmd/godotenv +``` + +## Usage + +Add your application configuration to your `.env` file in the root of your project: + +```shell +S3_BUCKET=YOURS3BUCKET +SECRET_KEY=YOURSECRETKEYGOESHERE +``` + +Then in your Go app you can do something like + +```go +package main + +import ( + "github.com/joho/godotenv" + "log" + "os" +) + +func main() { + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + + s3Bucket := os.Getenv("S3_BUCKET") + secretKey := os.Getenv("SECRET_KEY") + + // now do something with s3 or whatever +} +``` + +If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import + +```go +import _ "github.com/joho/godotenv/autoload" +``` + +While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit + +```go +_ = godotenv.Load("somerandomfile") +_ = godotenv.Load("filenumberone.env", "filenumbertwo.env") +``` + +If you want to be really fancy with your env file you can do comments and exports (below is a valid env file) + +```shell +# I am a comment and that is OK +SOME_VAR=someval +FOO=BAR # comments at line end are OK too +export BAR=BAZ +``` + +Or finally you can do YAML(ish) style + +```yaml +FOO: bar +BAR: baz +``` + +as a final aside, if you don't want godotenv munging your env you can just get a map back instead + +```go +var myEnv map[string]string +myEnv, err := godotenv.Read() + +s3Bucket := myEnv["S3_BUCKET"] +``` + +### Command Mode + +Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH` + +``` +godotenv -f /some/path/to/.env some_command with some args +``` + +If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD` + +## Contributing + +Contributions are most welcome! The parser itself is pretty stupidly naive and I wouldn't be surprised if it breaks with edge cases. + +*code changes without tests will not be accepted* + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Added some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + +## CI + +Linux: [![wercker status](https://app.wercker.com/status/507594c2ec7e60f19403a568dfea0f78/m "wercker status")](https://app.wercker.com/project/bykey/507594c2ec7e60f19403a568dfea0f78) Windows: [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4)](https://ci.appveyor.com/project/joho/godotenv) + +## Who? + +The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](http://whoisjohnbarton.com) based off the tests/fixtures in the original library. diff --git a/vendor/github.com/joho/godotenv/autoload/autoload.go b/vendor/github.com/joho/godotenv/autoload/autoload.go new file mode 100644 index 000000000000..fbcd2bdf8e94 --- /dev/null +++ b/vendor/github.com/joho/godotenv/autoload/autoload.go @@ -0,0 +1,15 @@ +package autoload + +/* + You can just read the .env file on import just by doing + + import _ "github.com/joho/godotenv/autoload" + + And bob's your mother's brother +*/ + +import "github.com/joho/godotenv" + +func init() { + godotenv.Load() +} diff --git a/vendor/github.com/joho/godotenv/cmd/godotenv/cmd.go b/vendor/github.com/joho/godotenv/cmd/godotenv/cmd.go new file mode 100644 index 000000000000..04a9f6497fde --- /dev/null +++ b/vendor/github.com/joho/godotenv/cmd/godotenv/cmd.go @@ -0,0 +1,54 @@ +package main + +import ( + "flag" + "fmt" + "log" + + "strings" + + "github.com/joho/godotenv" +) + +func main() { + var showHelp bool + flag.BoolVar(&showHelp, "h", false, "show help") + var rawEnvFilenames string + flag.StringVar(&rawEnvFilenames, "f", "", "comma separated paths to .env files") + + flag.Parse() + + usage := ` +Run a process with a env setup from a .env file + +godotenv [-f ENV_FILE_PATHS] COMMAND_ARGS + +ENV_FILE_PATHS: comma separated paths to .env files +COMMAND_ARGS: command and args you want to run + +example + godotenv -f /path/to/something/.env,/another/path/.env fortune +` + // if no args or -h flag + // print usage and return + args := flag.Args() + if showHelp || len(args) == 0 { + fmt.Println(usage) + return + } + + // load env + var envFilenames []string + if rawEnvFilenames != "" { + envFilenames = strings.Split(rawEnvFilenames, ",") + } + + // take rest of args and "exec" them + cmd := args[0] + cmdArgs := args[1:] + + err := godotenv.Exec(envFilenames, cmd, cmdArgs) + if err != nil { + log.Fatal(err) + } +} diff --git a/vendor/github.com/joho/godotenv/godotenv.go b/vendor/github.com/joho/godotenv/godotenv.go new file mode 100644 index 000000000000..94b2676bf8fd --- /dev/null +++ b/vendor/github.com/joho/godotenv/godotenv.go @@ -0,0 +1,229 @@ +// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv) +// +// Examples/readme can be found on the github page at https://github.com/joho/godotenv +// +// The TL;DR is that you make a .env file that looks something like +// +// SOME_ENV_VAR=somevalue +// +// and then in your go code you can call +// +// godotenv.Load() +// +// and all the env vars declared in .env will be avaiable through os.Getenv("SOME_ENV_VAR") +package godotenv + +import ( + "bufio" + "errors" + "os" + "os/exec" + "strings" +) + +// Load will read your env file(s) and load them into ENV for this process. +// +// Call this function as close as possible to the start of your program (ideally in main) +// +// If you call Load without any args it will default to loading .env in the current path +// +// You can otherwise tell it which files to load (there can be more than one) like +// +// godotenv.Load("fileone", "filetwo") +// +// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults +func Load(filenames ...string) (err error) { + filenames = filenamesOrDefault(filenames) + + for _, filename := range filenames { + err = loadFile(filename, false) + if err != nil { + return // return early on a spazout + } + } + return +} + +// Overload will read your env file(s) and load them into ENV for this process. +// +// Call this function as close as possible to the start of your program (ideally in main) +// +// If you call Overload without any args it will default to loading .env in the current path +// +// You can otherwise tell it which files to load (there can be more than one) like +// +// godotenv.Overload("fileone", "filetwo") +// +// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars. +func Overload(filenames ...string) (err error) { + filenames = filenamesOrDefault(filenames) + + for _, filename := range filenames { + err = loadFile(filename, true) + if err != nil { + return // return early on a spazout + } + } + return +} + +// Read all env (with same file loading semantics as Load) but return values as +// a map rather than automatically writing values into env +func Read(filenames ...string) (envMap map[string]string, err error) { + filenames = filenamesOrDefault(filenames) + envMap = make(map[string]string) + + for _, filename := range filenames { + individualEnvMap, individualErr := readFile(filename) + + if individualErr != nil { + err = individualErr + return // return early on a spazout + } + + for key, value := range individualEnvMap { + envMap[key] = value + } + } + + return +} + +// Exec loads env vars from the specified filenames (empty map falls back to default) +// then executes the cmd specified. +// +// Simply hooks up os.Stdin/err/out to the command and calls Run() +// +// If you want more fine grained control over your command it's recommended +// that you use `Load()` or `Read()` and the `os/exec` package yourself. +func Exec(filenames []string, cmd string, cmdArgs []string) error { + Load(filenames...) + + command := exec.Command(cmd, cmdArgs...) + command.Stdin = os.Stdin + command.Stdout = os.Stdout + command.Stderr = os.Stderr + return command.Run() +} + +func filenamesOrDefault(filenames []string) []string { + if len(filenames) == 0 { + return []string{".env"} + } + return filenames +} + +func loadFile(filename string, overload bool) error { + envMap, err := readFile(filename) + if err != nil { + return err + } + + for key, value := range envMap { + if os.Getenv(key) == "" || overload { + os.Setenv(key, value) + } + } + + return nil +} + +func readFile(filename string) (envMap map[string]string, err error) { + file, err := os.Open(filename) + if err != nil { + return + } + defer file.Close() + + envMap = make(map[string]string) + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + for _, fullLine := range lines { + if !isIgnoredLine(fullLine) { + key, value, err := parseLine(fullLine) + + if err == nil { + envMap[key] = value + } + } + } + return +} + +func parseLine(line string) (key string, value string, err error) { + if len(line) == 0 { + err = errors.New("zero length string") + return + } + + // ditch the comments (but keep quoted hashes) + if strings.Contains(line, "#") { + segmentsBetweenHashes := strings.Split(line, "#") + quotesAreOpen := false + var segmentsToKeep []string + for _, segment := range segmentsBetweenHashes { + if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 { + if quotesAreOpen { + quotesAreOpen = false + segmentsToKeep = append(segmentsToKeep, segment) + } else { + quotesAreOpen = true + } + } + + if len(segmentsToKeep) == 0 || quotesAreOpen { + segmentsToKeep = append(segmentsToKeep, segment) + } + } + + line = strings.Join(segmentsToKeep, "#") + } + + // now split key from value + splitString := strings.SplitN(line, "=", 2) + + if len(splitString) != 2 { + // try yaml mode! + splitString = strings.SplitN(line, ":", 2) + } + + if len(splitString) != 2 { + err = errors.New("Can't separate key from value") + return + } + + // Parse the key + key = splitString[0] + if strings.HasPrefix(key, "export") { + key = strings.TrimPrefix(key, "export") + } + key = strings.Trim(key, " ") + + // Parse the value + value = splitString[1] + // trim + value = strings.Trim(value, " ") + + // check if we've got quoted values + if strings.Count(value, "\"") == 2 || strings.Count(value, "'") == 2 { + // pull the quotes off the edges + value = strings.Trim(value, "\"'") + + // expand quotes + value = strings.Replace(value, "\\\"", "\"", -1) + // expand newlines + value = strings.Replace(value, "\\n", "\n", -1) + } + + return +} + +func isIgnoredLine(line string) bool { + trimmedLine := strings.Trim(line, " \n\t") + return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#") +} diff --git a/vendor/github.com/joho/godotenv/wercker.yml b/vendor/github.com/joho/godotenv/wercker.yml new file mode 100644 index 000000000000..c716ac926aa1 --- /dev/null +++ b/vendor/github.com/joho/godotenv/wercker.yml @@ -0,0 +1 @@ +box: pjvds/golang