Skip to content

Commit

Permalink
Merge branch 'main' into docs/seo-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
trujillo-adam authored Nov 29, 2024
2 parents dbc12cf + b66fbb0 commit f5bdfd6
Show file tree
Hide file tree
Showing 30 changed files with 448 additions and 204 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
run: |
# We run tests for all packages from all modules in this repository.
for dir in $(go list -m -f '{{.Dir}}' github.com/hashicorp/terraform/...); do
(cd $dir && go test "./...")
(cd $dir && go test -cover "./...")
done
race-tests:
Expand Down
47 changes: 16 additions & 31 deletions internal/backend/local/backend_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,33 +259,26 @@ func (b *Local) opApply(
// same parsing logic from the plan to generate the diagnostics.
undeclaredVariables := map[string]backendrun.UnparsedVariableValue{}

for varName, rawV := range op.Variables {
parsedVars, _ := backendrun.ParseVariableValues(op.Variables, lr.Config.Module.Variables)

for varName := range op.Variables {
parsedVar, parsed := parsedVars[varName]

decl, ok := lr.Config.Module.Variables[varName]
if !ok {
if !ok || !parsed {
// We'll try to parse this and handle diagnostics for missing
// variables with ParseUndeclaredVariableValues after.
undeclaredVariables[varName] = rawV
continue
}

// We're "parsing" only to get the resulting value's SourceType,
// so we'll use configs.VariableParseLiteral just because it's
// the most liberal interpretation and so least likely to
// fail with an unrelated error.
v, _ := rawV.ParseVariableValue(configs.VariableParseLiteral)
if v == nil {
// We'll ignore any that don't parse at all, because
// they'll fail elsewhere in this process anyway.
undeclaredVariables[varName] = op.Variables[varName]
continue
}

var rng *hcl.Range
if v.HasSourceRange() {
rng = v.SourceRange.ToHCL().Ptr()
if parsedVar.HasSourceRange() {
rng = parsedVar.SourceRange.ToHCL().Ptr()
}

// If the var is declared as ephemeral in config, go ahead and handle it
if ok && decl.Ephemeral {
if decl.Ephemeral {
// Determine whether this is an apply-time variable, i.e. an
// ephemeral variable that was set (non-null) during the
// planning phase.
Expand All @@ -311,17 +304,9 @@ func (b *Local) opApply(
continue
}

// Get the value of the variable, because we'll need it for
// the next two steps.
val, valDiags := rawV.ParseVariableValue(decl.ParsingMode)
diags = diags.Append(valDiags)
if valDiags.HasErrors() {
continue
}

// If this is an apply-time variable, the user must supply a
// value during apply: it can't be null.
if applyTimeVar && val.Value.IsNull() {
if applyTimeVar && parsedVar.Value.IsNull() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Ephemeral variable must be set for apply",
Expand All @@ -336,17 +321,17 @@ func (b *Local) opApply(
// If we get here, we are in possession of a non-null
// ephemeral apply-time input variable, and need only pass
// its value on to the ApplyOpts.
applyTimeValues[varName] = val
applyTimeValues[varName] = parsedVar
} else {
// If a non-ephemeral variable is set differently between plan and apply, we should emit a diagnostic.
plannedVariableValue, ok := plan.VariableValues[varName]
if !ok {
// We'll catch this with ParseUndeclaredVariableValues after
undeclaredVariables[varName] = rawV
undeclaredVariables[varName] = op.Variables[varName]
continue
}

val, err := plannedVariableValue.Decode(cty.DynamicPseudoType)
plannedVar, err := plannedVariableValue.Decode(cty.DynamicPseudoType)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Expand All @@ -355,11 +340,11 @@ func (b *Local) opApply(
Subject: rng,
})
} else {
if v.Value.Equals(val).False() {
if parsedVar.Value.Equals(plannedVar).False() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Can't change variable when applying a saved plan",
Detail: fmt.Sprintf("The variable %s cannot be set using the -var and -var-file options when applying a saved plan file, because a saved plan includes the variable values that were set when it was created. The saved plan specifies %s as the value whereas during apply the value %s was %s. To declare an ephemeral variable which is not saved in the plan file, use ephemeral = true.", varName, viewsjson.CompactValueStr(v.Value), viewsjson.CompactValueStr(val), v.SourceType.DiagnosticLabel()),
Detail: fmt.Sprintf("The variable %s cannot be set using the -var and -var-file options when applying a saved plan file, because a saved plan includes the variable values that were set when it was created. The saved plan specifies %s as the value whereas during apply the value %s was %s. To declare an ephemeral variable which is not saved in the plan file, use ephemeral = true.", varName, viewsjson.CompactValueStr(parsedVar.Value), viewsjson.CompactValueStr(plannedVar), parsedVar.SourceType.DiagnosticLabel()),
Subject: rng,
})
}
Expand Down
17 changes: 5 additions & 12 deletions internal/command/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -964,16 +964,10 @@ func TestApply_planWithVarFileChangingVariableValue(t *testing.T) {
}
}

func TestApply_planVars(t *testing.T) {
// This test ensures that it isn't allowed to set non-ephemeral input
// variables when applying from a saved plan file, since in that case the
// variable values come from the saved plan file.
//
// This situation was originally checked by the apply command itself,
// and that's what this test was originally exercising. This rule
// is now enforced by the "local" backend instead, but this test
// is still valid since the command instance delegates to the
// local backend.
func TestApply_planUndeclaredVars(t *testing.T) {
// This test ensures that it isn't allowed to set undeclared input variables
// when applying from a saved plan file, since in that case the variable
// values come from the saved plan file.

planPath := applyFixturePlanFile(t)
statePath := testTempFile(t)
Expand Down Expand Up @@ -1076,7 +1070,6 @@ foo = "bar"

"with planfile passing ephemeral variable through environment variable": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
t.Setenv("TF_VAR_foo", "bar")
defer t.Setenv("TF_VAR_foo", "")

args := []string{
"-state", statePath,
Expand Down Expand Up @@ -1168,7 +1161,7 @@ foo = "bar"

"without planfile passing ephemeral variable through environment variable": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
t.Setenv("TF_VAR_foo", "bar")
defer t.Setenv("TF_VAR_foo", "")
t.Setenv("TF_VAR_unused", `{key:"val"}`)

args := []string{
"-state", statePath,
Expand Down
9 changes: 9 additions & 0 deletions internal/command/jsonplan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,15 @@ func (p *plan) marshalRelevantAttrs(plan *plans.Plan) error {

p.RelevantAttributes = append(p.RelevantAttributes, ResourceAttr{addr, path})
}

// we want our outputs to be deterministic, so we'll sort the attributes
// here. The order of the attributes is not important, as long as it is
// stable.

sort.SliceStable(p.RelevantAttributes, func(i, j int) bool {
return strings.Compare(fmt.Sprintf("%#v", plan.RelevantAttributes[i]), fmt.Sprintf("%#v", plan.RelevantAttributes[j])) < 0
})

return nil
}

Expand Down
5 changes: 5 additions & 0 deletions internal/command/testdata/apply-ephemeral-variable/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ variable "bar" {
ephemeral = true
}

variable "unused" {
type = map(string)
default = null
}

resource "test_instance" "foo" {
ami = "bar"
}
32 changes: 22 additions & 10 deletions internal/getmodules/getter.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,41 @@ import (
var goGetterNoDetectors = []getter.Detector{}

var goGetterDecompressors = map[string]getter.Decompressor{
"bz2": new(getter.Bzip2Decompressor),
"gz": new(getter.GzipDecompressor),
"xz": new(getter.XzDecompressor),
"zip": new(getter.ZipDecompressor),

// Bzip2
"bz2": new(getter.Bzip2Decompressor),
"tbz2": new(getter.TarBzip2Decompressor),
"tar.bz2": new(getter.TarBzip2Decompressor),
"tar.tbz2": new(getter.TarBzip2Decompressor),

// Gzip
"gz": new(getter.GzipDecompressor),
"tar.gz": new(getter.TarGzipDecompressor),
"tgz": new(getter.TarGzipDecompressor),

// Xz
"xz": new(getter.XzDecompressor),
"tar.xz": new(getter.TarXzDecompressor),
"txz": new(getter.TarXzDecompressor),

// Zip
"zip": new(getter.ZipDecompressor),
}

var goGetterGetters = map[string]getter.Getter{
"file": new(getter.FileGetter),
"gcs": new(getter.GCSGetter),
"git": new(getter.GitGetter),
"hg": new(getter.HgGetter),
"s3": new(getter.S3Getter),
// Protocol-based getters
"http": getterHTTPGetter,
"https": getterHTTPGetter,

// Cloud storage getters
"gcs": new(getter.GCSGetter),
"s3": new(getter.S3Getter),

// Version control getters
"git": new(getter.GitGetter),
"hg": new(getter.HgGetter),

// Local and file-based getters
"file": new(getter.FileGetter),
}

var getterHTTPClient = cleanhttp.DefaultClient()
Expand Down
5 changes: 3 additions & 2 deletions internal/lang/funcs/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,19 @@ func MakeTemplateFileFunc(baseDir string, funcsCb func() (funcs map[string]funct
if err != nil {
return cty.DynamicPseudoType, err
}
vars, _ := args[1].UnmarkDeep()

// This is safe even if args[1] contains unknowns because the HCL
// template renderer itself knows how to short-circuit those.
val, err := renderTmpl(expr, args[1])
val, err := renderTmpl(expr, vars)
return val.Type(), err
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
pathArg, pathMarks := args[0].Unmark()

vars, varsMarks := args[1].UnmarkDeep()

if !pathArg.IsKnown() {
if !pathArg.IsKnown() || !vars.IsKnown() {
return cty.UnknownVal(retType).WithMarks(pathMarks, varsMarks), nil
}

Expand Down
10 changes: 8 additions & 2 deletions internal/lang/funcs/filesystem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,15 @@ func TestTemplateFile(t *testing.T) {
cty.StringVal("a"),
cty.StringVal("b").Mark("var"),
cty.StringVal("c"),
}),
}).Mark("vars"),
}),
cty.StringVal("- a\n- b\n- c\n").Mark("path").Mark("var"),
cty.StringVal("- a\n- b\n- c\n").Mark("path").Mark("var").Mark("vars"),
``,
},
{
cty.StringVal("testdata/list.tmpl").Mark("path"),
cty.UnknownVal(cty.Map(cty.String)),
cty.DynamicVal.Mark("path"),
``,
},
{
Expand Down
Loading

0 comments on commit f5bdfd6

Please sign in to comment.