From 259f2448cbca23021dbcfb26f35cbdf04e51a2a0 Mon Sep 17 00:00:00 2001 From: Yoriyasu Yano <430092+yorinasub17@users.noreply.github.com> Date: Thu, 27 Aug 2020 01:01:07 -0500 Subject: [PATCH 1/8] Add support for fetching remote templates. Additionally, rename template-folder to template-url. --- _docs/README.md | 44 +- _docs/boilerplate.yml | 2 +- cli/boilerplate_cli.go | 17 +- config/config_test.go | 38 +- examples/dependencies-dynamic/README.md | 6 +- examples/dependencies-dynamic/boilerplate.yml | 10 +- .../dependencies-recursive/boilerplate.yml | 6 +- examples/dependencies-remote/README.md | 6 + examples/dependencies-remote/boilerplate.yml | 32 + examples/dependencies/boilerplate.yml | 24 +- examples/docs/boilerplate.yml | 2 +- examples/java-project/boilerplate.yml | 2 +- examples/template-helpers/boilerplate.yml | 24 +- examples/template-helpers/sample.txt | 4 +- examples/variables-recursive/boilerplate.yml | 2 +- getter-helper/file_getter.go | 43 ++ getter-helper/getter_helper.go | 162 +++++ go.mod | 3 +- go.sum | 560 ++++++++++++++++++ integration-tests/examples_test.go | 47 +- options/options.go | 51 +- templates/template_processor.go | 37 +- templates/template_processor_test.go | 43 +- .../config-test/full-config/boilerplate.yml | 6 +- .../invalid-config/boilerplate.yml | 2 +- .../dependencies-dynamic/README.md | 6 +- util/file.go | 17 + variables/dependencies.go | 12 +- 28 files changed, 1073 insertions(+), 135 deletions(-) create mode 100644 examples/dependencies-remote/README.md create mode 100644 examples/dependencies-remote/boilerplate.yml create mode 100644 getter-helper/file_getter.go create mode 100644 getter-helper/getter_helper.go diff --git a/_docs/README.md b/_docs/README.md index 109bd871..eac9cf9a 100644 --- a/_docs/README.md +++ b/_docs/README.md @@ -34,11 +34,11 @@ Template](https://golang.org/pkg/text/template) syntax: Copy an image into the `website-boilerplate` folder and call it `logo.png`. -Finally, run `boilerplate`, setting the `--template-folder` to `website-boilerplate` and `--output-folder` to the path +Finally, run `boilerplate`, setting the `--template-url` to `website-boilerplate` and `--output-folder` to the path where you want the generated code to go: ``` -boilerplate --template-folder /home/ubuntu/website-boilerplate --output-folder /home/ubuntu/website-output +boilerplate --template-url /home/ubuntu/website-boilerplate --output-folder /home/ubuntu/website-output Title @@ -58,7 +58,7 @@ Generating /home/ubuntu/website-output/index.html Copying /home/ubuntu/website-output/logo.png ``` -Boilerplate copies any files from the `--template-folder` into the `--output-folder`, passing them through the +Boilerplate copies any files from the `--template-url` into the `--output-folder`, passing them through the [Go Template](https://golang.org/pkg/text/template) engine along the way. After running the command above, the `website-output` folder will contain a `logo.png` (unchanged from the original) and an `index.html` with the following contents: @@ -71,7 +71,7 @@ You can also run Boilerplate non-interactively, which is great for automation: ``` boilerplate \ - --template-folder /home/ubuntu/website-boilerplate \ + --template-url /home/ubuntu/website-boilerplate \ --output-folder /home/ubuntu/website-output \ --non-interactive \ --var Title="Boilerplate Example" \ @@ -112,11 +112,11 @@ You can find older versions on the [Releases Page](https://github.com/gruntwork- When you run Boilerplate, it performs the following steps: -1. Read the `boilerplate.yml` file in the folder specified by the `--template-folder` option to find all defined +1. Read the `boilerplate.yml` file in the folder specified by the `--template-url` option to find all defined varaibles. 1. Gather values for the variables from any `--var` and `--var-file` options that were passed in and prompting the user for the rest (unless the `--non-interactive` flag is specified). -1. Copy each file from `--template-folder` to `--output-folder`, running each non-binary file through the Go +1. Copy each file from `--template-url` to `--output-folder`, running each non-binary file through the Go Template engine with the map of variables as the data structure. Learn more about boilerplate in the following sections: @@ -133,7 +133,9 @@ Learn more about boilerplate in the following sections: The `boilerplate` binary supports the following options: -* `--template-folder FOLDER` (required): Generate the project from the templates in `FOLDER`. +* `--template-url URL` (required): Generate the project from the templates in `URL`. This can be a local path, or a + [go-getter](https://github.com/hashicorp/go-getter) compatible URL for remote templates (e.g., + `git@github.com:gruntwork-io/boilerplate.git//examples/include?ref=master`). * `--output-folder` (required): Create the output files and folders in `FOLDER`. * `--non-interactive` (optional): Do not prompt for input variables. All variables must be set via `--var` and `--var-file` options instead. @@ -155,21 +157,28 @@ Some examples: Generate a project in ~/output from the templates in ~/templates: ``` -boilerplate --template-folder ~/templates --output-folder ~/output +boilerplate --template-url ~/templates --output-folder ~/output ``` Generate a project in ~/output from the templates in ~/templates, using variables passed in via the command line: ``` -boilerplate --template-folder ~/templates --output-folder ~/output --var "Title=Boilerplate" --var "ShowLogo=false" +boilerplate --template-url ~/templates --output-folder ~/output --var "Title=Boilerplate" --var "ShowLogo=false" ``` Generate a project in ~/output from the templates in ~/templates, using variables read from a file: ``` -boilerplate --template-folder ~/templates --output-folder ~/output --var-file vars.yml +boilerplate --template-url ~/templates --output-folder ~/output --var-file vars.yml ``` +Generate a project in ~/output from the templates in this repo's `include` example dir, using variables read from a file: + +``` +boilerplate --template-url "git@github.com:gruntwork-io/boilerplate.git//examples/include?ref=master" --output-folder ~/output --var-file vars.yml +``` + + #### The boilerplate.yml file The `boilerplate.yml` file is used to configure `boilerplate`. The file is optional. If you don't specify it, you can @@ -191,7 +200,7 @@ variables: dependencies: - name: - template-folder: + template-url: output-folder: skip: dont-inherit-variables: @@ -244,7 +253,7 @@ See the [Variables](#variables) section for more info. executing the current one. Each dependency may contain the following keys: * `name` (Required): A unique name for the dependency. -* `template-folder` (Required): Run `boilerplate` on the templates in this folder. This path is relative to the +* `template-url` (Required): Run `boilerplate` on the templates in this folder. This path is relative to the current template. * `output-folder` (Required): Create the output files and folders in this folder. This path is relative to the output folder of the current template. @@ -350,7 +359,7 @@ Note the following: prompt you for each of those variables separately from the root ones. You can also use the `.` syntax as the name of the variable with the `-var` flag and inside of a var file to provide a value for a variable in a dependency. -* Interpolation: You may use interpolation in the `template-folder` and `output-folder` parameters of your +* Interpolation: You may use interpolation in the `template-url` and `output-folder` parameters of your dependencies. This allows you to use specify the paths to your template and output folders dynamically. * Conditional dependencies: You can enable or disable a dependency using the `skip` parameter, which supports Go templating syntax and boilerplate variables. If the `skip` parameter evaluates to the string `true`, the @@ -366,7 +375,7 @@ Note the following: dependencies: - name: conditional-dependency-example - template-folder: ../foo + template-url: ../foo output-folder: foo # Skip this dependency if both .Foo and .Bar are set to true skip: "{{"{{"}} and .Foo .Bar {{"}}"}}" @@ -402,7 +411,7 @@ Note the following: - {{"{{"}} .SomeVariable {{"}}"}} - {{"{{"}} .AnotherVariable {{"}}"}} ``` -* Boilerplate runs your `command` with the working directory set to the `--template-folder` option. +* Boilerplate runs your `command` with the working directory set to the `--template-url` option. * `skip` (Optional): Skip this hook if this condition, which can use Go templating syntax and boilerplate variables, evaluates to the string `true`. This is useful to conditionally enable or disable dependencies. @@ -411,7 +420,7 @@ Note the following: #### Templates Boilerplate puts all the variables into a Go map where the key is the name of the variable and the value is what -the user provided. It then starts copying files from the `--template-folder` into the `--output-folder`, passing each +the user provided. It then starts copying files from the `--template-url` into the `--output-folder`, passing each non-binary file through the [Go Template](https://golang.org/pkg/text/template) engine, with the variable map as a data structure. @@ -481,7 +490,8 @@ Boilerplate also includes several custom helpers that you can access that enhanc so you can use paths relative to the file from which you are calling the `shell` helper. Any argument you pass of the form `ENV:KEY=VALUE` will be set as an environment variable for the command rather than an argument. For another way to execute commands, see [hooks](#hooks). -* `templateFolder`: Return the value of the `--template-folder` command-line option. Useful for building relative paths. +* `templateFolder`: Return the value of the template working dir. This is the value of the `--template-url` command-line + option if local template, or the download dir if remote template. Useful for building relative paths. * `outputFolder`: Return the value of the `--output-folder` command-line option. Useful for building relative paths. * `envWithDefault NAME DEFAULT`: Render the value of environment variable `NAME`. If that environment variable is empty or not defined, render `DEFAULT` instead. diff --git a/_docs/boilerplate.yml b/_docs/boilerplate.yml index 7098b780..a8b8fa5f 100644 --- a/_docs/boilerplate.yml +++ b/_docs/boilerplate.yml @@ -1,2 +1,2 @@ variables: - - name: Version \ No newline at end of file + - name: Version diff --git a/cli/boilerplate_cli.go b/cli/boilerplate_cli.go index 43c9fed5..04497e2b 100644 --- a/cli/boilerplate_cli.go +++ b/cli/boilerplate_cli.go @@ -17,15 +17,19 @@ A tool for generating files and folders (\"boilerplate\") from a set of template Generate a project in ~/output from the templates in ~/templates: - boilerplate --template-folder ~/templates --output-folder ~/output + boilerplate --template-url ~/templates --output-folder ~/output Generate a project in ~/output from the templates in ~/templates, using variables passed in via the command line: - boilerplate --template-folder ~/templates --output-folder ~/output --var "Title=Boilerplate" --var "ShowLogo=false" + boilerplate --template-url ~/templates --output-folder ~/output --var "Title=Boilerplate" --var "ShowLogo=false" Generate a project in ~/output from the templates in ~/templates, using variables read from a file: - boilerplate --template-folder ~/templates --output-folder ~/output --var-file vars.yml + boilerplate --template-url ~/templates --output-folder ~/output --var-file vars.yml + +Generate a project in ~/output from the templates in this repo's include example dir, using variables read from a file: + + boilerplate --template-url "git@github.com:gruntwork-io/boilerplate.git//examples/include?ref=master" --output-folder ~/output --var-file vars.yml Options: @@ -47,8 +51,8 @@ func CreateBoilerplateCli(version string) *cli.App { app.Flags = []cli.Flag{ cli.StringFlag{ - Name: options.OptTemplateFolder, - Usage: "Generate the project from the templates in `FOLDER`.", + Name: options.OptTemplateUrl, + Usage: "Generate the project from the templates in `URL`. This can be a local path, or a go-getter compatible URL for remote templates (e.g., `git@github.com:gruntwork-io/boilerplate.git//examples/include?ref=master`).", }, cli.StringFlag{ Name: options.OptOutputFolder, @@ -91,8 +95,7 @@ func CreateBoilerplateCli(version string) *cli.App { // When you run the CLI, this is the action function that gets called func runApp(cliContext *cli.Context) error { if !cliContext.Args().Present() && cliContext.NumFlags() == 0 { - cli.ShowAppHelp(cliContext) - return nil + return cli.ShowAppHelp(cliContext) } opts, err := options.ParseOptions(cliContext) diff --git a/config/config_test.go b/config/config_test.go index 381b2541..f0e17f95 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -300,7 +300,7 @@ func TestParseBoilerplateConfigAllTypes(t *testing.T) { // YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation const CONFIG_ONE_DEPENDENCY = `dependencies: - name: dep1 - template-folder: /template/folder1 + template-url: /template/folder1 output-folder: /output/folder1 ` @@ -311,7 +311,7 @@ func TestParseBoilerplateConfigOneDependency(t *testing.T) { expected := &BoilerplateConfig{ Variables: []variables.Variable{}, Dependencies: []variables.Dependency{ - {Name: "dep1", TemplateFolder: "/template/folder1", OutputFolder: "/output/folder1", DontInheritVariables: false, Variables: []variables.Variable{}}, + {Name: "dep1", TemplateUrl: "/template/folder1", OutputFolder: "/output/folder1", DontInheritVariables: false, Variables: []variables.Variable{}}, }, Hooks: variables.Hooks{}, } @@ -323,11 +323,11 @@ func TestParseBoilerplateConfigOneDependency(t *testing.T) { // YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation const CONFIG_MULTIPLE_DEPENDENCIES = `dependencies: - name: dep1 - template-folder: /template/folder1 + template-url: /template/folder1 output-folder: /output/folder1 - name: dep2 - template-folder: /template/folder2 + template-url: /template/folder2 output-folder: /output/folder2 dont-inherit-variables: true variables: @@ -336,7 +336,7 @@ const CONFIG_MULTIPLE_DEPENDENCIES = `dependencies: default: foo - name: dep3 - template-folder: /template/folder3 + template-url: /template/folder3 output-folder: /output/folder3 skip: "{{ and .Foo .Bar }}" ` @@ -350,14 +350,14 @@ func TestParseBoilerplateConfigMultipleDependencies(t *testing.T) { Dependencies: []variables.Dependency{ { Name: "dep1", - TemplateFolder: "/template/folder1", + TemplateUrl: "/template/folder1", OutputFolder: "/output/folder1", DontInheritVariables: false, Variables: []variables.Variable{}, }, { Name: "dep2", - TemplateFolder: "/template/folder2", + TemplateUrl: "/template/folder2", OutputFolder: "/output/folder2", DontInheritVariables: true, Variables: []variables.Variable{ @@ -366,7 +366,7 @@ func TestParseBoilerplateConfigMultipleDependencies(t *testing.T) { }, { Name: "dep3", - TemplateFolder: "/template/folder3", + TemplateUrl: "/template/folder3", OutputFolder: "/output/folder3", DontInheritVariables: false, Variables: []variables.Variable{}, @@ -382,7 +382,7 @@ func TestParseBoilerplateConfigMultipleDependencies(t *testing.T) { // YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation const CONFIG_DEPENDENCY_MISSING_NAME = `dependencies: - - template-folder: /template/folder1 + - template-url: /template/folder1 output-folder: /output/folder1 ` @@ -401,19 +401,19 @@ const CONFIG_DEPENDENCY_MISSING_TEMPLATE_FOLDER = `dependencies: output-folder: /output/folder1 ` -func TestParseBoilerplateConfigDependencyMissingTemplateFolder(t *testing.T) { +func TestParseBoilerplateConfigDependencyMissingTemplateUrl(t *testing.T) { t.Parallel() _, err := ParseBoilerplateConfig([]byte(CONFIG_DEPENDENCY_MISSING_TEMPLATE_FOLDER)) assert.NotNil(t, err) - assert.True(t, errors.IsError(err, variables.RequiredFieldMissing("template-folder")), "Expected a RequiredFieldMissing error but got %s", reflect.TypeOf(errors.Unwrap(err))) + assert.True(t, errors.IsError(err, variables.RequiredFieldMissing("template-url")), "Expected a RequiredFieldMissing error but got %s", reflect.TypeOf(errors.Unwrap(err))) } // YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation const CONFIG_DEPENDENCY_MISSING_VARIABLE_NAME = `dependencies: - name: dep1 - template-folder: /template/folder1 + template-url: /template/folder1 output-folder: /output/folder1 variables: - description: Enter foo @@ -432,11 +432,11 @@ func TestParseBoilerplateConfigDependencyMissingVariableName(t *testing.T) { // YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation const CONFIG_DEPENDENCY_MISSING_OUTPUT_FOLDER = `dependencies: - name: dep1 - template-folder: /template/folder1 + template-url: /template/folder1 output-folder: /output/folder1 - name: dep2 - template-folder: /template/folder2 + template-url: /template/folder2 ` func TestParseBoilerplateConfigDependencyMissingOutputFolder(t *testing.T) { @@ -451,15 +451,15 @@ func TestParseBoilerplateConfigDependencyMissingOutputFolder(t *testing.T) { // YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation const CONFIG_DEPENDENCY_DUPLICATE_NAMES = `dependencies: - name: dep1 - template-folder: /template/folder1 + template-url: /template/folder1 output-folder: /output/folder1 - name: dep2 - template-folder: /template/folder2 + template-url: /template/folder2 output-folder: /output/folder2 - name: dep1 - template-folder: /template/folder3 + template-url: /template/folder3 output-folder: /output/folder3 ` @@ -619,8 +619,8 @@ func TestLoadBoilerplateConfigFullConfig(t *testing.T) { variables.NewStringVariable("baz").WithDescription("example description").WithDefault("default"), }, Dependencies: []variables.Dependency{ - {Name: "dep1", TemplateFolder: "/template/folder1", OutputFolder: "/output/folder1", DontInheritVariables: false, Variables: []variables.Variable{}}, - {Name: "dep2", TemplateFolder: "/template/folder2", OutputFolder: "/output/folder2", DontInheritVariables: true, Variables: []variables.Variable{ + {Name: "dep1", TemplateUrl: "/template/folder1", OutputFolder: "/output/folder1", DontInheritVariables: false, Variables: []variables.Variable{}}, + {Name: "dep2", TemplateUrl: "/template/folder2", OutputFolder: "/output/folder2", DontInheritVariables: true, Variables: []variables.Variable{ variables.NewStringVariable("baz").WithDescription("example description").WithDefault("other-default"), variables.NewStringVariable("abc").WithDescription("example description").WithDefault("default"), }}, diff --git a/examples/dependencies-dynamic/README.md b/examples/dependencies-dynamic/README.md index 072a69a1..6ae1d711 100644 --- a/examples/dependencies-dynamic/README.md +++ b/examples/dependencies-dynamic/README.md @@ -2,6 +2,6 @@ {{ .Description }}. It specifies both the [docs](/examples/docs) and [website](/examples/website) examples as dependencies to show how one boilerplate template -can pull in another, and that you can use interpolation in the `template-folder` and `output-folder` parameters of -dependencies to dynamically specify where to read the template and where to write the output. It also defines all the -variables needed for both of those dependencies at the top level to show how variable inheritance works. \ No newline at end of file +can pull in another, and that you can use interpolation in the `template-url` and `output-folder` parameters of +dependencies to dynamically specify where to read the template and where to write the output. It also defines all the +variables needed for both of those dependencies at the top level to show how variable inheritance works. diff --git a/examples/dependencies-dynamic/boilerplate.yml b/examples/dependencies-dynamic/boilerplate.yml index b2047427..5ac25928 100644 --- a/examples/dependencies-dynamic/boilerplate.yml +++ b/examples/dependencies-dynamic/boilerplate.yml @@ -17,7 +17,7 @@ variables: default: true - name: WebsiteTemplateFolder - description: The path to the website templates folder. This is used to check that interpolations work in the template-folder parameter of dependencies. + description: The path to the website templates folder. This is used to check that interpolations work in the template-url parameter of dependencies. - name: WebsiteOutputFolder description: The path to the website output folder. This is used to check that interpolations work in the output-folder parameter of dependencies. @@ -39,7 +39,7 @@ variables: dependencies: - name: docs - template-folder: ../docs + template-url: ../docs output-folder: ./docs skip: "{{ .SkipAllDependencies }}" variables: @@ -47,7 +47,7 @@ dependencies: description: Enter the title of the docs page - name: website - template-folder: "{{ .WebsiteTemplateFolder }}" + template-url: "{{ .WebsiteTemplateFolder }}" output-folder: "{{ .WebsiteOutputFolder }}" skip: "{{ or .SkipAllDependencies .SkipFirstWebsiteDependency }}" variables: @@ -55,10 +55,10 @@ dependencies: description: Enter the title of the website - name: skip-website - template-folder: "{{ .WebsiteTemplateFolder }}" + template-url: "{{ .WebsiteTemplateFolder }}" output-folder: "{{ .WebsiteOutputFolder }}" skip: "{{ or .SkipAllDependencies .SkipSecondWebsiteDependency }}" variables: - name: Title description: Enter the title of the website - default: This website dependency should be skipped \ No newline at end of file + default: This website dependency should be skipped diff --git a/examples/dependencies-recursive/boilerplate.yml b/examples/dependencies-recursive/boilerplate.yml index ea5d48eb..5afdc6e7 100644 --- a/examples/dependencies-recursive/boilerplate.yml +++ b/examples/dependencies-recursive/boilerplate.yml @@ -18,7 +18,7 @@ variables: dependencies: - name: dependencies - template-folder: ../dependencies + template-url: ../dependencies output-folder: ./dependencies variables: - name: Description @@ -28,7 +28,7 @@ dependencies: description: Enter the title for the dependencies example - name: java-project - template-folder: ../java-project + template-url: ../java-project output-folder: ./java-project variables: - name: CompanyName @@ -49,4 +49,4 @@ dependencies: - name: ExampleMap description: Enter some example values to store in a HashMap - type: map \ No newline at end of file + type: map diff --git a/examples/dependencies-remote/README.md b/examples/dependencies-remote/README.md new file mode 100644 index 00000000..9ce5552a --- /dev/null +++ b/examples/dependencies-remote/README.md @@ -0,0 +1,6 @@ +# {{ .Title }} + +{{ .Description }}. It specifies both the +[docs](/examples/docs) and [website](/examples/website) examples as dependencies to show how one boilerplate template +can pull in another. It also defines all the variables needed for both of those dependencies at the top level to show +how variable inheritance works. diff --git a/examples/dependencies-remote/boilerplate.yml b/examples/dependencies-remote/boilerplate.yml new file mode 100644 index 00000000..3e0084bf --- /dev/null +++ b/examples/dependencies-remote/boilerplate.yml @@ -0,0 +1,32 @@ +variables: + - name: Description + description: Enter the description of this template + + - name: Version + description: Enter the version number that will be used by the docs dependency + + - name: Title + description: Enter the title for the dependencies example + + - name: WelcomeText + description: Enter the welcome text used by the website dependency + + - name: ShowLogo + description: Should the webiste show the logo (true or false)? + type: bool + default: true + +dependencies: + - name: docs + template-url: "git@github.com:gruntwork-io/boilerplate.git//docs?ref=master" + output-folder: ./docs + variables: + - name: Title + description: Enter the title of the docs page + + - name: website + template-url: "git@github.com:gruntwork-io/boilerplate.git//website?ref=master" + output-folder: ./website + variables: + - name: Title + description: Enter the title of the website diff --git a/examples/dependencies/boilerplate.yml b/examples/dependencies/boilerplate.yml index 339ab052..9ebcbd05 100644 --- a/examples/dependencies/boilerplate.yml +++ b/examples/dependencies/boilerplate.yml @@ -17,16 +17,16 @@ variables: default: true dependencies: - - name: docs - template-folder: ../docs - output-folder: ./docs - variables: - - name: Title - description: Enter the title of the docs page + - name: docs + template-url: ../docs + output-folder: ./docs + variables: + - name: Title + description: Enter the title of the docs page - - name: website - template-folder: ../website - output-folder: ./website - variables: - - name: Title - description: Enter the title of the website \ No newline at end of file + - name: website + template-url: ../website + output-folder: ./website + variables: + - name: Title + description: Enter the title of the website diff --git a/examples/docs/boilerplate.yml b/examples/docs/boilerplate.yml index d5ee4076..97d83edf 100644 --- a/examples/docs/boilerplate.yml +++ b/examples/docs/boilerplate.yml @@ -9,4 +9,4 @@ variables: - name: FileName description: This variable will be used to create the name of a file dynamically - default: my example file \ No newline at end of file + default: my example file diff --git a/examples/java-project/boilerplate.yml b/examples/java-project/boilerplate.yml index 61e120dc..1b0ef7dc 100644 --- a/examples/java-project/boilerplate.yml +++ b/examples/java-project/boilerplate.yml @@ -17,4 +17,4 @@ variables: - name: ExampleMap description: Enter some example values to store in a HashMap - type: map \ No newline at end of file + type: map diff --git a/examples/template-helpers/boilerplate.yml b/examples/template-helpers/boilerplate.yml index 84ef8a22..0604b9bc 100644 --- a/examples/template-helpers/boilerplate.yml +++ b/examples/template-helpers/boilerplate.yml @@ -5,16 +5,16 @@ variables: default: Inazagi dependencies: - - name: docs - template-folder: ../docs - output-folder: ./docs - variables: - - name: Title - description: Enter the title of the docs page + - name: docs + template-url: ../docs + output-folder: ./docs + variables: + - name: Title + description: Enter the title of the docs page - - name: website - template-folder: ../website - output-folder: ./website - variables: - - name: Title - description: Enter the title of the website \ No newline at end of file + - name: website + template-url: ../website + output-folder: ./website + variables: + - name: Title + description: Enter the title of the website diff --git a/examples/template-helpers/sample.txt b/examples/template-helpers/sample.txt index a0bc2a6c..056b8edf 100644 --- a/examples/template-helpers/sample.txt +++ b/examples/template-helpers/sample.txt @@ -1,6 +1,6 @@ -{{ .BoilerplateConfigDeps.docs.TemplateFolder }} +{{ .BoilerplateConfigDeps.docs.TemplateUrl }} {{ .BoilerplateConfigDeps.docs.OutputFolder }} -{{ .BoilerplateConfigDeps.website.TemplateFolder }} +{{ .BoilerplateConfigDeps.website.TemplateUrl }} {{ .BoilerplateConfigDeps.website.OutputFolder }} {{ .SomeGlobalVar }} diff --git a/examples/variables-recursive/boilerplate.yml b/examples/variables-recursive/boilerplate.yml index 99f3572b..f60f14ee 100644 --- a/examples/variables-recursive/boilerplate.yml +++ b/examples/variables-recursive/boilerplate.yml @@ -110,5 +110,5 @@ variables: dependencies: - name: variables - template-folder: ../variables + template-url: ../variables output-folder: . diff --git a/getter-helper/file_getter.go b/getter-helper/file_getter.go new file mode 100644 index 00000000..5931b374 --- /dev/null +++ b/getter-helper/file_getter.go @@ -0,0 +1,43 @@ +package getter_helper + +import ( + "fmt" + "net/url" + "os" + + "github.com/hashicorp/go-getter" + + "github.com/gruntwork-io/boilerplate/util" +) + +// A custom getter.Getter implementation that uses file copying instead of symlinks. Symlinks are +// faster and use less disk space, but they cause issues in Windows and with infinite loops, so we copy files/folders +// instead. +type FileCopyGetter struct { + getter.FileGetter +} + +// The original FileGetter does NOT know how to do folder copying (it only does symlinks), so we provide a copy +// implementation here +func (g *FileCopyGetter) Get(dst string, u *url.URL) error { + path := u.Path + if u.RawPath != "" { + path = u.RawPath + } + + // The source path must exist and be a directory to be usable. + if fi, err := os.Stat(path); err != nil { + return fmt.Errorf("source path error: %s", err) + } else if !fi.IsDir() { + return fmt.Errorf("source path must be a directory") + } + + return util.CopyFolder(path, dst) +} + +// The original FileGetter already knows how to do file copying so long as we set the Copy flag to true, so just +// delegate to it +func (g *FileCopyGetter) GetFile(dst string, u *url.URL) error { + underlying := &getter.FileGetter{Copy: true} + return underlying.GetFile(dst, u) +} diff --git a/getter-helper/getter_helper.go b/getter-helper/getter_helper.go new file mode 100644 index 00000000..a8eb8920 --- /dev/null +++ b/getter-helper/getter_helper.go @@ -0,0 +1,162 @@ +package getter_helper + +import ( + "context" + "fmt" + "io/ioutil" + "net/url" + "os" + "os/signal" + "path/filepath" + "regexp" + "sync" + + getter "github.com/hashicorp/go-getter" + urlhelper "github.com/hashicorp/go-getter/helper/url" + + "github.com/gruntwork-io/boilerplate/errors" + "github.com/gruntwork-io/boilerplate/util" +) + +var forcedRegexp = regexp.MustCompile(`^([A-Za-z0-9]+)::(.+)$`) + +// ValidateTemplateUrl returns an error if the template URL is not following one of the supported detector patterns. +func ValidateTemplateUrl(templateUrl string) error { + _, err := ParseGetterUrl(templateUrl) + return err +} + +func ParseGetterUrl(templateUrl string) (*url.URL, error) { + pwd, err := os.Getwd() + if err != nil { + return nil, errors.WithStackTrace(err) + } + getterURLWithGetter, err := getter.Detect(templateUrl, pwd, getter.Detectors) + if err != nil { + return nil, errors.WithStackTrace(err) + } + return urlParseGetterUrl(getterURLWithGetter) +} + +// Parse the given source URL into a URL struct. This method can handle source URLs that include go-getter's "forced +// getter" prefixes, such as git::. +// The following routine was obtained from terragrunt. +func urlParseGetterUrl(rawGetterUrlStr string) (*url.URL, error) { + forcedGetter, getterUrlStr := getForcedGetter(rawGetterUrlStr) + + // Parse the URL without the getter prefix + canonicalGetterUrl, err := urlhelper.Parse(getterUrlStr) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + // Reattach the "getter" prefix as part of the scheme + if forcedGetter != "" { + canonicalGetterUrl.Scheme = fmt.Sprintf("%s::%s", forcedGetter, canonicalGetterUrl.Scheme) + } + + return canonicalGetterUrl, nil +} + +// Terraform source URLs can contain a "getter" prefix that specifies the type of protocol to use to download that URL, +// such as "git::", which means Git should be used to download the URL. This method returns the getter prefix and the +// rest of the URL. This code is copied from the getForcedGetter method of go-getter/get.go, as that method is not +// exported publicly. +func getForcedGetter(sourceUrl string) (string, string) { + if matches := forcedRegexp.FindStringSubmatch(sourceUrl); matches != nil && len(matches) > 2 { + return matches[1], matches[2] + } + + return "", sourceUrl +} + +// We use this code to force go-getter to copy files instead of creating symlinks. +func NewGetterClient(ctx context.Context, src string, dst string) (*getter.Client, error) { + pwd, err := os.Getwd() + if err != nil { + return nil, errors.WithStackTrace(err) + } + + client := &getter.Client{ + Ctx: ctx, + Src: src, + Dst: dst, + Pwd: pwd, + Mode: getter.ClientModeAny, + } + + // We copy all the default getters from the go-getter library, but replace the "file" getter. We shallow clone the + // getter map here rather than using getter.Getters directly because we shouldn't change the original, + // globally-shared getter.Getters map. + client.Getters = map[string]getter.Getter{} + for getterName, getterValue := range getter.Getters { + if getterName == "file" { + client.Getters[getterName] = &FileCopyGetter{} + } else { + client.Getters[getterName] = getterValue + } + } + + return client, nil +} + +// DownloadTemplatesToTemporaryFolder uses the go-getter library to fetch the templates from the configured URL to a +// temporary folder and returns the path to that folder. If there is a subdir in the template URL, return the combined +// path as well. +func DownloadTemplatesToTemporaryFolder(templateUrl string) (string, string, error) { + workingDir, err := ioutil.TempDir("", "boilerplate-cache*") + if err != nil { + return workingDir, workingDir, errors.WithStackTrace(err) + } + + // Always set a subdir path because go-getter can not clone into an existing dir. + cloneDir := filepath.Join(workingDir, "wd") + + util.Logger.Printf("Downloading templates from %s to %s", templateUrl, workingDir) + + // If there is a subdir component, we download everything and combine the path at the end to return the working path + mainPath, subDir := getter.SourceDirSubdir(templateUrl) + outDir := filepath.Clean(filepath.Join(cloneDir, subDir)) + + ctx, cancel := context.WithCancel(context.Background()) + client, err := NewGetterClient(ctx, mainPath, cloneDir) + if err != nil { + cancel() + return workingDir, outDir, err + } + + // Start getter in the background so we can trap and forward interrupt signals + wg := sync.WaitGroup{} + wg.Add(1) + errChan := make(chan error, 2) + go func() { + defer wg.Done() + defer cancel() + if err := client.Get(); err != nil { + errChan <- err + } + }() + + // Signal handler + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt) + + select { + // Interrupted + case sig := <-c: + signal.Reset(os.Interrupt) + cancel() + wg.Wait() + return workingDir, outDir, fmt.Errorf("Download interrupted with signal %v", sig) + + // No errors + case <-ctx.Done(): + wg.Wait() + return workingDir, outDir, nil + + // There was an error + case err := <-errChan: + wg.Wait() + return workingDir, outDir, err + } +} diff --git a/go.mod b/go.mod index 1ef8d09e..bc5dbf2b 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,9 @@ require ( github.com/Masterminds/sprig v2.22.0+incompatible github.com/fatih/color v1.9.0 github.com/go-errors/errors v1.1.1 - github.com/google/uuid v1.1.1 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 + github.com/gruntwork-io/terratest v0.28.14 + github.com/hashicorp/go-getter v1.4.1 github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.11 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect diff --git a/go.sum b/go.sum index 8ac44792..ea0e3e1f 100644 --- a/go.sum +++ b/go.sum @@ -1,73 +1,633 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1 h1:lRi0CHyU+ytlvylOlFKKq0af6JncuyoRh1J+QJBqQx0= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v38.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.15.78 h1:LaXy6lWR0YK7LKyuU0QWy2ws/LWTPfYV/UgfiBu4tvY= +github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= +github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.1 h1:MXnqY6SlWySaZAqNnXThOvjRFdiiOuKtC6i7baFdNdU= +github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v0.0.0-20200109221225-a4f60165b7a3/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg= github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-containerregistry v0.0.0-20200110202235-f4fb41bf00a3/go.mod h1:2wIuQute9+hhWqvL3vEI7YB0EKluF4WcPzI1eAliazk= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/gruntwork-io/gruntwork-cli v0.5.1/go.mod h1:IBX21bESC1/LGoV7jhXKUnTQTZgQ6dYRsoj/VqxUSZQ= github.com/gruntwork-io/gruntwork-cli v0.7.0 h1:YgSAmfCj9c61H+zuvHwKfYUwlMhu5arnQQLM4RH+CYs= github.com/gruntwork-io/gruntwork-cli v0.7.0/go.mod h1:jp6Z7NcLF2avpY8v71fBx6hds9eOFPELSuD/VPv7w00= +github.com/gruntwork-io/terratest v0.28.14 h1:X8AND6VxMdy7vmLiSVwKjuFm83KmWE5s6wssjBQPNuQ= +github.com/gruntwork-io/terratest v0.28.14/go.mod h1:PkVylPuUNmItkfOTwSiFreYA4FkanK8AluBuNeGxQOw= +github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-getter v1.4.1 h1:3A2Mh8smGFcf5M+gmcv898mZdrxpseik45IpcyISLsA= +github.com/hashicorp/go-getter v1.4.1/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/oracle/oci-go-sdk v7.1.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= +github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44238/go.mod h1:JwQJCMWpUDqjZrB5jpw0f5VbN7U95zxFy1ZDpoEarGo= +github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299 h1:zQpM52jfKHG6II1ISZY1ZcpygvuSFZpLwfluuF89XOg= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50 h1:YvQ10rzcqWXLlJZ3XCUoO25savxmscf4+SC+ZqiCHhA= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191205215504-7b8c8591a921/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200113040837-eac381796e91 h1:OOkytthzFBKHY5EfEgLUabprb0LtJVkQtNxAQ02+UE4= +golang.org/x/tools v0.0.0-20200113040837-eac381796e91/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f h1:2wh8dWY8959cBGQvk1RD+/eQBgRYYDaZ+hT0/zsARoA= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= +k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= +k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= +k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= +k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= +k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= +k8s.io/code-generator v0.0.0-20191121015212-c4c8f8345c7e/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= +k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/integration-tests/examples_test.go b/integration-tests/examples_test.go index 2e5ab1d8..b5d0343f 100644 --- a/integration-tests/examples_test.go +++ b/integration-tests/examples_test.go @@ -9,7 +9,9 @@ import ( "strings" "testing" + "github.com/gruntwork-io/terratest/modules/git" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/gruntwork-io/boilerplate/cli" "github.com/gruntwork-io/boilerplate/options" @@ -26,11 +28,11 @@ func TestExamples(t *testing.T) { examplesVarFilesBasePath := "../test-fixtures/examples-var-files" outputBasePath, err := ioutil.TempDir("", "boilerplate-test-output") - assert.Nil(t, err) + require.NoError(t, err) defer os.Remove(outputBasePath) examples, err := ioutil.ReadDir(examplesBasePath) - assert.Nil(t, err) + require.NoError(t, err) for _, example := range examples { if !example.IsDir() { @@ -57,12 +59,43 @@ func TestExamples(t *testing.T) { } } +func TestExamplesAsRemoteTemplate(t *testing.T) { + t.Parallel() + + branchName := git.GetCurrentBranchName(t) + examplesBasePath := "../examples" + examplesExpectedOutputBasePath := "../test-fixtures/examples-expected-output" + examplesVarFilesBasePath := "../test-fixtures/examples-var-files" + + outputBasePath, err := ioutil.TempDir("", "boilerplate-test-output") + require.NoError(t, err) + defer os.Remove(outputBasePath) + + examples, err := ioutil.ReadDir(examplesBasePath) + require.NoError(t, err) + + for _, example := range examples { + if !example.IsDir() { + continue + } + + t.Run(path.Base(example.Name()), func(t *testing.T) { + templateFolder := fmt.Sprintf("git@github.com:gruntwork-io/boilerplate.git//examples/%s?ref=%s", example.Name(), branchName) + outputFolder := path.Join(outputBasePath, example.Name()) + varFile := path.Join(examplesVarFilesBasePath, example.Name(), "vars.yml") + expectedOutputFolder := path.Join(examplesExpectedOutputBasePath, example.Name()) + testExample(t, templateFolder, outputFolder, varFile, expectedOutputFolder, string(options.ExitWithError)) + }) + } + +} + func testExample(t *testing.T, templateFolder string, outputFolder string, varFile string, expectedOutputFolder string, missingKeyAction string) { app := cli.CreateBoilerplateCli("test") args := []string{ "boilerplate", - "--template-folder", + "--template-url", templateFolder, "--output-folder", outputFolder, @@ -79,7 +112,7 @@ func testExample(t *testing.T, templateFolder string, outputFolder string, varFi } err := app.Run(args) - assert.Nil(t, err, "boilerplate exited with an error when trying to generate example %s: %s", templateFolder, err) + assert.NoError(t, err) assertDirectoriesEqual(t, expectedOutputFolder, outputFolder) } @@ -89,8 +122,6 @@ func testExample(t *testing.T, templateFolder string, outputFolder string, varFi func assertDirectoriesEqual(t *testing.T, folderWithExpectedContents string, folderWithActualContents string) { cmd := exec.Command("diff", "-r", "-u", folderWithExpectedContents, folderWithActualContents) - bytes, err := cmd.Output() - output := string(bytes) - - assert.Nil(t, err, "diff command exited with an error. This likely means the contents of %s and %s are different. Here is the output of the diff command:\n%s", folderWithExpectedContents, folderWithActualContents, output) + _, err := cmd.Output() + assert.NoError(t, err) } diff --git a/options/options.go b/options/options.go index ff9670da..33059de2 100644 --- a/options/options.go +++ b/options/options.go @@ -6,11 +6,11 @@ import ( "github.com/urfave/cli" "github.com/gruntwork-io/boilerplate/errors" - "github.com/gruntwork-io/boilerplate/util" + getter_helper "github.com/gruntwork-io/boilerplate/getter-helper" "github.com/gruntwork-io/boilerplate/variables" ) -const OptTemplateFolder = "template-folder" +const OptTemplateUrl = "template-url" const OptOutputFolder = "output-folder" const OptNonInteractive = "non-interactive" const OptVar = "var" @@ -22,7 +22,11 @@ const OptDisableShell = "disable-shell" // The command-line options for the boilerplate app type BoilerplateOptions struct { - TemplateFolder string + // go-getter supported URL where the template can be sourced. + TemplateUrl string + // Working directory where the go-getter defined template is downloaded. + TemplateFolder string + OutputFolder string NonInteractive bool Vars map[string]interface{} @@ -34,12 +38,12 @@ type BoilerplateOptions struct { // Validate that the options have reasonable values and return an error if they don't func (options *BoilerplateOptions) Validate() error { - if options.TemplateFolder == "" { - return errors.WithStackTrace(TemplateFolderOptionCannotBeEmpty) + if options.TemplateUrl == "" { + return errors.WithStackTrace(TemplateUrlOptionCannotBeEmpty) } - if !util.PathExists(options.TemplateFolder) { - return errors.WithStackTrace(TemplateFolderDoesNotExist(options.TemplateFolder)) + if err := getter_helper.ValidateTemplateUrl(options.TemplateUrl); err != nil { + return err } if options.OutputFolder == "" { @@ -74,8 +78,14 @@ func ParseOptions(cliContext *cli.Context) (*BoilerplateOptions, error) { } } + templateUrl, templateFolder, err := DetermineTemplateConfig(cliContext.String(OptTemplateUrl)) + if err != nil { + return nil, err + } + options := &BoilerplateOptions{ - TemplateFolder: cliContext.String(OptTemplateFolder), + TemplateUrl: templateUrl, + TemplateFolder: templateFolder, OutputFolder: cliContext.String(OptOutputFolder), NonInteractive: cliContext.Bool(OptNonInteractive), OnMissingKey: missingKeyAction, @@ -92,6 +102,23 @@ func ParseOptions(cliContext *cli.Context) (*BoilerplateOptions, error) { return options, nil } +// DetermineTemplateConfig decides what should be passed to TemplateUrl and TemplateFolder. This parses the templateUrl +// and determines if it is a local path. If so, use that path directly instead of downloading it to a temp working dir. +// We do this by setting the template folder, which will instruct the process routine to skip downloading the template. +// Returns TemplateUrl, TemplateFolder, error +func DetermineTemplateConfig(templateUrl string) (string, string, error) { + url, err := getter_helper.ParseGetterUrl(templateUrl) + if err != nil { + return "", "", err + } + if url.Scheme == "file" { + // Intentionally return as both TemplateUrl and TemplateFolder so that validation passes, but still skip + // download. + return templateUrl, templateUrl, nil + } + return templateUrl, "", nil +} + // This type is an enum that represents what we can do when a template looks up a missing key. This typically happens // when there is a typo in the variable name in a template. type MissingKeyAction string @@ -140,15 +167,9 @@ func ParseMissingConfigAction(str string) (MissingConfigAction, error) { // Custom error types -var TemplateFolderOptionCannotBeEmpty = fmt.Errorf("The --%s option cannot be empty", OptTemplateFolder) +var TemplateUrlOptionCannotBeEmpty = fmt.Errorf("The --%s option cannot be empty", OptTemplateUrl) var OutputFolderOptionCannotBeEmpty = fmt.Errorf("The --%s option cannot be empty", OptOutputFolder) -type TemplateFolderDoesNotExist string - -func (err TemplateFolderDoesNotExist) Error() string { - return fmt.Sprintf("Folder %s does not exist", string(err)) -} - type InvalidMissingKeyAction string func (err InvalidMissingKeyAction) Error() string { diff --git a/templates/template_processor.go b/templates/template_processor.go index e6afe65c..e6deb944 100644 --- a/templates/template_processor.go +++ b/templates/template_processor.go @@ -9,6 +9,7 @@ import ( "github.com/gruntwork-io/boilerplate/config" "github.com/gruntwork-io/boilerplate/errors" + getter_helper "github.com/gruntwork-io/boilerplate/getter-helper" "github.com/gruntwork-io/boilerplate/options" "github.com/gruntwork-io/boilerplate/render" "github.com/gruntwork-io/boilerplate/util" @@ -16,10 +17,27 @@ import ( ) // Process the boilerplate template specified in the given options and use the existing variables. This function will -// load any missing variables (either from command line options or by prompting the user), execute all the dependent -// boilerplate templates, and then execute this template. Note that we pass in rootOptions so that template dependencies -// can inspect properties of the root template. +// download remote templates to a temporary working directory, which is cleaned up at the end of the function. This +// function will load any missing variables (either from command line options or by prompting the user), execute all the +// dependent boilerplate templates, and then execute this template. Note that we pass in rootOptions so that template +// dependencies can inspect properties of the root template. func ProcessTemplate(options, rootOpts *options.BoilerplateOptions, thisDep variables.Dependency) error { + // If TemplateFolder is already set, use that directly as it is a local template. Otherwise, download to a temporary + // working directory. + if options.TemplateFolder == "" { + workingDir, templateFolder, err := getter_helper.DownloadTemplatesToTemporaryFolder(options.TemplateUrl) + defer func() { + util.Logger.Printf("Cleaning up working directory.") + os.RemoveAll(workingDir) + }() + if err != nil { + return err + } + + // Set the TemplateFolder of the options to the download dir + options.TemplateFolder = templateFolder + } + rootBoilerplateConfig, err := config.LoadBoilerplateConfig(rootOpts) if err != nil { return err @@ -174,7 +192,7 @@ func processDependency(dependency variables.Dependency, opts *options.Boilerplat // Clone the given options for use when rendering the given dependency. The dependency will get the same options as // the original passed in, except for the template folder, output folder, and command-line vars. func cloneOptionsForDependency(dependency variables.Dependency, originalOpts *options.BoilerplateOptions, variables map[string]interface{}) (*options.BoilerplateOptions, error) { - renderedTemplateFolder, err := render.RenderTemplate(originalOpts.TemplateFolder, dependency.TemplateFolder, variables, originalOpts) + renderedTemplateUrl, err := render.RenderTemplate(originalOpts.TemplateFolder, dependency.TemplateUrl, variables, originalOpts) if err != nil { return nil, err } @@ -183,10 +201,17 @@ func cloneOptionsForDependency(dependency variables.Dependency, originalOpts *op return nil, err } - templateFolder := render.PathRelativeToTemplate(originalOpts.TemplateFolder, renderedTemplateFolder) + templateUrl, templateFolder, err := options.DetermineTemplateConfig(renderedTemplateUrl) + // If local, make sure to return relative path in context of original template folder + if templateFolder != "" { + templateFolder = render.PathRelativeToTemplate(originalOpts.TemplateFolder, renderedTemplateUrl) + } + + // Output folder should be local path relative to original output folder, or absolute path outputFolder := render.PathRelativeToTemplate(originalOpts.OutputFolder, renderedOutputFolder) return &options.BoilerplateOptions{ + TemplateUrl: templateUrl, TemplateFolder: templateFolder, OutputFolder: outputFolder, NonInteractive: originalOpts.NonInteractive, @@ -235,7 +260,7 @@ func shouldProcessDependency(dependency variables.Dependency, opts *options.Boil return true, nil } - return util.PromptUserForYesNo(fmt.Sprintf("This boilerplate template has a dependency! Run boilerplate on dependency %s with template folder %s and output folder %s?", dependency.Name, dependency.TemplateFolder, dependency.OutputFolder)) + return util.PromptUserForYesNo(fmt.Sprintf("This boilerplate template has a dependency! Run boilerplate on dependency %s with template folder %s and output folder %s?", dependency.Name, dependency.TemplateUrl, dependency.OutputFolder)) } // Return true if the skip parameter of the given dependency evaluates to a "true" value diff --git a/templates/template_processor_test.go b/templates/template_processor_test.go index fd4b0142..7d4000dd 100644 --- a/templates/template_processor_test.go +++ b/templates/template_processor_test.go @@ -1,15 +1,42 @@ package templates import ( + "fmt" "os" + "path/filepath" "testing" + "github.com/gruntwork-io/gruntwork-cli/errors" + "github.com/gruntwork-io/terratest/modules/git" + "github.com/gruntwork-io/terratest/modules/shell" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/gruntwork-io/boilerplate/options" "github.com/gruntwork-io/boilerplate/variables" ) +func TestDownloadTemplatesToTempDir(t *testing.T) { + t.Parallel() + + pwd, err := os.Getwd() + require.NoError(t, err) + examplePath := filepath.Join(pwd, "..", "examples", "variables") + + branch := git.GetCurrentBranchName(t) + templateUrl := fmt.Sprintf("git@github.com:gruntwork-io/boilerplate.git//examples/variables?ref=%s", branch) + workingDir, err := downloadTemplatesToTemporaryFolder(templateUrl) + defer os.RemoveAll(workingDir) + require.NoError(t, err, errors.PrintErrorWithStackTrace(err)) + + // Run diff to make sure there are no differences + cmd := shell.Command{ + Command: "diff", + Args: []string{examplePath, workingDir}, + } + shell.RunCommand(t, cmd) +} + func TestOutPath(t *testing.T) { t.Parallel() @@ -55,19 +82,19 @@ func TestCloneOptionsForDependency(t *testing.T) { expectedOpts options.BoilerplateOptions }{ { - variables.Dependency{Name: "dep1", TemplateFolder: "../dep1", OutputFolder: "../out1"}, + variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1"}, options.BoilerplateOptions{TemplateFolder: "/template/path/", OutputFolder: "/output/path/", NonInteractive: true, Vars: map[string]interface{}{}, OnMissingKey: options.ExitWithError}, map[string]interface{}{}, options.BoilerplateOptions{TemplateFolder: "/template/dep1", OutputFolder: "/output/out1", NonInteractive: true, Vars: map[string]interface{}{}, OnMissingKey: options.ExitWithError}, }, { - variables.Dependency{Name: "dep1", TemplateFolder: "../dep1", OutputFolder: "../out1"}, + variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1"}, options.BoilerplateOptions{TemplateFolder: "/template/path/", OutputFolder: "/output/path/", NonInteractive: false, Vars: map[string]interface{}{"foo": "bar"}, OnMissingKey: options.Invalid}, map[string]interface{}{"baz": "blah"}, options.BoilerplateOptions{TemplateFolder: "/template/dep1", OutputFolder: "/output/out1", NonInteractive: false, Vars: map[string]interface{}{"baz": "blah"}, OnMissingKey: options.Invalid}, }, { - variables.Dependency{Name: "dep1", TemplateFolder: "{{ .foo }}", OutputFolder: "{{ .baz }}"}, + variables.Dependency{Name: "dep1", TemplateUrl: "{{ .foo }}", OutputFolder: "{{ .baz }}"}, options.BoilerplateOptions{TemplateFolder: "/template/path/", OutputFolder: "/output/path/", NonInteractive: false, Vars: map[string]interface{}{}, OnMissingKey: options.ExitWithError}, map[string]interface{}{"foo": "bar", "baz": "blah"}, options.BoilerplateOptions{TemplateFolder: "/template/path/bar", OutputFolder: "/output/path/blah", NonInteractive: false, Vars: map[string]interface{}{"foo": "bar", "baz": "blah"}, OnMissingKey: options.ExitWithError}, @@ -90,27 +117,27 @@ func TestCloneVariablesForDependency(t *testing.T) { expectedVariables map[string]interface{} }{ { - variables.Dependency{Name: "dep1", TemplateFolder: "../dep1", OutputFolder: "../out1"}, + variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1"}, map[string]interface{}{}, map[string]interface{}{}, }, { - variables.Dependency{Name: "dep1", TemplateFolder: "../dep1", OutputFolder: "../out1"}, + variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1"}, map[string]interface{}{"foo": "bar", "baz": "blah"}, map[string]interface{}{"foo": "bar", "baz": "blah"}, }, { - variables.Dependency{Name: "dep1", TemplateFolder: "../dep1", OutputFolder: "../out1"}, + variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1"}, map[string]interface{}{"foo": "bar", "baz": "blah", "dep1.abc": "should-modify-name", "dep2.def": "should-copy-unmodified"}, map[string]interface{}{"foo": "bar", "baz": "blah", "abc": "should-modify-name", "dep2.def": "should-copy-unmodified"}, }, { - variables.Dependency{Name: "dep1", TemplateFolder: "../dep1", OutputFolder: "../out1"}, + variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1"}, map[string]interface{}{"foo": "bar", "baz": "blah", "dep1.abc": "should-modify-name", "dep2.def": "should-copy-unmodified", "abc": "should-be-overwritten-by-dep1.abc"}, map[string]interface{}{"foo": "bar", "baz": "blah", "abc": "should-modify-name", "dep2.def": "should-copy-unmodified"}, }, { - variables.Dependency{Name: "dep1", TemplateFolder: "../dep1", OutputFolder: "../out1", DontInheritVariables: true}, + variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1", DontInheritVariables: true}, map[string]interface{}{"foo": "bar", "baz": "blah", "dep1.abc": "should-modify-name", "dep2.def": "should-copy-unmodified"}, map[string]interface{}{}, }, diff --git a/test-fixtures/config-test/full-config/boilerplate.yml b/test-fixtures/config-test/full-config/boilerplate.yml index 1800c1c0..53224f88 100644 --- a/test-fixtures/config-test/full-config/boilerplate.yml +++ b/test-fixtures/config-test/full-config/boilerplate.yml @@ -10,11 +10,11 @@ variables: dependencies: - name: dep1 - template-folder: /template/folder1 + template-url: /template/folder1 output-folder: /output/folder1 - name: dep2 - template-folder: /template/folder2 + template-url: /template/folder2 output-folder: /output/folder2 dont-inherit-variables: true variables: @@ -34,4 +34,4 @@ hooks: after: - command: foo - - command: bar \ No newline at end of file + - command: bar diff --git a/test-fixtures/config-test/invalid-config/boilerplate.yml b/test-fixtures/config-test/invalid-config/boilerplate.yml index 279bec9a..015a50de 100644 --- a/test-fixtures/config-test/invalid-config/boilerplate.yml +++ b/test-fixtures/config-test/invalid-config/boilerplate.yml @@ -1 +1 @@ -this-is-not-valid-yaml \ No newline at end of file +this-is-not-valid-yaml diff --git a/test-fixtures/examples-expected-output/dependencies-dynamic/README.md b/test-fixtures/examples-expected-output/dependencies-dynamic/README.md index c29cb428..8b458a71 100644 --- a/test-fixtures/examples-expected-output/dependencies-dynamic/README.md +++ b/test-fixtures/examples-expected-output/dependencies-dynamic/README.md @@ -2,6 +2,6 @@ This is a boilerplate template that shows an example of using dependencies. It specifies both the [docs](/examples/docs) and [website](/examples/website) examples as dependencies to show how one boilerplate template -can pull in another, and that you can use interpolation in the `template-folder` and `output-folder` parameters of -dependencies to dynamically specify where to read the template and where to write the output. It also defines all the -variables needed for both of those dependencies at the top level to show how variable inheritance works. \ No newline at end of file +can pull in another, and that you can use interpolation in the `template-url` and `output-folder` parameters of +dependencies to dynamically specify where to read the template and where to write the output. It also defines all the +variables needed for both of those dependencies at the top level to show how variable inheritance works. diff --git a/util/file.go b/util/file.go index 37844761..b51bf673 100644 --- a/util/file.go +++ b/util/file.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "os/exec" + "path/filepath" "strings" "github.com/gruntwork-io/boilerplate/errors" @@ -117,6 +118,22 @@ func WriteFileWithSamePermissions(source string, destination string, contents [] return ioutil.WriteFile(destination, contents, fileInfo.Mode()) } +// Copy all the files and folders in srcFolder to targetFolder. +func CopyFolder(srcFolder string, targetFolder string) error { + return filepath.Walk(srcFolder, func(path string, info os.FileInfo, err error) error { + relPath, err := filepath.Rel(srcFolder, path) + if err != nil { + return err + } + + if IsDir(path) { + return os.MkdirAll(filepath.Join(targetFolder, relPath), 0755) + } else { + return CopyFile(path, filepath.Join(targetFolder, relPath)) + } + }) +} + // custom error types type NoSuchFile string diff --git a/variables/dependencies.go b/variables/dependencies.go index f681ba5e..41b8cb2f 100644 --- a/variables/dependencies.go +++ b/variables/dependencies.go @@ -11,7 +11,7 @@ import ( // A single boilerplate template that this boilerplate.yml depends on being executed first type Dependency struct { Name string - TemplateFolder string + TemplateUrl string OutputFolder string Skip string DontInheritVariables bool @@ -47,11 +47,11 @@ func SplitIntoDependencyNameAndVariableName(uniqueVariableName string) (string, // // dependencies: // - name: -// template-folder: +// template-url: // output-folder: // // - name: -// template-folder: +// template-url: // output-folder: // // This method takes the data above and unmarshals it into a list of Dependency objects @@ -84,7 +84,7 @@ func UnmarshalDependenciesFromBoilerplateConfigYaml(fields map[string]interface{ // Given a map of key:value pairs read from a Boilerplate YAML config file of the format: // // name: -// template-folder: +// template-url: // output-folder: // // This method takes the data above and unmarshals it into a Dependency object @@ -94,7 +94,7 @@ func UnmarshalDependencyFromBoilerplateConfigYaml(fields map[string]interface{}) return nil, err } - templateFolder, err := unmarshalStringField(fields, "template-folder", true, *name) + templateUrl, err := unmarshalStringField(fields, "template-url", true, *name) if err != nil { return nil, err } @@ -125,7 +125,7 @@ func UnmarshalDependencyFromBoilerplateConfigYaml(fields map[string]interface{}) return &Dependency{ Name: *name, - TemplateFolder: *templateFolder, + TemplateUrl: *templateUrl, OutputFolder: *outputFolder, Skip: skip, DontInheritVariables: dontInheritVariables, From 2fb4a96a0ff91c615239073d7ce1fc6835621179 Mon Sep 17 00:00:00 2001 From: Yoriyasu Yano <430092+yorinasub17@users.noreply.github.com> Date: Thu, 27 Aug 2020 01:12:41 -0500 Subject: [PATCH 2/8] Fix dependencies-remote example --- examples/dependencies-remote/boilerplate.yml | 4 ++-- integration-tests/examples_test.go | 3 ++- .../examples-var-files/dependencies-remote/vars.yml | 6 ++++++ 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 test-fixtures/examples-var-files/dependencies-remote/vars.yml diff --git a/examples/dependencies-remote/boilerplate.yml b/examples/dependencies-remote/boilerplate.yml index 3e0084bf..b17f277f 100644 --- a/examples/dependencies-remote/boilerplate.yml +++ b/examples/dependencies-remote/boilerplate.yml @@ -18,14 +18,14 @@ variables: dependencies: - name: docs - template-url: "git@github.com:gruntwork-io/boilerplate.git//docs?ref=master" + template-url: "git@github.com:gruntwork-io/boilerplate.git//examples/docs?ref=master" output-folder: ./docs variables: - name: Title description: Enter the title of the docs page - name: website - template-url: "git@github.com:gruntwork-io/boilerplate.git//website?ref=master" + template-url: "git@github.com:gruntwork-io/boilerplate.git//examples/website?ref=master" output-folder: ./website variables: - name: Title diff --git a/integration-tests/examples_test.go b/integration-tests/examples_test.go index b5d0343f..919812e6 100644 --- a/integration-tests/examples_test.go +++ b/integration-tests/examples_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/gruntwork-io/boilerplate/cli" + "github.com/gruntwork-io/boilerplate/errors" "github.com/gruntwork-io/boilerplate/options" "github.com/gruntwork-io/boilerplate/util" ) @@ -112,7 +113,7 @@ func testExample(t *testing.T, templateFolder string, outputFolder string, varFi } err := app.Run(args) - assert.NoError(t, err) + assert.NoError(t, err, errors.PrintErrorWithStackTrace(err)) assertDirectoriesEqual(t, expectedOutputFolder, outputFolder) } diff --git a/test-fixtures/examples-var-files/dependencies-remote/vars.yml b/test-fixtures/examples-var-files/dependencies-remote/vars.yml new file mode 100644 index 00000000..2e0504d6 --- /dev/null +++ b/test-fixtures/examples-var-files/dependencies-remote/vars.yml @@ -0,0 +1,6 @@ +Version: 0.0.3 +Title: Dependencies example +website.Title: Boilerplate +docs.Title: Docs example +WelcomeText: Welcome! +Description: This is a boilerplate template that shows an example of using dependencies \ No newline at end of file From 1974dfd0b80beed6ec0bb0faa886ce7a5980ee00 Mon Sep 17 00:00:00 2001 From: Yoriyasu Yano <430092+yorinasub17@users.noreply.github.com> Date: Thu, 27 Aug 2020 01:16:21 -0500 Subject: [PATCH 3/8] Move download templates test to right place --- getter-helper/getter_helper_test.go | 34 ++++++++++++++++++++++++++++ templates/template_processor_test.go | 27 ---------------------- 2 files changed, 34 insertions(+), 27 deletions(-) create mode 100644 getter-helper/getter_helper_test.go diff --git a/getter-helper/getter_helper_test.go b/getter-helper/getter_helper_test.go new file mode 100644 index 00000000..51da8c0a --- /dev/null +++ b/getter-helper/getter_helper_test.go @@ -0,0 +1,34 @@ +package getter_helper + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/gruntwork-io/gruntwork-cli/errors" + "github.com/gruntwork-io/terratest/modules/git" + "github.com/gruntwork-io/terratest/modules/shell" + "github.com/stretchr/testify/require" +) + +func TestDownloadTemplatesToTempDir(t *testing.T) { + t.Parallel() + + pwd, err := os.Getwd() + require.NoError(t, err) + examplePath := filepath.Join(pwd, "..", "examples", "variables") + + branch := git.GetCurrentBranchName(t) + templateUrl := fmt.Sprintf("git@github.com:gruntwork-io/boilerplate.git//examples/variables?ref=%s", branch) + workingDir, workPath, err := DownloadTemplatesToTemporaryFolder(templateUrl) + defer os.RemoveAll(workingDir) + require.NoError(t, err, errors.PrintErrorWithStackTrace(err)) + + // Run diff to make sure there are no differences + cmd := shell.Command{ + Command: "diff", + Args: []string{examplePath, workPath}, + } + shell.RunCommand(t, cmd) +} diff --git a/templates/template_processor_test.go b/templates/template_processor_test.go index 7d4000dd..7e8d340f 100644 --- a/templates/template_processor_test.go +++ b/templates/template_processor_test.go @@ -1,42 +1,15 @@ package templates import ( - "fmt" "os" - "path/filepath" "testing" - "github.com/gruntwork-io/gruntwork-cli/errors" - "github.com/gruntwork-io/terratest/modules/git" - "github.com/gruntwork-io/terratest/modules/shell" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/gruntwork-io/boilerplate/options" "github.com/gruntwork-io/boilerplate/variables" ) -func TestDownloadTemplatesToTempDir(t *testing.T) { - t.Parallel() - - pwd, err := os.Getwd() - require.NoError(t, err) - examplePath := filepath.Join(pwd, "..", "examples", "variables") - - branch := git.GetCurrentBranchName(t) - templateUrl := fmt.Sprintf("git@github.com:gruntwork-io/boilerplate.git//examples/variables?ref=%s", branch) - workingDir, err := downloadTemplatesToTemporaryFolder(templateUrl) - defer os.RemoveAll(workingDir) - require.NoError(t, err, errors.PrintErrorWithStackTrace(err)) - - // Run diff to make sure there are no differences - cmd := shell.Command{ - Command: "diff", - Args: []string{examplePath, workingDir}, - } - shell.RunCommand(t, cmd) -} - func TestOutPath(t *testing.T) { t.Parallel() From 46d5336ca0ce1f41334cc546da76ddead4ef53fe Mon Sep 17 00:00:00 2001 From: Yoriyasu Yano <430092+yorinasub17@users.noreply.github.com> Date: Thu, 27 Aug 2020 01:31:22 -0500 Subject: [PATCH 4/8] Fix build --- integration-tests/examples_test.go | 16 ++++--- templates/template_processor_test.go | 6 +-- .../dependencies-remote/README.md | 6 +++ .../dependencies-remote/docs/README.md | 42 ++++++++++++++++++ .../my_example_file.py | 1 + .../dependencies-remote/website/index.html | 9 ++++ .../dependencies-remote/website/logo.png | Bin 0 -> 37261 bytes 7 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 test-fixtures/examples-expected-output/dependencies-remote/README.md create mode 100644 test-fixtures/examples-expected-output/dependencies-remote/docs/README.md create mode 100644 test-fixtures/examples-expected-output/dependencies-remote/docs/interpolated-folder-example-folder/my_example_file.py create mode 100644 test-fixtures/examples-expected-output/dependencies-remote/website/index.html create mode 100644 test-fixtures/examples-expected-output/dependencies-remote/website/logo.png diff --git a/integration-tests/examples_test.go b/integration-tests/examples_test.go index 919812e6..a5b64ad9 100644 --- a/integration-tests/examples_test.go +++ b/integration-tests/examples_test.go @@ -4,12 +4,12 @@ import ( "fmt" "io/ioutil" "os" - "os/exec" "path" "strings" "testing" "github.com/gruntwork-io/terratest/modules/git" + "github.com/gruntwork-io/terratest/modules/shell" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -80,6 +80,11 @@ func TestExamplesAsRemoteTemplate(t *testing.T) { continue } + if example.Name() == "variables" { + t.Logf("Skipping example %s because it is implicitly tested via dependencies.", example.Name()) + continue + } + t.Run(path.Base(example.Name()), func(t *testing.T) { templateFolder := fmt.Sprintf("git@github.com:gruntwork-io/boilerplate.git//examples/%s?ref=%s", example.Name(), branchName) outputFolder := path.Join(outputBasePath, example.Name()) @@ -121,8 +126,9 @@ func testExample(t *testing.T, templateFolder string, outputFolder string, varFi // takes a lot of code. Why waste time on that when this functionality is already nicely implemented in the Unix/Linux // "diff" command? We shell out to that command at test time. func assertDirectoriesEqual(t *testing.T, folderWithExpectedContents string, folderWithActualContents string) { - cmd := exec.Command("diff", "-r", "-u", folderWithExpectedContents, folderWithActualContents) - - _, err := cmd.Output() - assert.NoError(t, err) + cmd := shell.Command{ + Command: "diff", + Args: []string{"-r", "-u", folderWithExpectedContents, folderWithActualContents}, + } + shell.RunCommand(t, cmd) } diff --git a/templates/template_processor_test.go b/templates/template_processor_test.go index 7e8d340f..f8137517 100644 --- a/templates/template_processor_test.go +++ b/templates/template_processor_test.go @@ -58,19 +58,19 @@ func TestCloneOptionsForDependency(t *testing.T) { variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1"}, options.BoilerplateOptions{TemplateFolder: "/template/path/", OutputFolder: "/output/path/", NonInteractive: true, Vars: map[string]interface{}{}, OnMissingKey: options.ExitWithError}, map[string]interface{}{}, - options.BoilerplateOptions{TemplateFolder: "/template/dep1", OutputFolder: "/output/out1", NonInteractive: true, Vars: map[string]interface{}{}, OnMissingKey: options.ExitWithError}, + options.BoilerplateOptions{TemplateUrl: "../dep1", TemplateFolder: "/template/dep1", OutputFolder: "/output/out1", NonInteractive: true, Vars: map[string]interface{}{}, OnMissingKey: options.ExitWithError}, }, { variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1"}, options.BoilerplateOptions{TemplateFolder: "/template/path/", OutputFolder: "/output/path/", NonInteractive: false, Vars: map[string]interface{}{"foo": "bar"}, OnMissingKey: options.Invalid}, map[string]interface{}{"baz": "blah"}, - options.BoilerplateOptions{TemplateFolder: "/template/dep1", OutputFolder: "/output/out1", NonInteractive: false, Vars: map[string]interface{}{"baz": "blah"}, OnMissingKey: options.Invalid}, + options.BoilerplateOptions{TemplateUrl: "../dep1", TemplateFolder: "/template/dep1", OutputFolder: "/output/out1", NonInteractive: false, Vars: map[string]interface{}{"baz": "blah"}, OnMissingKey: options.Invalid}, }, { variables.Dependency{Name: "dep1", TemplateUrl: "{{ .foo }}", OutputFolder: "{{ .baz }}"}, options.BoilerplateOptions{TemplateFolder: "/template/path/", OutputFolder: "/output/path/", NonInteractive: false, Vars: map[string]interface{}{}, OnMissingKey: options.ExitWithError}, map[string]interface{}{"foo": "bar", "baz": "blah"}, - options.BoilerplateOptions{TemplateFolder: "/template/path/bar", OutputFolder: "/output/path/blah", NonInteractive: false, Vars: map[string]interface{}{"foo": "bar", "baz": "blah"}, OnMissingKey: options.ExitWithError}, + options.BoilerplateOptions{TemplateUrl: "bar", TemplateFolder: "/template/path/bar", OutputFolder: "/output/path/blah", NonInteractive: false, Vars: map[string]interface{}{"foo": "bar", "baz": "blah"}, OnMissingKey: options.ExitWithError}, }, } diff --git a/test-fixtures/examples-expected-output/dependencies-remote/README.md b/test-fixtures/examples-expected-output/dependencies-remote/README.md new file mode 100644 index 00000000..23a843c6 --- /dev/null +++ b/test-fixtures/examples-expected-output/dependencies-remote/README.md @@ -0,0 +1,6 @@ +# Dependencies example + +This is a boilerplate template that shows an example of using dependencies. It specifies both the +[docs](/examples/docs) and [website](/examples/website) examples as dependencies to show how one boilerplate template +can pull in another. It also defines all the variables needed for both of those dependencies at the top level to show +how variable inheritance works. diff --git a/test-fixtures/examples-expected-output/dependencies-remote/docs/README.md b/test-fixtures/examples-expected-output/dependencies-remote/docs/README.md new file mode 100644 index 00000000..d441b336 --- /dev/null +++ b/test-fixtures/examples-expected-output/dependencies-remote/docs/README.md @@ -0,0 +1,42 @@ +# Docs example + +This shows an example of how to use boilerplate to fill in parts of your documentation. + +## Variables + +Here is how you can use a variable: + +The latest version of my app is 0.0.3. + +You could create a CI job that, for each release, regenerates your docs with the latest value of the `Version` variable +passed in using the `--var` option. + +## Snippets + +Here is how to use the `snippet` helper to embed files or parts of files from source code: + +```html + + + {{.Title}} + + +

{{.WelcomeText}}

+ {{if .ShowLogo}}{{end}} + + +``` + +## Arithmetic + +Here is how you can use the arithmetic helpers to create a numbered list: + +1. Item +2. Item +3. Item + +And here is another way to do it using the slice helper: + +1. Item +2. Item +3. Item diff --git a/test-fixtures/examples-expected-output/dependencies-remote/docs/interpolated-folder-example-folder/my_example_file.py b/test-fixtures/examples-expected-output/dependencies-remote/docs/interpolated-folder-example-folder/my_example_file.py new file mode 100644 index 00000000..4ce1d4af --- /dev/null +++ b/test-fixtures/examples-expected-output/dependencies-remote/docs/interpolated-folder-example-folder/my_example_file.py @@ -0,0 +1 @@ +# This file and its parent folder both show an example of using Go template syntax and boilerplate variables in their names \ No newline at end of file diff --git a/test-fixtures/examples-expected-output/dependencies-remote/website/index.html b/test-fixtures/examples-expected-output/dependencies-remote/website/index.html new file mode 100644 index 00000000..d84f15b5 --- /dev/null +++ b/test-fixtures/examples-expected-output/dependencies-remote/website/index.html @@ -0,0 +1,9 @@ + + + Boilerplate + + +

Welcome!

+ + + \ No newline at end of file diff --git a/test-fixtures/examples-expected-output/dependencies-remote/website/logo.png b/test-fixtures/examples-expected-output/dependencies-remote/website/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4b4af5bb80a3b81f5b05aeb1fbf06febeb2006a5 GIT binary patch literal 37261 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 Dnw3^d literal 0 HcmV?d00001 From b7750e80357fdfc2daac97a6066e5b2c666443d9 Mon Sep 17 00:00:00 2001 From: Yoriyasu Yano <430092+yorinasub17@users.noreply.github.com> Date: Fri, 28 Aug 2020 14:16:08 -0500 Subject: [PATCH 5/8] Simplify get routine by removing goroutine --- getter-helper/getter_helper.go | 44 ++++------------------------------ 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/getter-helper/getter_helper.go b/getter-helper/getter_helper.go index a8eb8920..bdb7e23b 100644 --- a/getter-helper/getter_helper.go +++ b/getter-helper/getter_helper.go @@ -6,10 +6,8 @@ import ( "io/ioutil" "net/url" "os" - "os/signal" "path/filepath" "regexp" - "sync" getter "github.com/hashicorp/go-getter" urlhelper "github.com/hashicorp/go-getter/helper/url" @@ -71,14 +69,14 @@ func getForcedGetter(sourceUrl string) (string, string) { } // We use this code to force go-getter to copy files instead of creating symlinks. -func NewGetterClient(ctx context.Context, src string, dst string) (*getter.Client, error) { +func NewGetterClient(src string, dst string) (*getter.Client, error) { pwd, err := os.Getwd() if err != nil { return nil, errors.WithStackTrace(err) } client := &getter.Client{ - Ctx: ctx, + Ctx: context.Background(), Src: src, Dst: dst, Pwd: pwd, @@ -118,45 +116,13 @@ func DownloadTemplatesToTemporaryFolder(templateUrl string) (string, string, err mainPath, subDir := getter.SourceDirSubdir(templateUrl) outDir := filepath.Clean(filepath.Join(cloneDir, subDir)) - ctx, cancel := context.WithCancel(context.Background()) - client, err := NewGetterClient(ctx, mainPath, cloneDir) + client, err := NewGetterClient(mainPath, cloneDir) if err != nil { - cancel() return workingDir, outDir, err } - // Start getter in the background so we can trap and forward interrupt signals - wg := sync.WaitGroup{} - wg.Add(1) - errChan := make(chan error, 2) - go func() { - defer wg.Done() - defer cancel() - if err := client.Get(); err != nil { - errChan <- err - } - }() - - // Signal handler - c := make(chan os.Signal) - signal.Notify(c, os.Interrupt) - - select { - // Interrupted - case sig := <-c: - signal.Reset(os.Interrupt) - cancel() - wg.Wait() - return workingDir, outDir, fmt.Errorf("Download interrupted with signal %v", sig) - - // No errors - case <-ctx.Done(): - wg.Wait() - return workingDir, outDir, nil - - // There was an error - case err := <-errChan: - wg.Wait() + if err := client.Get(); err != nil { return workingDir, outDir, err } + return workingDir, outDir, nil } From ea516e0fff31bb13db6f551b1e6c082717d0f91b Mon Sep 17 00:00:00 2001 From: Yoriyasu Yano <430092+yorinasub17@users.noreply.github.com> Date: Fri, 28 Aug 2020 14:48:43 -0500 Subject: [PATCH 6/8] Attempt to solve windows long path issue using UNC --- getter-helper/getter_helper.go | 3 +- getter-helper/tmp_path_unix.go | 11 ++++ getter-helper/tmp_path_windows.go | 94 +++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 getter-helper/tmp_path_unix.go create mode 100644 getter-helper/tmp_path_windows.go diff --git a/getter-helper/getter_helper.go b/getter-helper/getter_helper.go index bdb7e23b..a8c5ae2a 100644 --- a/getter-helper/getter_helper.go +++ b/getter-helper/getter_helper.go @@ -3,7 +3,6 @@ package getter_helper import ( "context" "fmt" - "io/ioutil" "net/url" "os" "path/filepath" @@ -102,7 +101,7 @@ func NewGetterClient(src string, dst string) (*getter.Client, error) { // temporary folder and returns the path to that folder. If there is a subdir in the template URL, return the combined // path as well. func DownloadTemplatesToTemporaryFolder(templateUrl string) (string, string, error) { - workingDir, err := ioutil.TempDir("", "boilerplate-cache*") + workingDir, err := getTempFolder() if err != nil { return workingDir, workingDir, errors.WithStackTrace(err) } diff --git a/getter-helper/tmp_path_unix.go b/getter-helper/tmp_path_unix.go new file mode 100644 index 00000000..6daa5f06 --- /dev/null +++ b/getter-helper/tmp_path_unix.go @@ -0,0 +1,11 @@ +// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris + +package getter_helper + +import ( + "io/ioutil" +) + +func getTempFolder() (string, error) { + return ioutil.TempDir("", "boilerplate-cache*") +} diff --git a/getter-helper/tmp_path_windows.go b/getter-helper/tmp_path_windows.go new file mode 100644 index 00000000..84f0e8a5 --- /dev/null +++ b/getter-helper/tmp_path_windows.go @@ -0,0 +1,94 @@ +//+build windows + +package getter_helper + +import ( + "io/ioutil" + "os" + "path/filepath" +) + +// getTempFolder on Windows will return the computed temporary folder in UNC path to avoid long path issues. +// See https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths for more information on Windows Long +// Paths and UNC. +func getTempFolder() (string, error) { + workingDir, err := ioutil.TempDir("", "boilerplate-cache*") + if err != nil { + return workingDir, err + } + return fixLongPath(workingDir), err +} + +// The following function is copied almost verbatim from +// https://github.com/golang/go/blob/master/src/os/path_windows.go +// with the following modifications: +// +// - Always convert to long form (this is so that it stays long form even after elements are appended to the path) +// - Reference functions in the stdlib as opposed to internal private funcs. +// +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// fixLongPath returns the extended-length (\\?\-prefixed) form of +// path when needed, in order to avoid the default 260 character file +// path limit imposed by Windows. If path is not easily converted to +// the extended-length form (for example, if path is a relative path +// or contains .. elements), or is short enough, fixLongPath returns +// path unmodified. +// +// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath +func fixLongPath(path string) string { + // The extended form begins with \\?\, as in + // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. + // The extended form disables evaluation of . and .. path + // elements and disables the interpretation of / as equivalent + // to \. The conversion here rewrites / to \ and elides + // . elements as well as trailing or duplicate separators. For + // simplicity it avoids the conversion entirely for relative + // paths or paths containing .. elements. For now, + // \\server\share paths are not converted to + // \\?\UNC\server\share paths because the rules for doing so + // are less well-specified. + if len(path) >= 2 && path[:2] == `\\` { + // Don't canonicalize UNC paths. + return path + } + if !filepath.IsAbs(path) { + // Relative path + return path + } + + const prefix = `\\?` + + pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) + copy(pathbuf, prefix) + n := len(path) + r, w := 0, len(prefix) + for r < n { + switch { + case os.IsPathSeparator(path[r]): + // empty block + r++ + case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): + // /./ + r++ + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): + // /../ is currently unhandled + return path + default: + pathbuf[w] = '\\' + w++ + for ; r < n && !os.IsPathSeparator(path[r]); r++ { + pathbuf[w] = path[r] + w++ + } + } + } + // A drive's root directory needs a trailing \ + if w == len(`\\?\c:`) { + pathbuf[w] = '\\' + w++ + } + return string(pathbuf[:w]) +} From 89ca2b521078efa314ae35529ee78fc360834a5a Mon Sep 17 00:00:00 2001 From: Yoriyasu Yano <430092+yorinasub17@users.noreply.github.com> Date: Sat, 29 Aug 2020 20:07:39 -0500 Subject: [PATCH 7/8] Support windows by allowing urlencoded file paths and canonicalizing filepaths --- .../{{.FileName%7csnakeCase}}.py} | 0 .../example/Example.java | 0 integration-tests/examples_test.go | 12 ------- integration-tests/test_helpers_unix.go | 20 +++++++++++ integration-tests/test_helpers_windows.go | 36 +++++++++++++++++++ templates/template_processor.go | 15 ++++++-- templates/template_processor_test.go | 4 +-- 7 files changed, 71 insertions(+), 16 deletions(-) rename examples/docs/{interpolated-folder-{{.SubFolderName|dasherize}}/{{.FileName|snakeCase}}.py => interpolated-folder-{{.SubFolderName%7Cdasherize}}/{{.FileName%7csnakeCase}}.py} (100%) rename examples/java-project/com/{{{.CompanyName | dasherize | downcase}} => {{.CompanyName %7C dasherize %7C downcase}}}/example/Example.java (100%) create mode 100644 integration-tests/test_helpers_unix.go create mode 100644 integration-tests/test_helpers_windows.go diff --git a/examples/docs/interpolated-folder-{{.SubFolderName|dasherize}}/{{.FileName|snakeCase}}.py b/examples/docs/interpolated-folder-{{.SubFolderName%7Cdasherize}}/{{.FileName%7csnakeCase}}.py similarity index 100% rename from examples/docs/interpolated-folder-{{.SubFolderName|dasherize}}/{{.FileName|snakeCase}}.py rename to examples/docs/interpolated-folder-{{.SubFolderName%7Cdasherize}}/{{.FileName%7csnakeCase}}.py diff --git a/examples/java-project/com/{{.CompanyName | dasherize | downcase}}/example/Example.java b/examples/java-project/com/{{.CompanyName %7C dasherize %7C downcase}}/example/Example.java similarity index 100% rename from examples/java-project/com/{{.CompanyName | dasherize | downcase}}/example/Example.java rename to examples/java-project/com/{{.CompanyName %7C dasherize %7C downcase}}/example/Example.java diff --git a/integration-tests/examples_test.go b/integration-tests/examples_test.go index a5b64ad9..4042bdf3 100644 --- a/integration-tests/examples_test.go +++ b/integration-tests/examples_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/gruntwork-io/terratest/modules/git" - "github.com/gruntwork-io/terratest/modules/shell" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -121,14 +120,3 @@ func testExample(t *testing.T, templateFolder string, outputFolder string, varFi assert.NoError(t, err, errors.PrintErrorWithStackTrace(err)) assertDirectoriesEqual(t, expectedOutputFolder, outputFolder) } - -// Diffing two directories to ensure they have the exact same files, contents, etc and showing exactly what's different -// takes a lot of code. Why waste time on that when this functionality is already nicely implemented in the Unix/Linux -// "diff" command? We shell out to that command at test time. -func assertDirectoriesEqual(t *testing.T, folderWithExpectedContents string, folderWithActualContents string) { - cmd := shell.Command{ - Command: "diff", - Args: []string{"-r", "-u", folderWithExpectedContents, folderWithActualContents}, - } - shell.RunCommand(t, cmd) -} diff --git a/integration-tests/test_helpers_unix.go b/integration-tests/test_helpers_unix.go new file mode 100644 index 00000000..f01438fd --- /dev/null +++ b/integration-tests/test_helpers_unix.go @@ -0,0 +1,20 @@ +// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris + +package integration_tests + +import ( + "testing" + + "github.com/gruntwork-io/terratest/modules/shell" +) + +// Diffing two directories to ensure they have the exact same files, contents, etc and showing exactly what's different +// takes a lot of code. Why waste time on that when this functionality is already nicely implemented in the Unix/Linux +// "diff" command? We shell out to that command at test time. +func assertDirectoriesEqual(t *testing.T, folderWithExpectedContents string, folderWithActualContents string) { + cmd := shell.Command{ + Command: "diff", + Args: []string{"-r", "-u", folderWithExpectedContents, folderWithActualContents}, + } + shell.RunCommand(t, cmd) +} diff --git a/integration-tests/test_helpers_windows.go b/integration-tests/test_helpers_windows.go new file mode 100644 index 00000000..d49d3e7f --- /dev/null +++ b/integration-tests/test_helpers_windows.go @@ -0,0 +1,36 @@ +//+build windows + +package integration_tests + +import ( + "fmt" + "os/exec" + "testing" + + "github.com/gruntwork-io/terratest/modules/shell" + "github.com/stretchr/testify/require" +) + +// Like Unix/Linux, diffing two files is relatively easier than golang native functions in powershell. +// Get the contents of each folder and then compare them. +// Inspired by https://devblogs.microsoft.com/scripting/easily-compare-two-folders-by-using-powershell/ +func assertDirectoriesEqual(t *testing.T, folderWithExpectedContents string, folderWithActualContents string) { + powershellDiffTemplate := `$fone = Get-ChildItem -Recurse -path %s +$ftwo = Get-ChildItem -Recurse -path %s +Compare-Object -ReferenceObject $fone -DifferenceObject $ftwo +` + powershellDiffCmd := fmt.Sprintf(powershellDiffTemplate, folderWithExpectedContents, folderWithActualContents) + runPowershell(t, powershellDiffCmd) +} + +func runPowershell(t *testing.T, args ...string) { + ps, err := exec.LookPath("powershell.exe") + require.NoError(t, err) + + psArgs := append([]string{"-NoProfile", "-NonInteractive"}, args...) + cmd := shell.Command{ + Command: ps, + Args: psArgs, + } + shell.RunCommand(t, cmd) +} diff --git a/templates/template_processor.go b/templates/template_processor.go index e6deb944..379974f5 100644 --- a/templates/template_processor.go +++ b/templates/template_processor.go @@ -3,6 +3,7 @@ package templates import ( "fmt" "io/ioutil" + "net/url" "os" "path" "path/filepath" @@ -330,7 +331,13 @@ func outPath(file string, opts *options.BoilerplateOptions, variables map[string return "", errors.WithStackTrace(err) } - interpolatedFilePath, err := render.RenderTemplate(file, file, variables, opts) + // | is an illegal filename char in Windows, so we also support urlencoded chars in the path. To support this, we + // first urldecode the file before passing it through. + urlDecodedFile, err := url.QueryUnescape(file) + if err != nil { + return "", errors.WithStackTrace(err) + } + interpolatedFilePath, err := render.RenderTemplate(file, urlDecodedFile, variables, opts) if err != nil { return "", errors.WithStackTrace(err) } @@ -383,5 +390,9 @@ func processTemplate(templatePath string, opts *options.BoilerplateOptions, vari // Return true if this is a path that should not be copied func shouldSkipPath(path string, opts *options.BoilerplateOptions) bool { - return path == opts.TemplateFolder || path == config.BoilerplateConfigPath(opts.TemplateFolder) + // Canonicalize paths for os portability. + canonicalPath := filepath.ToSlash(path) + canonicalTemplateFolder := filepath.ToSlash(opts.TemplateFolder) + canonicalBoilerplateConfigPath := filepath.ToSlash(config.BoilerplateConfigPath(opts.TemplateFolder)) + return canonicalPath == canonicalTemplateFolder || canonicalPath == canonicalBoilerplateConfigPath } diff --git a/templates/template_processor_test.go b/templates/template_processor_test.go index f8137517..9f2bb672 100644 --- a/templates/template_processor_test.go +++ b/templates/template_processor_test.go @@ -14,7 +14,7 @@ func TestOutPath(t *testing.T) { t.Parallel() pwd, err := os.Getwd() - assert.Nil(t, err, "Couldn't get working directory") + assert.NoError(t, err) testCases := []struct { file string @@ -40,7 +40,7 @@ func TestOutPath(t *testing.T) { OnMissingConfig: options.Exit, } actual, err := outPath(testCase.file, &opts, testCase.variables) - assert.Nil(t, err, "Got unexpected error (file = %s, templateFolder = %s, outputFolder = %s, and variables = %s): %v", testCase.file, testCase.templateFolder, testCase.outputFolder, testCase.variables, err) + assert.NoError(t, err, "Got unexpected error (file = %s, templateFolder = %s, outputFolder = %s, and variables = %s): %v", testCase.file, testCase.templateFolder, testCase.outputFolder, testCase.variables, err) assert.Equal(t, testCase.expected, actual, "(file = %s, templateFolder = %s, outputFolder = %s, and variables = %s)", testCase.file, testCase.templateFolder, testCase.outputFolder, testCase.variables) } } From e77854c05d85af27c9ed0a75d6c02bd9f88f2af6 Mon Sep 17 00:00:00 2001 From: Yoriyasu Yano <430092+yorinasub17@users.noreply.github.com> Date: Sat, 29 Aug 2020 22:22:42 -0500 Subject: [PATCH 8/8] Split out shell tests to only run on unix --- integration-tests/examples_test.go | 51 +++++++++++++-------- integration-tests/examples_unix_test.go | 59 +++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 integration-tests/examples_unix_test.go diff --git a/integration-tests/examples_test.go b/integration-tests/examples_test.go index 4042bdf3..dc19e3a3 100644 --- a/integration-tests/examples_test.go +++ b/integration-tests/examples_test.go @@ -29,7 +29,7 @@ func TestExamples(t *testing.T) { outputBasePath, err := ioutil.TempDir("", "boilerplate-test-output") require.NoError(t, err) - defer os.Remove(outputBasePath) + defer os.RemoveAll(outputBasePath) examples, err := ioutil.ReadDir(examplesBasePath) require.NoError(t, err) @@ -38,6 +38,10 @@ func TestExamples(t *testing.T) { if !example.IsDir() { continue } + if strings.Contains(example.Name(), "shell") { + // This is captured in TestExamplesShell + continue + } t.Run(path.Base(example.Name()), func(t *testing.T) { templateFolder := path.Join(examplesBasePath, example.Name()) @@ -69,29 +73,40 @@ func TestExamplesAsRemoteTemplate(t *testing.T) { outputBasePath, err := ioutil.TempDir("", "boilerplate-test-output") require.NoError(t, err) - defer os.Remove(outputBasePath) + defer os.RemoveAll(outputBasePath) examples, err := ioutil.ReadDir(examplesBasePath) require.NoError(t, err) - for _, example := range examples { - if !example.IsDir() { - continue - } + // Insulate the following parallel tests in a group so that cleanup routines run after all tests are done. + t.Run("group", func(t *testing.T) { + for _, example := range examples { + // Capture range variable to avoid it changing on each iteration during the tests + example := example - if example.Name() == "variables" { - t.Logf("Skipping example %s because it is implicitly tested via dependencies.", example.Name()) - continue - } + if !example.IsDir() { + continue + } + if strings.Contains(example.Name(), "shell") { + // This is captured in TestExamplesShell + continue + } - t.Run(path.Base(example.Name()), func(t *testing.T) { - templateFolder := fmt.Sprintf("git@github.com:gruntwork-io/boilerplate.git//examples/%s?ref=%s", example.Name(), branchName) - outputFolder := path.Join(outputBasePath, example.Name()) - varFile := path.Join(examplesVarFilesBasePath, example.Name(), "vars.yml") - expectedOutputFolder := path.Join(examplesExpectedOutputBasePath, example.Name()) - testExample(t, templateFolder, outputFolder, varFile, expectedOutputFolder, string(options.ExitWithError)) - }) - } + if example.Name() == "variables" { + t.Logf("Skipping example %s because it is implicitly tested via dependencies.", example.Name()) + continue + } + + t.Run(path.Base(example.Name()), func(t *testing.T) { + t.Parallel() + templateFolder := fmt.Sprintf("git@github.com:gruntwork-io/boilerplate.git//examples/%s?ref=%s", example.Name(), branchName) + outputFolder := path.Join(outputBasePath, example.Name()) + varFile := path.Join(examplesVarFilesBasePath, example.Name(), "vars.yml") + expectedOutputFolder := path.Join(examplesExpectedOutputBasePath, example.Name()) + testExample(t, templateFolder, outputFolder, varFile, expectedOutputFolder, string(options.ExitWithError)) + }) + } + }) } diff --git a/integration-tests/examples_unix_test.go b/integration-tests/examples_unix_test.go new file mode 100644 index 00000000..08befe6c --- /dev/null +++ b/integration-tests/examples_unix_test.go @@ -0,0 +1,59 @@ +// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris + +// The following tests should only be run on unix machines + +package integration_tests + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "testing" + + "github.com/gruntwork-io/boilerplate/options" + "github.com/gruntwork-io/terratest/modules/git" + "github.com/stretchr/testify/require" +) + +func TestExamplesShell(t *testing.T) { + t.Parallel() + + branchName := git.GetCurrentBranchName(t) + examplesBasePath := "../examples" + examplesExpectedOutputBasePath := "../test-fixtures/examples-expected-output" + examplesVarFilesBasePath := "../test-fixtures/examples-var-files" + + outputBasePath, err := ioutil.TempDir("", "boilerplate-test-output") + require.NoError(t, err) + defer os.RemoveAll(outputBasePath) + + shellExamples := []string{"shell", "shell-disabled"} + // Insulate the following parallel tests in a group so that cleanup routines run after all tests are done. + t.Run("group", func(t *testing.T) { + for _, example := range shellExamples { + // Capture range variable to avoid it changing on each iteration during the tests + example := example + + outputFolder := path.Join(outputBasePath, example) + varFile := path.Join(examplesVarFilesBasePath, example, "vars.yml") + expectedOutputFolder := path.Join(examplesExpectedOutputBasePath, example) + + t.Run(example, func(t *testing.T) { + t.Parallel() + templateFolder := path.Join(examplesBasePath, example) + for _, missingKeyAction := range options.AllMissingKeyActions { + t.Run(fmt.Sprintf("%s-missing-key-%s", example, string(missingKeyAction)), func(t *testing.T) { + testExample(t, templateFolder, outputFolder, varFile, expectedOutputFolder, string(missingKeyAction)) + }) + } + }) + + t.Run(fmt.Sprintf("%s-remote", example), func(t *testing.T) { + t.Parallel() + templateFolder := fmt.Sprintf("git@github.com:gruntwork-io/boilerplate.git//examples/%s?ref=%s", example, branchName) + testExample(t, templateFolder, outputFolder, varFile, expectedOutputFolder, string(options.ExitWithError)) + }) + } + }) +}