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 <megan@hashicorp.com>
This commit is contained in:
Adrien Delorme 2020-07-23 09:25:07 +02:00 committed by GitHub
parent 1f6473b4c1
commit 125178d943
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 137 additions and 3 deletions

View File

@ -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),

View File

@ -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 {

View File

@ -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)

View File

@ -0,0 +1 @@
echo hello from the ${BUILDER} builder ${USER}

View File

@ -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)}"]
}
}

View File

@ -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)}"]
}
}

View File

@ -0,0 +1 @@
echo hello from the %BUILDER% builder %USER%

View File

@ -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 {

View File

@ -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{}),

View File

@ -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,

View File

@ -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 {

View File

@ -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}

View File

@ -144,6 +144,7 @@ export default [
'variables',
'locals',
'contextual-variables',
'path-variables',
'syntax',
'onlyexcept',
'expressions',

View File

@ -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.