From 125178d94372a803138fe3693765cd281074434b Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Thu, 23 Jul 2020 09:25:07 +0200 Subject: [PATCH] core: Decode: when in HCL2 decoding mode; reset the whole struct before preparing it. (#9622) * core: Decode when in HCL2 decoding mode; reset the whole struct before preparing it. * HCL2: add path variables + docs & tests Co-authored-by: Megan Marsh --- command/build_test.go | 40 +++++++++++++++++++ command/console_test.go | 7 ++++ command/exec_test.go | 2 + command/test-fixtures/hcl/reprepare/hello.sh | 1 + .../hcl/reprepare/shell-local-windows.pkr.hcl | 13 ++++++ .../hcl/reprepare/shell-local.pkr.hcl | 13 ++++++ .../test-fixtures/hcl/reprepare/test_cmd.cmd | 1 + common/shell-local/run.go | 15 ++++++- hcl2template/common_test.go | 3 ++ hcl2template/parser.go | 11 ++++- hcl2template/types.packer_config.go | 7 ++++ helper/config/decode.go | 12 ++++++ website/data/docs-navigation.js | 1 + .../pages/docs/from-1.5/path-variables.mdx | 14 +++++++ 14 files changed, 137 insertions(+), 3 deletions(-) create mode 100755 command/test-fixtures/hcl/reprepare/hello.sh create mode 100644 command/test-fixtures/hcl/reprepare/shell-local-windows.pkr.hcl create mode 100644 command/test-fixtures/hcl/reprepare/shell-local.pkr.hcl create mode 100644 command/test-fixtures/hcl/reprepare/test_cmd.cmd create mode 100644 website/pages/docs/from-1.5/path-variables.mdx diff --git a/command/build_test.go b/command/build_test.go index 1a4351d08..5391499d7 100644 --- a/command/build_test.go +++ b/command/build_test.go @@ -7,6 +7,8 @@ import ( "math" "os" "path/filepath" + "runtime" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -323,6 +325,44 @@ func TestBuild(t *testing.T) { } } +func Test_build_output(t *testing.T) { + + tc := []struct { + command []string + env []string + expected string + runtime string + }{ + {[]string{"build", "--color=false", testFixture("hcl", "reprepare", "shell-local.pkr.hcl")}, nil, + ` + null.example: hello from the NULL builder packeruser +Build 'null.example' finished. +`, "posix"}, + {[]string{"build", "--color=false", testFixture("hcl", "reprepare", "shell-local-windows.pkr.hcl")}, nil, + ` + null.example: hello from the NULL builder packeruser +Build 'null.example' finished. +`, "windows"}, + } + + for _, tc := range tc { + if (runtime.GOOS == "windows") != (tc.runtime == "windows") { + continue + } + t.Run(fmt.Sprintf("packer %s", tc.command), func(t *testing.T) { + p := helperCommand(t, tc.command...) + p.Env = append(p.Env, tc.env...) + bs, err := p.Output() + if err != nil { + t.Fatalf("%v: %s", err, bs) + } + if !strings.Contains(string(bs), tc.expected) { + t.Fatalf("Should have given output %s.\nReceived: %s", tc.expected, string(bs)) + } + }) + } +} + func TestBuildOnlyFileCommaFlags(t *testing.T) { c := &BuildCommand{ Meta: testMetaFile(t), diff --git a/command/console_test.go b/command/console_test.go index 42b41d47a..23d56296f 100644 --- a/command/console_test.go +++ b/command/console_test.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "os" "path/filepath" "strings" "testing" @@ -12,6 +13,10 @@ import ( ) func Test_console(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("Getwd: %v", err) + } tc := []struct { piped string @@ -25,6 +30,8 @@ func Test_console(t *testing.T) { {"upper(var.fruit)", []string{"console", filepath.Join(testFixture("var-arg"), "fruit_builder.pkr.hcl")}, []string{"PKR_VAR_fruit=potato"}, "POTATO\n"}, {"1 + 5", []string{"console", "--config-type=hcl2"}, nil, "6\n"}, {"var.images", []string{"console", filepath.Join(testFixture("var-arg"), "map.pkr.hcl")}, nil, "{\n" + ` "key" = "value"` + "\n}\n"}, + {"path.cwd", []string{"console", filepath.Join(testFixture("var-arg"), "map.pkr.hcl")}, nil, strings.ReplaceAll(cwd, `\`, `/`) + "\n"}, + {"path.root", []string{"console", filepath.Join(testFixture("var-arg"), "map.pkr.hcl")}, nil, strings.ReplaceAll(testFixture("var-arg"), `\`, `/`) + "\n"}, } for _, tc := range tc { diff --git a/command/exec_test.go b/command/exec_test.go index d9508a090..61769f962 100644 --- a/command/exec_test.go +++ b/command/exec_test.go @@ -87,6 +87,8 @@ func TestHelperProcess(*testing.T) { os.Exit((&ConsoleCommand{Meta: commandMeta()}).Run(args)) case "inspect": os.Exit((&InspectCommand{Meta: commandMeta()}).Run(args)) + case "build": + os.Exit((&BuildCommand{Meta: commandMeta()}).Run(args)) default: fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd) os.Exit(2) diff --git a/command/test-fixtures/hcl/reprepare/hello.sh b/command/test-fixtures/hcl/reprepare/hello.sh new file mode 100755 index 000000000..e3cd0fc88 --- /dev/null +++ b/command/test-fixtures/hcl/reprepare/hello.sh @@ -0,0 +1 @@ +echo hello from the ${BUILDER} builder ${USER} \ No newline at end of file diff --git a/command/test-fixtures/hcl/reprepare/shell-local-windows.pkr.hcl b/command/test-fixtures/hcl/reprepare/shell-local-windows.pkr.hcl new file mode 100644 index 000000000..a09cba13e --- /dev/null +++ b/command/test-fixtures/hcl/reprepare/shell-local-windows.pkr.hcl @@ -0,0 +1,13 @@ +source "null" "example" { + communicator = "none" +} + +build { + sources = [ + "source.null.example" + ] + provisioner "shell-local" { + script = "./${path.root}/test_cmd.cmd" + environment_vars = ["USER=packeruser", "BUILDER=${upper(build.ID)}"] + } +} diff --git a/command/test-fixtures/hcl/reprepare/shell-local.pkr.hcl b/command/test-fixtures/hcl/reprepare/shell-local.pkr.hcl new file mode 100644 index 000000000..5e2fcc12c --- /dev/null +++ b/command/test-fixtures/hcl/reprepare/shell-local.pkr.hcl @@ -0,0 +1,13 @@ +source "null" "example" { + communicator = "none" +} + +build { + sources = [ + "source.null.example" + ] + provisioner "shell-local" { + script = "./${path.root}/hello.sh" + environment_vars = ["USER=packeruser", "BUILDER=${upper(build.ID)}"] + } +} diff --git a/command/test-fixtures/hcl/reprepare/test_cmd.cmd b/command/test-fixtures/hcl/reprepare/test_cmd.cmd new file mode 100644 index 000000000..f0c984af8 --- /dev/null +++ b/command/test-fixtures/hcl/reprepare/test_cmd.cmd @@ -0,0 +1 @@ +echo hello from the %BUILDER% builder %USER% \ No newline at end of file diff --git a/common/shell-local/run.go b/common/shell-local/run.go index 8f2423ac1..7bf657123 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "os" + "path/filepath" "runtime" "sort" "strings" @@ -70,7 +71,17 @@ func Run(ctx context.Context, ui packer.Ui, config *Config, generatedData map[st } for _, script := range scripts { - interpolatedCmds, err := createInterpolatedCommands(config, script, flattenedEnvVars) + // use absolute path in case the script is linked with forward slashes + // on windows. + absScript, err := filepath.Abs(script) + if err != nil { + return false, fmt.Errorf( + "Error executing script: %s\n%v\n", + absScript, + err, + ) + } + interpolatedCmds, err := createInterpolatedCommands(config, absScript, flattenedEnvVars) if err != nil { return false, err } @@ -91,7 +102,7 @@ func Run(ctx context.Context, ui packer.Ui, config *Config, generatedData map[st return false, fmt.Errorf( "Error executing script: %s\n\n"+ "Please see output above for more information.", - script) + absScript) } if err := config.ValidExitCode(cmd.ExitStatus()); err != nil { diff --git a/hcl2template/common_test.go b/hcl2template/common_test.go index 781febd90..3436b6d86 100644 --- a/hcl2template/common_test.go +++ b/hcl2template/common_test.go @@ -77,6 +77,9 @@ func testParse(t *testing.T, tests []parseTest) { ProvisionerBlock{}, PostProcessorBlock{}, ), + cmpopts.IgnoreFields(PackerConfig{}, + "Cwd", // Cwd will change for every computer + ), cmpopts.IgnoreTypes(HCL2Ref{}), cmpopts.IgnoreTypes([]hcl.Range{}), cmpopts.IgnoreTypes(hcl.Range{}), diff --git a/hcl2template/parser.go b/hcl2template/parser.go index f251d6305..c2544c72b 100644 --- a/hcl2template/parser.go +++ b/hcl2template/parser.go @@ -70,7 +70,7 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st hclFiles, jsonFiles, moreDiags := GetHCL2Files(filename, hcl2FileExt, hcl2JsonFileExt) diags = append(diags, moreDiags...) if len(hclFiles)+len(jsonFiles) == 0 { - diags = append(moreDiags, &hcl.Diagnostic{ + diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Could not find any config file in " + filename, Detail: "A config file must be suffixed with `.pkr.hcl` or " + @@ -96,8 +96,17 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st if isDir, err := isDir(basedir); err == nil && !isDir { basedir = filepath.Dir(basedir) } + wd, err := os.Getwd() + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Could not find current working directory", + Detail: err.Error(), + }) + } cfg := &PackerConfig{ Basedir: basedir, + Cwd: wd, builderSchemas: p.BuilderSchemas, provisionersSchemas: p.ProvisionersSchemas, postProcessorsSchemas: p.PostProcessorsSchemas, diff --git a/hcl2template/types.packer_config.go b/hcl2template/types.packer_config.go index dd72a6955..946f453dd 100644 --- a/hcl2template/types.packer_config.go +++ b/hcl2template/types.packer_config.go @@ -16,6 +16,8 @@ import ( type PackerConfig struct { // Directory where the config files are defined Basedir string + // directory Packer was called from + Cwd string // Available Source blocks Sources map[SourceRef]SourceBlock @@ -49,6 +51,7 @@ type ValidationOptions struct { const ( inputVariablesAccessor = "var" localsAccessor = "local" + pathVariablesAccessor = "path" sourcesAccessor = "source" buildAccessor = "build" ) @@ -69,6 +72,10 @@ func (cfg *PackerConfig) EvalContext(variables map[string]cty.Value) *hcl.EvalCo "name": cty.UnknownVal(cty.String), }), buildAccessor: cty.UnknownVal(cty.EmptyObject), + pathVariablesAccessor: cty.ObjectVal(map[string]cty.Value{ + "cwd": cty.StringVal(strings.ReplaceAll(cfg.Cwd, `\`, `/`)), + "root": cty.StringVal(strings.ReplaceAll(cfg.Basedir, `\`, `/`)), + }), }, } for k, v := range variables { diff --git a/helper/config/decode.go b/helper/config/decode.go index 54eaa5068..66eb02d53 100644 --- a/helper/config/decode.go +++ b/helper/config/decode.go @@ -72,6 +72,18 @@ func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error { return err } raws[i] = raw + { + // reset target to zero. + // In HCL2, we need to prepare provisioners/post-processors after a + // builder has started in order to have build values correctly + // extrapolated. Packer plugins have never been prepared twice in + // the past and some of them set fields during their Validation + // steps; which end up in an invalid provisioner/post-processor, + // like in [GH-9596]. This ensures Packer plugin will be reset + // right before we Prepare them. + p := reflect.ValueOf(target).Elem() + p.Set(reflect.Zero(p.Type())) + } } if config == nil { config = &DecodeOpts{Interpolate: true} diff --git a/website/data/docs-navigation.js b/website/data/docs-navigation.js index 530f11472..05c7030a6 100644 --- a/website/data/docs-navigation.js +++ b/website/data/docs-navigation.js @@ -144,6 +144,7 @@ export default [ 'variables', 'locals', 'contextual-variables', + 'path-variables', 'syntax', 'onlyexcept', 'expressions', diff --git a/website/pages/docs/from-1.5/path-variables.mdx b/website/pages/docs/from-1.5/path-variables.mdx new file mode 100644 index 000000000..619b7526b --- /dev/null +++ b/website/pages/docs/from-1.5/path-variables.mdx @@ -0,0 +1,14 @@ +--- +layout: docs +page_title: Path Variables - HCL Configuration Language +sidebar_title: Path Variables +description: |- + Special variables provide directory information. This page covers all path + variables. +--- + +# Path variables + +- `path.cwd`: the directory from where Packer was started. + +- `path.root`: the directory of the input HCL file or the input folder.