Merge pull request #29913 from hashicorp/brandonc/run_variables_types
fix(run variables): support all variable types (map, list, bool, number, null, string)
This commit is contained in:
commit
e07a7c472b
|
@ -477,7 +477,7 @@ func TestCloud_planWithRequiredVariables(t *testing.T) {
|
||||||
defer configCleanup()
|
defer configCleanup()
|
||||||
defer done(t)
|
defer done(t)
|
||||||
|
|
||||||
op.Variables = testVariables(terraform.ValueFromCLIArg, "foo") // "bar" variable value missing
|
op.Variables = testVariables(terraform.ValueFromCLIArg, "foo") // "bar" variable defined in config is missing
|
||||||
op.Workspace = testBackendSingleWorkspaceName
|
op.Workspace = testBackendSingleWorkspaceName
|
||||||
|
|
||||||
run, err := b.Operation(context.Background(), op)
|
run, err := b.Operation(context.Background(), op)
|
||||||
|
@ -487,7 +487,7 @@ func TestCloud_planWithRequiredVariables(t *testing.T) {
|
||||||
|
|
||||||
<-run.Done()
|
<-run.Done()
|
||||||
// The usual error of a required variable being missing is deferred and the operation
|
// The usual error of a required variable being missing is deferred and the operation
|
||||||
// is successful
|
// is successful.
|
||||||
if run.Result != backend.OperationSuccess {
|
if run.Result != backend.OperationSuccess {
|
||||||
t.Fatal("expected plan operation to succeed")
|
t.Fatal("expected plan operation to succeed")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
package cloud
|
package cloud
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"github.com/hashicorp/hcl/v2/hclwrite"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/backend"
|
"github.com/hashicorp/terraform/internal/backend"
|
||||||
"github.com/hashicorp/terraform/internal/configs"
|
"github.com/hashicorp/terraform/internal/configs"
|
||||||
"github.com/hashicorp/terraform/internal/terraform"
|
"github.com/hashicorp/terraform/internal/terraform"
|
||||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func allowedSourceType(source terraform.ValueSourceType) bool {
|
func allowedSourceType(source terraform.ValueSourceType) bool {
|
||||||
|
@ -17,7 +14,7 @@ func allowedSourceType(source terraform.ValueSourceType) bool {
|
||||||
|
|
||||||
// ParseCloudRunVariables accepts a mapping of unparsed values and a mapping of variable
|
// ParseCloudRunVariables accepts a mapping of unparsed values and a mapping of variable
|
||||||
// declarations and returns a name/value variable map appropriate for an API run context,
|
// declarations and returns a name/value variable map appropriate for an API run context,
|
||||||
// that is, containing declared string variables only sourced from non-file inputs like CLI args
|
// that is, containing variables only sourced from non-file inputs like CLI args
|
||||||
// and environment variables. However, all variable parsing diagnostics are returned
|
// and environment variables. However, all variable parsing diagnostics are returned
|
||||||
// in order to allow callers to short circuit cloud runs that contain variable
|
// in order to allow callers to short circuit cloud runs that contain variable
|
||||||
// declaration or parsing errors. The only exception is that missing required values are not
|
// declaration or parsing errors. The only exception is that missing required values are not
|
||||||
|
@ -36,16 +33,9 @@ func ParseCloudRunVariables(vv map[string]backend.UnparsedVariableValue, decls m
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
valueData, err := ctyjson.Marshal(v.Value, v.Value.Type())
|
// RunVariables are always expressed as HCL strings
|
||||||
if err != nil {
|
tokens := hclwrite.TokensForValue(v.Value)
|
||||||
return nil, diags.Append(fmt.Errorf("error marshaling input variable value as json: %w", err))
|
ret[name] = string(tokens.Bytes())
|
||||||
}
|
|
||||||
var variableValue string
|
|
||||||
if err = json.Unmarshal(valueData, &variableValue); err != nil {
|
|
||||||
// This should never happen since cty marshaled the value to begin with without error
|
|
||||||
return nil, diags.Append(fmt.Errorf("error unmarshaling run variable: %w", err))
|
|
||||||
}
|
|
||||||
ret[name] = variableValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, diags
|
return ret, diags
|
||||||
|
|
|
@ -15,11 +15,16 @@ import (
|
||||||
func TestParseCloudRunVariables(t *testing.T) {
|
func TestParseCloudRunVariables(t *testing.T) {
|
||||||
t.Run("populates variables from allowed sources", func(t *testing.T) {
|
t.Run("populates variables from allowed sources", func(t *testing.T) {
|
||||||
vv := map[string]backend.UnparsedVariableValue{
|
vv := map[string]backend.UnparsedVariableValue{
|
||||||
"undeclared": testUnparsedVariableValue{source: terraform.ValueFromCLIArg, value: "0"},
|
"undeclared": testUnparsedVariableValue{source: terraform.ValueFromCLIArg, value: cty.StringVal("0")},
|
||||||
"declaredFromConfig": testUnparsedVariableValue{source: terraform.ValueFromConfig, value: "1"},
|
"declaredFromConfig": testUnparsedVariableValue{source: terraform.ValueFromConfig, value: cty.StringVal("1")},
|
||||||
"declaredFromNamedFile": testUnparsedVariableValue{source: terraform.ValueFromNamedFile, value: "2"},
|
"declaredFromNamedFileMapString": testUnparsedVariableValue{source: terraform.ValueFromNamedFile, value: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("bar")})},
|
||||||
"declaredFromCLIArg": testUnparsedVariableValue{source: terraform.ValueFromCLIArg, value: "3"},
|
"declaredFromNamedFileBool": testUnparsedVariableValue{source: terraform.ValueFromNamedFile, value: cty.BoolVal(true)},
|
||||||
"declaredFromEnvVar": testUnparsedVariableValue{source: terraform.ValueFromEnvVar, value: "4"},
|
"declaredFromNamedFileNumber": testUnparsedVariableValue{source: terraform.ValueFromNamedFile, value: cty.NumberIntVal(2)},
|
||||||
|
"declaredFromNamedFileListString": testUnparsedVariableValue{source: terraform.ValueFromNamedFile, value: cty.ListVal([]cty.Value{cty.StringVal("2a"), cty.StringVal("2b")})},
|
||||||
|
"declaredFromNamedFileNull": testUnparsedVariableValue{source: terraform.ValueFromNamedFile, value: cty.NullVal(cty.String)},
|
||||||
|
"declaredFromNamedMapComplex": testUnparsedVariableValue{source: terraform.ValueFromNamedFile, value: cty.MapVal(map[string]cty.Value{"foo": cty.ObjectVal(map[string]cty.Value{"qux": cty.ListVal([]cty.Value{cty.BoolVal(true), cty.BoolVal(false)})})})},
|
||||||
|
"declaredFromCLIArg": testUnparsedVariableValue{source: terraform.ValueFromCLIArg, value: cty.StringVal("3")},
|
||||||
|
"declaredFromEnvVar": testUnparsedVariableValue{source: terraform.ValueFromEnvVar, value: cty.StringVal("4")},
|
||||||
}
|
}
|
||||||
|
|
||||||
decls := map[string]*configs.Variable{
|
decls := map[string]*configs.Variable{
|
||||||
|
@ -34,11 +39,66 @@ func TestParseCloudRunVariables(t *testing.T) {
|
||||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
End: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"declaredFromNamedFile": {
|
"declaredFromNamedFileMapString": {
|
||||||
Name: "declaredFromNamedFile",
|
Name: "declaredFromNamedFileMapString",
|
||||||
|
Type: cty.Map(cty.String),
|
||||||
|
ConstraintType: cty.Map(cty.String),
|
||||||
|
ParsingMode: configs.VariableParseHCL,
|
||||||
|
DeclRange: hcl.Range{
|
||||||
|
Filename: "fake.tf",
|
||||||
|
Start: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
End: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"declaredFromNamedFileBool": {
|
||||||
|
Name: "declaredFromNamedFileBool",
|
||||||
|
Type: cty.Bool,
|
||||||
|
ConstraintType: cty.Bool,
|
||||||
|
ParsingMode: configs.VariableParseLiteral,
|
||||||
|
DeclRange: hcl.Range{
|
||||||
|
Filename: "fake.tf",
|
||||||
|
Start: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
End: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"declaredFromNamedFileNumber": {
|
||||||
|
Name: "declaredFromNamedFileNumber",
|
||||||
|
Type: cty.Number,
|
||||||
|
ConstraintType: cty.Number,
|
||||||
|
ParsingMode: configs.VariableParseLiteral,
|
||||||
|
DeclRange: hcl.Range{
|
||||||
|
Filename: "fake.tf",
|
||||||
|
Start: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
End: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"declaredFromNamedFileListString": {
|
||||||
|
Name: "declaredFromNamedFileListString",
|
||||||
|
Type: cty.List(cty.String),
|
||||||
|
ConstraintType: cty.List(cty.String),
|
||||||
|
ParsingMode: configs.VariableParseHCL,
|
||||||
|
DeclRange: hcl.Range{
|
||||||
|
Filename: "fake.tf",
|
||||||
|
Start: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
End: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"declaredFromNamedFileNull": {
|
||||||
|
Name: "declaredFromNamedFileNull",
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
ConstraintType: cty.String,
|
ConstraintType: cty.String,
|
||||||
ParsingMode: configs.VariableParseLiteral,
|
ParsingMode: configs.VariableParseHCL,
|
||||||
|
DeclRange: hcl.Range{
|
||||||
|
Filename: "fake.tf",
|
||||||
|
Start: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
End: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"declaredFromNamedMapComplex": {
|
||||||
|
Name: "declaredFromNamedMapComplex",
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
ConstraintType: cty.DynamicPseudoType,
|
||||||
|
ParsingMode: configs.VariableParseHCL,
|
||||||
DeclRange: hcl.Range{
|
DeclRange: hcl.Range{
|
||||||
Filename: "fake.tf",
|
Filename: "fake.tf",
|
||||||
Start: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
Start: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
@ -81,10 +141,14 @@ func TestParseCloudRunVariables(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
wantVals := make(map[string]string)
|
wantVals := make(map[string]string)
|
||||||
|
wantVals["declaredFromNamedFileBool"] = "true"
|
||||||
wantVals["declaredFromNamedFile"] = "2"
|
wantVals["declaredFromNamedFileNumber"] = "2"
|
||||||
wantVals["declaredFromCLIArg"] = "3"
|
wantVals["declaredFromNamedFileListString"] = `["2a", "2b"]`
|
||||||
wantVals["declaredFromEnvVar"] = "4"
|
wantVals["declaredFromNamedFileNull"] = "null"
|
||||||
|
wantVals["declaredFromNamedFileMapString"] = "{\n foo = \"bar\"\n}"
|
||||||
|
wantVals["declaredFromNamedMapComplex"] = "{\n foo = {\n qux = [true, false]\n }\n}"
|
||||||
|
wantVals["declaredFromCLIArg"] = `"3"`
|
||||||
|
wantVals["declaredFromEnvVar"] = `"4"`
|
||||||
|
|
||||||
gotVals, diags := ParseCloudRunVariables(vv, decls)
|
gotVals, diags := ParseCloudRunVariables(vv, decls)
|
||||||
if diff := cmp.Diff(wantVals, gotVals, cmp.Comparer(cty.Value.RawEquals)); diff != "" {
|
if diff := cmp.Diff(wantVals, gotVals, cmp.Comparer(cty.Value.RawEquals)); diff != "" {
|
||||||
|
@ -103,12 +167,12 @@ func TestParseCloudRunVariables(t *testing.T) {
|
||||||
|
|
||||||
type testUnparsedVariableValue struct {
|
type testUnparsedVariableValue struct {
|
||||||
source terraform.ValueSourceType
|
source terraform.ValueSourceType
|
||||||
value string
|
value cty.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v testUnparsedVariableValue) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
|
func (v testUnparsedVariableValue) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
|
||||||
return &terraform.InputValue{
|
return &terraform.InputValue{
|
||||||
Value: cty.StringVal(v.value),
|
Value: v.value,
|
||||||
SourceType: v.source,
|
SourceType: v.source,
|
||||||
SourceRange: tfdiags.SourceRange{
|
SourceRange: tfdiags.SourceRange{
|
||||||
Filename: "fake.tfvars",
|
Filename: "fake.tfvars",
|
||||||
|
|
Loading…
Reference in New Issue