mirror of https://github.com/hashicorp/terraform
Run variables allow the run API to accept input variables not found in the configuration slug (the file-based ones plus workspace vars)pull/29826/head
parent
dbbfae5a1c
commit
dd856f8a1b
@ -0,0 +1,52 @@
|
||||
package cloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||
)
|
||||
|
||||
func allowedSourceType(source terraform.ValueSourceType) bool {
|
||||
return source == terraform.ValueFromNamedFile || source == terraform.ValueFromCLIArg || source == terraform.ValueFromEnvVar
|
||||
}
|
||||
|
||||
// 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,
|
||||
// that is, containing declared string variables only sourced from non-file inputs like CLI args
|
||||
// and environment variables. However, all variable parsing diagnostics are returned
|
||||
// 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
|
||||
// considered errors because they may be defined within the cloud workspace.
|
||||
func ParseCloudRunVariables(vv map[string]backend.UnparsedVariableValue, decls map[string]*configs.Variable) (map[string]string, tfdiags.Diagnostics) {
|
||||
declared, diags := backend.ParseDeclaredVariableValues(vv, decls)
|
||||
_, undedeclaredDiags := backend.ParseUndeclaredVariableValues(vv, decls)
|
||||
diags = diags.Append(undedeclaredDiags)
|
||||
|
||||
ret := make(map[string]string, len(declared))
|
||||
|
||||
// Even if there are parsing or declaration errors, populate the return map with the
|
||||
// variables that could be used for cloud runs
|
||||
for name, v := range declared {
|
||||
if !allowedSourceType(v.SourceType) {
|
||||
continue
|
||||
}
|
||||
|
||||
valueData, err := ctyjson.Marshal(v.Value, v.Value.Type())
|
||||
if err != nil {
|
||||
return nil, diags.Append(fmt.Errorf("error marshaling input variable value as json: %w", err))
|
||||
}
|
||||
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
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
package cloud
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestParseCloudRunVariables(t *testing.T) {
|
||||
t.Run("populates variables from allowed sources", func(t *testing.T) {
|
||||
vv := map[string]backend.UnparsedVariableValue{
|
||||
"undeclared": testUnparsedVariableValue{source: terraform.ValueFromCLIArg, value: "0"},
|
||||
"declaredFromConfig": testUnparsedVariableValue{source: terraform.ValueFromConfig, value: "1"},
|
||||
"declaredFromNamedFile": testUnparsedVariableValue{source: terraform.ValueFromNamedFile, value: "2"},
|
||||
"declaredFromCLIArg": testUnparsedVariableValue{source: terraform.ValueFromCLIArg, value: "3"},
|
||||
"declaredFromEnvVar": testUnparsedVariableValue{source: terraform.ValueFromEnvVar, value: "4"},
|
||||
}
|
||||
|
||||
decls := map[string]*configs.Variable{
|
||||
"declaredFromConfig": {
|
||||
Name: "declaredFromConfig",
|
||||
Type: cty.String,
|
||||
ConstraintType: cty.String,
|
||||
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},
|
||||
},
|
||||
},
|
||||
"declaredFromNamedFile": {
|
||||
Name: "declaredFromNamedFile",
|
||||
Type: cty.String,
|
||||
ConstraintType: cty.String,
|
||||
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},
|
||||
},
|
||||
},
|
||||
"declaredFromCLIArg": {
|
||||
Name: "declaredFromCLIArg",
|
||||
Type: cty.String,
|
||||
ConstraintType: cty.String,
|
||||
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},
|
||||
},
|
||||
},
|
||||
"declaredFromEnvVar": {
|
||||
Name: "declaredFromEnvVar",
|
||||
Type: cty.String,
|
||||
ConstraintType: cty.String,
|
||||
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},
|
||||
},
|
||||
},
|
||||
"missing": {
|
||||
Name: "missing",
|
||||
Type: cty.String,
|
||||
ConstraintType: cty.String,
|
||||
Default: cty.StringVal("2"),
|
||||
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},
|
||||
},
|
||||
},
|
||||
}
|
||||
wantVals := make(map[string]string)
|
||||
|
||||
wantVals["declaredFromNamedFile"] = "2"
|
||||
wantVals["declaredFromCLIArg"] = "3"
|
||||
wantVals["declaredFromEnvVar"] = "4"
|
||||
|
||||
gotVals, diags := ParseCloudRunVariables(vv, decls)
|
||||
if diff := cmp.Diff(wantVals, gotVals, cmp.Comparer(cty.Value.RawEquals)); diff != "" {
|
||||
t.Errorf("wrong result\n%s", diff)
|
||||
}
|
||||
|
||||
if got, want := len(diags), 1; got != want {
|
||||
t.Fatalf("expected 1 variable error: %v, got %v", diags.Err(), want)
|
||||
}
|
||||
|
||||
if got, want := diags[0].Description().Summary, "Value for undeclared variable"; got != want {
|
||||
t.Errorf("wrong summary for diagnostic 0\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type testUnparsedVariableValue struct {
|
||||
source terraform.ValueSourceType
|
||||
value string
|
||||
}
|
||||
|
||||
func (v testUnparsedVariableValue) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
|
||||
return &terraform.InputValue{
|
||||
Value: cty.StringVal(v.value),
|
||||
SourceType: v.source,
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Filename: "fake.tfvars",
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
Loading…
Reference in new issue