Repository: ariga/atlas
Pull request #1172 (2022-10-03 11:16:17)

schemahcl: accept list(T) as inputs
Also, use the native cty.Value type in our codebase


Patch:
diff --git a/cmd/atlas/internal/cmdapi/cmdapi.go b/cmd/atlas/internal/cmdapi/cmdapi.go
index 6e82df0b..3ab83f73 100644
--- a/cmd/atlas/internal/cmdapi/cmdapi.go
+++ b/cmd/atlas/internal/cmdapi/cmdapi.go
@@ -7,10 +7,12 @@
 package cmdapi
 
 import (
+	"encoding/csv"
 	"fmt"
 	"strings"
 
 	"github.com/spf13/cobra"
+	"github.com/zclconf/go-cty/cty"
 	"golang.org/x/mod/semver"
 )
 
@@ -29,7 +31,7 @@ var (
 		SelectedEnv string
 		// Vars contains the input variables passed from the CLI to
 		// Atlas DDL or project files.
-		Vars map[string]string
+		Vars Vars
 	}
 
 	// version holds Atlas version. When built with cloud packages
@@ -42,7 +44,7 @@ var (
 		Short: "Prints this Atlas CLI version information.",
 		Run: func(cmd *cobra.Command, args []string) {
 			v, u := parse(version)
-			Root.Printf("atlas version %s\n%s\n", v, u)
+			cmd.Printf("atlas version %s\n%s\n", v, u)
 		},
 	}
 
@@ -68,7 +70,7 @@ func init() {
 // receivesEnv configures cmd to receive the common '--env' flag.
 func receivesEnv(cmd *cobra.Command) {
 	cmd.PersistentFlags().StringVarP(&GlobalFlags.SelectedEnv, "env", "", "", "set which env from the project file to use")
-	cmd.PersistentFlags().StringToStringVarP(&GlobalFlags.Vars, varFlag, "", nil, "input variables")
+	cmd.PersistentFlags().VarP(&GlobalFlags.Vars, varFlag, "", "input variables")
 }
 
 // inputValsFromEnv populates GlobalFlags.Vars from the active environment. If we are working
@@ -118,3 +120,50 @@ func parse(version string) (string, string) {
 func Version() string {
 	return version
 }
+
+// Vars implements pflag.Value.
+type Vars map[string]cty.Value
+
+// String implements pflag.Value.String.
+func (v Vars) String() string {
+	var b strings.Builder
+	for k := range v {
+		if b.Len() > 0 {
+			b.WriteString(", ")
+		}
+		b.WriteString(k)
+		b.WriteString(":")
+		b.WriteString(v[k].GoString())
+	}
+	return "[" + b.String() + "]"
+}
+
+// Set implements pflag.Value.Set.
+func (v *Vars) Set(s string) error {
+	if *v == nil {
+		*v = make(Vars)
+	}
+	kvs, err := csv.NewReader(strings.NewReader(s)).Read()
+	if err != nil {
+		return err
+	}
+	for i := range kvs {
+		kv := strings.SplitN(kvs[i], "=", 2)
+		if len(kv) != 2 {
+			return fmt.Errorf("variables must be format as key=value, got: %q", kvs[i])
+		}
+		v1 := cty.StringVal(kv[1])
+		switch v2, ok := (*v)[kv[0]]; {
+		case ok:
+			(*v)[kv[0]] = cty.ListVal([]cty.Value{v1, v2})
+		default:
+			(*v)[kv[0]] = v1
+		}
+	}
+	return nil
+}
+
+// Type implements pflag.Value.Type.
+func (v *Vars) Type() string {
+	return "<name>=<value>"
+}
diff --git a/cmd/atlas/internal/cmdapi/project.go b/cmd/atlas/internal/cmdapi/project.go
index e949b777..4781df36 100644
--- a/cmd/atlas/internal/cmdapi/project.go
+++ b/cmd/atlas/internal/cmdapi/project.go
@@ -10,19 +10,21 @@ import (
 	"os"
 
 	"ariga.io/atlas/schemahcl"
+
+	"github.com/zclconf/go-cty/cty"
 )
 
 const projectFileName = "atlas.hcl"
 
 type loadConfig struct {
-	inputVals map[string]string
+	inputVals map[string]cty.Value
 }
 
 // LoadOption configures the LoadEnv function.
 type LoadOption func(*loadConfig)
 
 // WithInput is a LoadOption that sets the input values for the LoadEnv function.
-func WithInput(vals map[string]string) LoadOption {
+func WithInput(vals map[string]cty.Value) LoadOption {
 	return func(config *loadConfig) {
 		config.inputVals = vals
 	}
diff --git a/cmd/atlas/internal/cmdapi/schema.go b/cmd/atlas/internal/cmdapi/schema.go
index 6f27cbfe..64c2d6c0 100644
--- a/cmd/atlas/internal/cmdapi/schema.go
+++ b/cmd/atlas/internal/cmdapi/schema.go
@@ -16,9 +16,11 @@ import (
 	"ariga.io/atlas/sql/migrate"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlclient"
+
 	"github.com/hashicorp/hcl/v2/hclwrite"
 	"github.com/manifoldco/promptui"
 	"github.com/spf13/cobra"
+	"github.com/zclconf/go-cty/cty"
 )
 
 const (
@@ -378,7 +380,7 @@ func CmdFmtRun(cmd *cobra.Command, args []string) {
 	}
 }
 
-func applyRun(cmd *cobra.Command, client *sqlclient.Client, devURL string, paths []string, dryRun, autoApprove bool, input map[string]string) error {
+func applyRun(cmd *cobra.Command, client *sqlclient.Client, devURL string, paths []string, dryRun, autoApprove bool, input map[string]cty.Value) error {
 	schemas, ctx := SchemaFlags.Schemas, cmd.Context()
 	if client.URL.Schema != "" {
 		schemas = append(schemas, client.URL.Schema)
diff --git a/doc/md/reference.md b/doc/md/reference.md
index ef075fbb..3ee88eb0 100644
--- a/doc/md/reference.md
+++ b/doc/md/reference.md
@@ -50,7 +50,7 @@ atlas migrate
 ```
       --dir string           select migration directory using URL format (default "file://migrations")
       --env string           set which env from the project file to use
-      --var stringToString   input variables (default [])
+      --var <name>=<value>   input variables (default [])
 
 ```
 
@@ -312,7 +312,7 @@ The `atlas schema` command groups subcommands for working with Atlas schemas.
 #### Flags
 ```
       --env string           set which env from the project file to use
-      --var stringToString   input variables (default [])
+      --var <name>=<value>   input variables (default [])
 
 ```
 
diff --git a/schemahcl/context.go b/schemahcl/context.go
index 08efedee..466a0d23 100644
--- a/schemahcl/context.go
+++ b/schemahcl/context.go
@@ -13,6 +13,7 @@ import (
 	"github.com/hashicorp/hcl/v2/gohcl"
 	"github.com/hashicorp/hcl/v2/hclsyntax"
 	"github.com/zclconf/go-cty/cty"
+	"github.com/zclconf/go-cty/cty/convert"
 	"github.com/zclconf/go-cty/cty/function"
 )
 
@@ -30,7 +31,7 @@ type varDef struct {
 //	  type = string // also supported: int, bool
 //	  default = "rotemtam"
 //	}
-func (s *State) setInputVals(ctx *hcl.EvalContext, body hcl.Body, input map[string]string) error {
+func (s *State) setInputVals(ctx *hcl.EvalContext, body hcl.Body, input map[string]cty.Value) error {
 	var (
 		doc struct {
 			Vars   []*varDef `hcl:"variable,block"`
@@ -61,19 +62,26 @@ func (s *State) setInputVals(ctx *hcl.EvalContext, body hcl.Body, input map[stri
 	}
 	ctxVars := make(map[string]cty.Value)
 	for _, v := range doc.Vars {
-		inputVal, ok := input[v.Name]
-		if ok {
-			ctyVal, err := readVar(v, inputVal)
-			if err != nil {
-				return fmt.Errorf("failed reading var: %w", err)
-			}
-			ctxVars[v.Name] = ctyVal
-			continue
-		}
-		if v.Default == cty.NilVal {
+		var vv cty.Value
+		switch iv, ok := input[v.Name]; {
+		case ok:
+			vv = iv
+		case v.Default != cty.NilVal:
+			vv = v.Default
+		default:
 			return fmt.Errorf("missing value for required variable %q", v.Name)
 		}
-		ctxVars[v.Name] = v.Default
+		vt := v.Type.EncapsulatedValue().(*cty.Type)
+		// In case the input value is a primitive type and the expected type is a list,
+		// wrap it as a list because the variable type may not be known to the caller.
+		if vt.IsListType() && vv.Type().Equals(vt.ElementType()) {
+			vv = cty.ListVal([]cty.Value{vv})
+		}
+		cv, err := convert.Convert(vv, *vt)
+		if err != nil {
+			return fmt.Errorf("variable %q: %w", v.Name, err)
+		}
+		ctxVars[v.Name] = cv
 	}
 	mergeCtxVar(ctx, ctxVars)
 	return nil
@@ -91,33 +99,6 @@ func mergeCtxVar(ctx *hcl.EvalContext, vals map[string]cty.Value) {
 	ctx.Variables[key] = cty.ObjectVal(vals)
 }
 
-// readVar reads the raw inputVal as a cty.Value using the type definition on v.
-func readVar(v *varDef, inputVal string) (cty.Value, error) {
-	et := v.Type.EncapsulatedValue()
-	typ, ok := et.(*cty.Type)
-	if !ok || typ == nil {
-		return cty.NilVal, fmt.Errorf("expected non-nillable schemahcl.Type got %T", et)
-	}
-	switch *typ {
-	case cty.String:
-		return cty.StringVal(inputVal), nil
-	case cty.Number:
-		i, err := strconv.Atoi(inputVal)
-		if err != nil {
-			return cty.NilVal, err
-		}
-		return cty.NumberIntVal(int64(i)), nil
-	case cty.Bool:
-		b, err := strconv.ParseBool(inputVal)
-		if err != nil {
-			return cty.NilVal, err
-		}
-		return cty.BoolVal(b), nil
-	default:
-		return cty.NilVal, fmt.Errorf("unknown type: %q", v.Name)
-	}
-}
-
 func setBlockVars(ctx *hcl.EvalContext, b *hclsyntax.Body) (*hcl.EvalContext, error) {
 	defs := defRegistry(b)
 	vars, err := blockVars(b.Blocks, "", defs)
diff --git a/schemahcl/hcl.go b/schemahcl/hcl.go
index 4a8c6b00..05d70892 100644
--- a/schemahcl/hcl.go
+++ b/schemahcl/hcl.go
@@ -32,10 +32,10 @@ type (
 	// Evaluator is the interface that wraps the Eval function.
 	Evaluator interface {
 		// Eval evaluates parsed HCL files using input variables into a schema.Realm.
-		Eval(*hclparse.Parser, any, map[string]string) error
+		Eval(*hclparse.Parser, any, map[string]cty.Value) error
 	}
 	// EvalFunc is an adapter that allows the use of an ordinary function as an Evaluator.
-	EvalFunc func(*hclparse.Parser, any, map[string]string) error
+	EvalFunc func(*hclparse.Parser, any, map[string]cty.Value) error
 	// Marshaler is the interface that wraps the MarshalSpec function.
 	Marshaler interface {
 		// MarshalSpec marshals the provided input into a valid Atlas HCL document.
@@ -57,7 +57,7 @@ func (s *State) MarshalSpec(v any) ([]byte, error) {
 
 // EvalFiles evaluates the files in the provided paths using the input variables and
 // populates v with the result.
-func (s *State) EvalFiles(paths []string, v any, input map[string]string) error {
+func (s *State) EvalFiles(paths []string, v any, input map[string]cty.Value) error {
 	parser := hclparse.NewParser()
 	for _, path := range paths {
 		if _, diag := parser.ParseHCLFile(path); diag.HasErrors() {
@@ -69,7 +69,7 @@ func (s *State) EvalFiles(paths []string, v any, input map[string]string) error
 
 // Eval evaluates the parsed HCL documents using the input variables and populates v
 // using the result.
-func (s *State) Eval(parsed *hclparse.Parser, v any, input map[string]string) error {
+func (s *State) Eval(parsed *hclparse.Parser, v any, input map[string]cty.Value) error {
 	ctx := s.config.newCtx()
 	reg := &blockDef{
 		fields:   make(map[string]struct{}),
@@ -128,7 +128,7 @@ func (s *State) Eval(parsed *hclparse.Parser, v any, input map[string]string) er
 
 // EvalBytes evaluates the data byte-slice as an Atlas HCL document using the input variables
 // and stores the result in v.
-func (s *State) EvalBytes(data []byte, v any, input map[string]string) error {
+func (s *State) EvalBytes(data []byte, v any, input map[string]cty.Value) error {
 	parser := hclparse.NewParser()
 	if _, diag := parser.ParseHCL(data, ""); diag.HasErrors() {
 		return diag
@@ -625,7 +625,7 @@ func hclList(items []hclwrite.Tokens) hclwrite.Tokens {
 }
 
 // Eval implements the Evaluator interface.
-func (f EvalFunc) Eval(p *hclparse.Parser, i any, input map[string]string) error {
+func (f EvalFunc) Eval(p *hclparse.Parser, i any, input map[string]cty.Value) error {
 	return f(p, i, input)
 }
 
diff --git a/sql/internal/specutil/spec.go b/sql/internal/specutil/spec.go
index 1914d5d0..d9df06a8 100644
--- a/sql/internal/specutil/spec.go
+++ b/sql/internal/specutil/spec.go
@@ -11,7 +11,9 @@ import (
 	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
+
 	"github.com/hashicorp/hcl/v2/hclparse"
+	"github.com/zclconf/go-cty/cty"
 )
 
 // StrAttr is a helper method for constructing *schemahcl.Attr of type string.
@@ -192,8 +194,8 @@ func QualifyReferences(tableSpecs []*sqlspec.Table, realm *schema.Realm) error {
 
 // HCLBytesFunc returns a helper that evaluates an HCL document from a byte slice instead
 // of from an hclparse.Parser instance.
-func HCLBytesFunc(ev schemahcl.Evaluator) func(b []byte, v any, inp map[string]string) error {
-	return func(b []byte, v any, inp map[string]string) error {
+func HCLBytesFunc(ev schemahcl.Evaluator) func(b []byte, v any, inp map[string]cty.Value) error {
+	return func(b []byte, v any, inp map[string]cty.Value) error {
 		parser := hclparse.NewParser()
 		if _, diag := parser.ParseHCL(b, ""); diag.HasErrors() {
 			return diag
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index 780db6d0..a566c0c4 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -15,7 +15,9 @@ import (
 	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
+
 	"github.com/hashicorp/hcl/v2/hclparse"
+	"github.com/zclconf/go-cty/cty"
 )
 
 type doc struct {
@@ -24,7 +26,7 @@ type doc struct {
 }
 
 // evalSpec evaluates an Atlas DDL document into v using the input.
-func evalSpec(p *hclparse.Parser, v any, input map[string]string) error {
+func evalSpec(p *hclparse.Parser, v any, input map[string]cty.Value) error {
 	var d doc
 	if err := hclState.Eval(p, &d, input); err != nil {
 		return err
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index 1dff4086..0b6d68f1 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -15,7 +15,9 @@ import (
 	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
+
 	"github.com/hashicorp/hcl/v2/hclparse"
+	"github.com/zclconf/go-cty/cty"
 )
 
 type (
@@ -38,7 +40,7 @@ func init() {
 }
 
 // evalSpec evaluates an Atlas DDL document into v using the input.
-func evalSpec(p *hclparse.Parser, v any, input map[string]string) error {
+func evalSpec(p *hclparse.Parser, v any, input map[string]cty.Value) error {
 	var d doc
 	if err := hclState.Eval(p, &d, input); err != nil {
 		return err
diff --git a/sql/sqlite/sqlspec.go b/sql/sqlite/sqlspec.go
index d67484b7..c95b6fbf 100644
--- a/sql/sqlite/sqlspec.go
+++ b/sql/sqlite/sqlspec.go
@@ -15,11 +15,13 @@ import (
 	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
+
 	"github.com/hashicorp/hcl/v2/hclparse"
+	"github.com/zclconf/go-cty/cty"
 )
 
 // evalSpec evaluates an Atlas DDL document using an unmarshaler into v by using the input.
-func evalSpec(p *hclparse.Parser, v any, input map[string]string) error {
+func evalSpec(p *hclparse.Parser, v any, input map[string]cty.Value) error {
 	var d doc
 	if err := hclState.Eval(p, &d, input); err != nil {
 		return err


Test patch:
diff --git a/cmd/atlas/internal/cmdapi/project_test.go b/cmd/atlas/internal/cmdapi/project_test.go
index 45659e66..5830a106 100644
--- a/cmd/atlas/internal/cmdapi/project_test.go
+++ b/cmd/atlas/internal/cmdapi/project_test.go
@@ -11,7 +11,9 @@ import (
 	"testing"
 
 	"ariga.io/atlas/schemahcl"
+
 	"github.com/stretchr/testify/require"
+	"github.com/zclconf/go-cty/cty"
 )
 
 func TestLoadEnv(t *testing.T) {
@@ -126,8 +128,8 @@ env "multi" {
 		require.EqualValues(t, []string{"./a.hcl", "./b.hcl"}, srcs)
 	})
 	t.Run("with input", func(t *testing.T) {
-		env, err := LoadEnv(path, "local", WithInput(map[string]string{
-			"name": "goodbye",
+		env, err := LoadEnv(path, "local", WithInput(map[string]cty.Value{
+			"name": cty.StringVal("goodbye"),
 		}))
 		require.NoError(t, err)
 		str, ok := env.Attr("str")
diff --git a/internal/integration/hclsqlspec/hclsqlspec_test.go b/internal/integration/hclsqlspec/hclsqlspec_test.go
index 91998fea..e00af199 100644
--- a/internal/integration/hclsqlspec/hclsqlspec_test.go
+++ b/internal/integration/hclsqlspec/hclsqlspec_test.go
@@ -7,6 +7,8 @@ package hclsqlspec
 import (
 	"testing"
 
+	"github.com/zclconf/go-cty/cty"
+
 	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/mysql"
 	"ariga.io/atlas/sql/postgres"
@@ -20,7 +22,7 @@ import (
 var dialects = []struct {
 	name string
 	schemahcl.Marshaler
-	Eval func(b []byte, v any, inp map[string]string) error
+	Eval func(b []byte, v any, inp map[string]cty.Value) error
 }{
 	{
 		name:      "mysql",
diff --git a/schemahcl/context_test.go b/schemahcl/context_test.go
index 01737c28..ff051f72 100644
--- a/schemahcl/context_test.go
+++ b/schemahcl/context_test.go
@@ -9,6 +9,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"github.com/zclconf/go-cty/cty"
 )
 
 func TestReferences(t *testing.T) {
@@ -489,26 +490,42 @@ variable "bool" {
   type = bool
 }
 
+variable "convert_int" {
+  type = int
+}
+
+variable "convert_bool" {
+  type = bool
+}
+
 name = var.name
 default = var.default
 int = var.int
 bool = var.bool
+convert_int = var.convert_int
+convert_bool = var.convert_bool
 `
 	state := New()
 	var test struct {
-		Name    string `spec:"name"`
-		Default string `spec:"default"`
-		Int     int    `spec:"int"`
-		Bool    bool   `spec:"bool"`
+		Name        string `spec:"name"`
+		Default     string `spec:"default"`
+		Int         int    `spec:"int"`
+		Bool        bool   `spec:"bool"`
+		ConvertInt  int    `spec:"convert_int"`
+		ConvertBool bool   `spec:"convert_bool"`
 	}
-	err := state.EvalBytes([]byte(h), &test, map[string]string{
-		"name": "rotemtam",
-		"int":  "42",
-		"bool": "true",
+	err := state.EvalBytes([]byte(h), &test, map[string]cty.Value{
+		"name":         cty.StringVal("rotemtam"),
+		"int":          cty.NumberIntVal(42),
+		"bool":         cty.BoolVal(true),
+		"convert_int":  cty.StringVal("1"),
+		"convert_bool": cty.StringVal("true"),
 	})
 	require.NoError(t, err)
 	require.EqualValues(t, "rotemtam", test.Name)
 	require.EqualValues(t, "hello", test.Default)
 	require.EqualValues(t, 42, test.Int)
 	require.EqualValues(t, true, test.Bool)
+	require.EqualValues(t, 1, test.ConvertInt)
+	require.EqualValues(t, true, test.ConvertBool)
 }
diff --git a/schemahcl/hcl_test.go b/schemahcl/hcl_test.go
index 35c4eccd..62b1eb82 100644
--- a/schemahcl/hcl_test.go
+++ b/schemahcl/hcl_test.go
@@ -12,6 +12,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"github.com/zclconf/go-cty/cty"
 )
 
 func TestAttributes(t *testing.T) {
@@ -305,7 +306,9 @@ person "rotem" {
 	family = family.default
 }
 `
-	err := New().EvalBytes([]byte(h), &test, map[string]string{"family_name": "tam"})
+	err := New().EvalBytes([]byte(h), &test, map[string]cty.Value{
+		"family_name": cty.StringVal("tam"),
+	})
 	require.NoError(t, err)
 	require.EqualValues(t, "$family.tam", test.People[0].Family.V)
 }
@@ -331,8 +334,8 @@ func TestMultiFile(t *testing.T) {
 		}
 		paths = append(paths, filepath.Join(testDir, file.Name()))
 	}
-	err = New().EvalFiles(paths, &test, map[string]string{
-		"hobby": "coding",
+	err = New().EvalFiles(paths, &test, map[string]cty.Value{
+		"hobby": cty.StringVal("coding"),
 	})
 	require.NoError(t, err)
 	require.Len(t, test.People, 2)
@@ -364,6 +367,10 @@ variable "tenants" {
   default = ["atlas", "ent"]
 }
 
+variable "domains" {
+  type = list(string)
+}
+
 env "prod" {
   dynamic "url" {
     for_each = var.tenants
@@ -389,7 +396,12 @@ env "staging" {
 }
 `)
 	)
-	require.NoError(t, New().EvalBytes(b, &doc, nil))
+	require.NoError(t, New().EvalBytes(b, &doc, map[string]cty.Value{
+		"domains": cty.ListVal([]cty.Value{
+			cty.StringVal("a"),
+			cty.StringVal("b"),
+		}),
+	}))
 	require.Len(t, doc.Envs, 2)
 	require.Equal(t, "prod", doc.Envs[0].Name)
 	require.Equal(t, "staging", doc.Envs[1].Name)
@@ -399,4 +411,15 @@ env "staging" {
 	require.Equal(t, "mysql://root:pass@:3306/atlas", doc.Envs[1].URLs[0].Value)
 	require.Equal(t, "mysql://root:pass@:3306/ent", doc.Envs[0].URLs[1].Value)
 	require.Equal(t, "mysql://root:pass@:3306/ent", doc.Envs[1].URLs[1].Value)
+
+	// A one-element is allowed for list types.
+	require.NoError(t, New().EvalBytes(b, &doc, map[string]cty.Value{
+		"domains": cty.StringVal("a"),
+	}))
+
+	// Mismatched element types.
+	err := New().EvalBytes(b, &doc, map[string]cty.Value{
+		"domains": cty.BoolVal(false),
+	})
+	require.EqualError(t, err, `variable "domains": list of string required`)
 }
diff --git a/sql/internal/spectest/spectest.go b/sql/internal/spectest/spectest.go
index b2ca5e4e..034223c9 100644
--- a/sql/internal/spectest/spectest.go
+++ b/sql/internal/spectest/spectest.go
@@ -11,8 +11,10 @@ import (
 	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/internal/specutil"
 	"ariga.io/atlas/sql/schema"
+
 	"github.com/hashicorp/hcl/v2/hclparse"
 	"github.com/stretchr/testify/require"
+	"github.com/zclconf/go-cty/cty"
 )
 
 // RegistrySanityTest runs a sanity for a TypeRegistry, generated a dummy *schemahcl.Type
@@ -64,7 +66,7 @@ table "users" {
 	p := hclparse.NewParser()
 	_, diag := p.ParseHCL([]byte(h), "")
 	require.False(t, diag.HasErrors())
-	err := evaluator.Eval(p, &test, map[string]string{"tenant": "rotemtam"})
+	err := evaluator.Eval(p, &test, map[string]cty.Value{"tenant": cty.StringVal("rotemtam")})
 	require.NoError(t, err)
 	require.EqualValues(t, "rotemtam", test.Schemas[0].Name)
 	require.Len(t, test.Schemas[0].Tables, 1)