diff --git a/internal/cloud/backend.go b/internal/cloud/backend.go index 7f51d9a1b9..9d20de7755 100644 --- a/internal/cloud/backend.go +++ b/internal/cloud/backend.go @@ -22,7 +22,6 @@ import ( "github.com/hashicorp/terraform-svchost/disco" "github.com/mitchellh/colorstring" "github.com/zclconf/go-cty/cty" - "github.com/zclconf/go-cty/cty/gocty" "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/backend/backendrun" @@ -499,21 +498,36 @@ func resolveCloudConfig(obj cty.Value) (cloudConfig, tfdiags.Diagnostics) { tagsAsMap := make(map[string]string) if val.Type().IsObjectType() || val.Type().IsMapType() { for k, v := range val.AsValueMap() { + if v.Type() != cty.String { + diags = diags.Append(errors.New("tag object values must be strings")) + return ret, diags + } tagsAsMap[k] = v.AsString() } log.Printf("[TRACE] cloud: using tags %q from cloud config block", tagsAsMap) ret.workspaceMapping.TagsAsMap = tagsAsMap - } else if val.Type().IsSetType() { + } else if val.Type().IsTupleType() || val.Type().IsSetType() { var tagsAsSet []string - err := gocty.FromCtyValue(val, &tagsAsSet) - if err != nil { - diags = diags.Append(fmt.Errorf("an unexpected error occurred: %w", err)) - } else { - log.Printf("[TRACE] cloud: using tags %q from cloud config block", tagsAsSet) + length := val.LengthInt() + if length > 0 { + it := val.ElementIterator() + for it.Next() { + _, v := it.Element() + if !v.Type().Equals(cty.String) { + diags = diags.Append(errors.New("tag elements must be strings")) + return ret, diags + } + if vs := v.AsString(); vs != "" { + tagsAsSet = append(tagsAsSet, vs) + } + } } + + log.Printf("[TRACE] cloud: using tags %q from cloud config block", tagsAsSet) ret.workspaceMapping.TagsAsSet = tagsAsSet } else { diags = diags.Append(fmt.Errorf("tags must be a set or object, not %s", val.Type().FriendlyName())) + return ret, diags } } if val := workspaces.GetAttr("project"); !val.IsNull() { diff --git a/internal/cloud/backend_test.go b/internal/cloud/backend_test.go index e4b13ed75b..d5c644ff40 100644 --- a/internal/cloud/backend_test.go +++ b/internal/cloud/backend_test.go @@ -687,6 +687,34 @@ func TestCloud_config(t *testing.T) { })), confErr: `tags must be a set or object, not string`, }, + "invalid tags dynamic type, object with non-string": { + config: cty.ObjectVal((map[string]cty.Value{ + "hostname": cty.NullVal(cty.String), + "organization": cty.StringVal("hashicorp"), + "token": cty.NullVal(cty.String), + "workspaces": cty.ObjectVal(map[string]cty.Value{ + "name": cty.NullVal(cty.String), + "tags": cty.MapVal(map[string]cty.Value{ + "dept": cty.NumberIntVal(1), + }), + "project": cty.NullVal(cty.String), + }), + })), + confErr: `tag object values must be strings`, + }, + "invalid tags dynamic type, tuple non-string": { + config: cty.ObjectVal((map[string]cty.Value{ + "hostname": cty.NullVal(cty.String), + "organization": cty.StringVal("hashicorp"), + "token": cty.NullVal(cty.String), + "workspaces": cty.ObjectVal(map[string]cty.Value{ + "name": cty.NullVal(cty.String), + "tags": cty.TupleVal([]cty.Value{cty.NumberIntVal(1)}), + "project": cty.NullVal(cty.String), + }), + })), + confErr: `tag elements must be strings`, + }, "null config": { config: cty.NullVal(cty.EmptyObject), }, diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index df0f124588..de9c877282 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -763,7 +763,7 @@ func (m *Meta) determineInitReason(previousBackendType string, currentBackendTyp // from the backend state. This should be used only when a user runs // `terraform init -backend=false`. This function returns a local backend if // there is no backend state or no backend configured. -func (m *Meta) backendFromState(ctx context.Context) (backend.Backend, tfdiags.Diagnostics) { +func (m *Meta) backendFromState(_ context.Context) (backend.Backend, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics // Get the path to where we store a local cache of backend configuration // if we're using a remote backend. This may not yet exist which means