mirror of https://github.com/databricks/cli.git
Added support for complex variables (#1467)
## Changes Added support for complex variables Now it's possible to add and use complex variables as shown below ``` bundle: name: complex-variables resources: jobs: my_job: job_clusters: - job_cluster_key: key new_cluster: ${var.cluster} tasks: - task_key: test job_cluster_key: key variables: cluster: description: "A cluster definition" type: complex default: spark_version: "13.2.x-scala2.11" node_type_id: "Standard_DS3_v2" num_workers: 2 spark_conf: spark.speculation: true spark.databricks.delta.retentionDurationCheck.enabled: false ``` Fixes #1298 - [x] Support for complex variables - [x] Allow variable overrides (with shortcut) in targets - [x] Don't allow to provide complex variables via flag or env variable - [x] Fail validation if complex value is used but not `type: complex` provided - [x] Support using variables inside complex variables ## Tests Added unit tests --------- Co-authored-by: shreyas-goenka <88374338+shreyas-goenka@users.noreply.github.com>
This commit is contained in:
parent
ce5a3f2ce6
commit
5f42791609
|
@ -35,7 +35,7 @@ func TestResolveClusterReference(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"some-variable": {
|
"some-variable": {
|
||||||
Value: &justString,
|
Value: justString,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -53,8 +53,8 @@ func TestResolveClusterReference(t *testing.T) {
|
||||||
|
|
||||||
diags := bundle.Apply(context.Background(), b, ResolveResourceReferences())
|
diags := bundle.Apply(context.Background(), b, ResolveResourceReferences())
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
require.Equal(t, "1234-5678-abcd", *b.Config.Variables["my-cluster-id-1"].Value)
|
require.Equal(t, "1234-5678-abcd", b.Config.Variables["my-cluster-id-1"].Value)
|
||||||
require.Equal(t, "9876-5432-xywz", *b.Config.Variables["my-cluster-id-2"].Value)
|
require.Equal(t, "9876-5432-xywz", b.Config.Variables["my-cluster-id-2"].Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveNonExistentClusterReference(t *testing.T) {
|
func TestResolveNonExistentClusterReference(t *testing.T) {
|
||||||
|
@ -69,7 +69,7 @@ func TestResolveNonExistentClusterReference(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"some-variable": {
|
"some-variable": {
|
||||||
Value: &justString,
|
Value: justString,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -105,7 +105,7 @@ func TestNoLookupIfVariableIsSet(t *testing.T) {
|
||||||
|
|
||||||
diags := bundle.Apply(context.Background(), b, ResolveResourceReferences())
|
diags := bundle.Apply(context.Background(), b, ResolveResourceReferences())
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
require.Equal(t, "random value", *b.Config.Variables["my-cluster-id"].Value)
|
require.Equal(t, "random value", b.Config.Variables["my-cluster-id"].Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveServicePrincipal(t *testing.T) {
|
func TestResolveServicePrincipal(t *testing.T) {
|
||||||
|
@ -132,14 +132,11 @@ func TestResolveServicePrincipal(t *testing.T) {
|
||||||
|
|
||||||
diags := bundle.Apply(context.Background(), b, ResolveResourceReferences())
|
diags := bundle.Apply(context.Background(), b, ResolveResourceReferences())
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
require.Equal(t, "app-1234", *b.Config.Variables["my-sp"].Value)
|
require.Equal(t, "app-1234", b.Config.Variables["my-sp"].Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveVariableReferencesInVariableLookups(t *testing.T) {
|
func TestResolveVariableReferencesInVariableLookups(t *testing.T) {
|
||||||
s := func(s string) *string {
|
s := "bar"
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
b := &bundle.Bundle{
|
b := &bundle.Bundle{
|
||||||
Config: config.Root{
|
Config: config.Root{
|
||||||
Bundle: config.Bundle{
|
Bundle: config.Bundle{
|
||||||
|
@ -147,7 +144,7 @@ func TestResolveVariableReferencesInVariableLookups(t *testing.T) {
|
||||||
},
|
},
|
||||||
Variables: map[string]*variable.Variable{
|
Variables: map[string]*variable.Variable{
|
||||||
"foo": {
|
"foo": {
|
||||||
Value: s("bar"),
|
Value: s,
|
||||||
},
|
},
|
||||||
"lookup": {
|
"lookup": {
|
||||||
Lookup: &variable.Lookup{
|
Lookup: &variable.Lookup{
|
||||||
|
@ -168,7 +165,7 @@ func TestResolveVariableReferencesInVariableLookups(t *testing.T) {
|
||||||
diags := bundle.Apply(context.Background(), b, bundle.Seq(ResolveVariableReferencesInLookup(), ResolveResourceReferences()))
|
diags := bundle.Apply(context.Background(), b, bundle.Seq(ResolveVariableReferencesInLookup(), ResolveResourceReferences()))
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
require.Equal(t, "cluster-bar-dev", b.Config.Variables["lookup"].Lookup.Cluster)
|
require.Equal(t, "cluster-bar-dev", b.Config.Variables["lookup"].Lookup.Cluster)
|
||||||
require.Equal(t, "1234-5678-abcd", *b.Config.Variables["lookup"].Value)
|
require.Equal(t, "1234-5678-abcd", b.Config.Variables["lookup"].Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveLookupVariableReferencesInVariableLookups(t *testing.T) {
|
func TestResolveLookupVariableReferencesInVariableLookups(t *testing.T) {
|
||||||
|
@ -197,22 +194,15 @@ func TestResolveLookupVariableReferencesInVariableLookups(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoResolveLookupIfVariableSetWithEnvVariable(t *testing.T) {
|
func TestNoResolveLookupIfVariableSetWithEnvVariable(t *testing.T) {
|
||||||
s := func(s string) *string {
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
b := &bundle.Bundle{
|
b := &bundle.Bundle{
|
||||||
Config: config.Root{
|
Config: config.Root{
|
||||||
Bundle: config.Bundle{
|
Bundle: config.Bundle{
|
||||||
Target: "dev",
|
Target: "dev",
|
||||||
},
|
},
|
||||||
Variables: map[string]*variable.Variable{
|
Variables: map[string]*variable.Variable{
|
||||||
"foo": {
|
|
||||||
Value: s("bar"),
|
|
||||||
},
|
|
||||||
"lookup": {
|
"lookup": {
|
||||||
Lookup: &variable.Lookup{
|
Lookup: &variable.Lookup{
|
||||||
Cluster: "cluster-${var.foo}-${bundle.target}",
|
Cluster: "cluster-${bundle.target}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -227,5 +217,5 @@ func TestNoResolveLookupIfVariableSetWithEnvVariable(t *testing.T) {
|
||||||
|
|
||||||
diags := bundle.Apply(ctx, b, bundle.Seq(SetVariables(), ResolveVariableReferencesInLookup(), ResolveResourceReferences()))
|
diags := bundle.Apply(ctx, b, bundle.Seq(SetVariables(), ResolveVariableReferencesInLookup(), ResolveResourceReferences()))
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
require.Equal(t, "1234-5678-abcd", *b.Config.Variables["lookup"].Value)
|
require.Equal(t, "1234-5678-abcd", b.Config.Variables["lookup"].Value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ type resolveVariableReferences struct {
|
||||||
prefixes []string
|
prefixes []string
|
||||||
pattern dyn.Pattern
|
pattern dyn.Pattern
|
||||||
lookupFn func(dyn.Value, dyn.Path) (dyn.Value, error)
|
lookupFn func(dyn.Value, dyn.Path) (dyn.Value, error)
|
||||||
|
skipFn func(dyn.Value) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResolveVariableReferences(prefixes ...string) bundle.Mutator {
|
func ResolveVariableReferences(prefixes ...string) bundle.Mutator {
|
||||||
|
@ -31,6 +32,18 @@ func ResolveVariableReferencesInLookup() bundle.Mutator {
|
||||||
}, pattern: dyn.NewPattern(dyn.Key("variables"), dyn.AnyKey(), dyn.Key("lookup")), lookupFn: lookupForVariables}
|
}, pattern: dyn.NewPattern(dyn.Key("variables"), dyn.AnyKey(), dyn.Key("lookup")), lookupFn: lookupForVariables}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ResolveVariableReferencesInComplexVariables() bundle.Mutator {
|
||||||
|
return &resolveVariableReferences{prefixes: []string{
|
||||||
|
"bundle",
|
||||||
|
"workspace",
|
||||||
|
"variables",
|
||||||
|
},
|
||||||
|
pattern: dyn.NewPattern(dyn.Key("variables"), dyn.AnyKey(), dyn.Key("value")),
|
||||||
|
lookupFn: lookupForComplexVariables,
|
||||||
|
skipFn: skipResolvingInNonComplexVariables,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func lookup(v dyn.Value, path dyn.Path) (dyn.Value, error) {
|
func lookup(v dyn.Value, path dyn.Path) (dyn.Value, error) {
|
||||||
// Future opportunity: if we lookup this path in both the given root
|
// Future opportunity: if we lookup this path in both the given root
|
||||||
// and the synthesized root, we know if it was explicitly set or implied to be empty.
|
// and the synthesized root, we know if it was explicitly set or implied to be empty.
|
||||||
|
@ -38,6 +51,34 @@ func lookup(v dyn.Value, path dyn.Path) (dyn.Value, error) {
|
||||||
return dyn.GetByPath(v, path)
|
return dyn.GetByPath(v, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lookupForComplexVariables(v dyn.Value, path dyn.Path) (dyn.Value, error) {
|
||||||
|
if path[0].Key() != "variables" {
|
||||||
|
return lookup(v, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
varV, err := dyn.GetByPath(v, path[:len(path)-1])
|
||||||
|
if err != nil {
|
||||||
|
return dyn.InvalidValue, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vv variable.Variable
|
||||||
|
err = convert.ToTyped(&vv, varV)
|
||||||
|
if err != nil {
|
||||||
|
return dyn.InvalidValue, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if vv.Type == variable.VariableTypeComplex {
|
||||||
|
return dyn.InvalidValue, fmt.Errorf("complex variables cannot contain references to another complex variables")
|
||||||
|
}
|
||||||
|
|
||||||
|
return lookup(v, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipResolvingInNonComplexVariables(v dyn.Value) bool {
|
||||||
|
_, ok := v.AsMap()
|
||||||
|
return !ok
|
||||||
|
}
|
||||||
|
|
||||||
func lookupForVariables(v dyn.Value, path dyn.Path) (dyn.Value, error) {
|
func lookupForVariables(v dyn.Value, path dyn.Path) (dyn.Value, error) {
|
||||||
if path[0].Key() != "variables" {
|
if path[0].Key() != "variables" {
|
||||||
return lookup(v, path)
|
return lookup(v, path)
|
||||||
|
@ -100,17 +141,27 @@ func (m *resolveVariableReferences) Apply(ctx context.Context, b *bundle.Bundle)
|
||||||
// Resolve variable references in all values.
|
// Resolve variable references in all values.
|
||||||
return dynvar.Resolve(v, func(path dyn.Path) (dyn.Value, error) {
|
return dynvar.Resolve(v, func(path dyn.Path) (dyn.Value, error) {
|
||||||
// Rewrite the shorthand path ${var.foo} into ${variables.foo.value}.
|
// Rewrite the shorthand path ${var.foo} into ${variables.foo.value}.
|
||||||
if path.HasPrefix(varPath) && len(path) == 2 {
|
if path.HasPrefix(varPath) {
|
||||||
path = dyn.NewPath(
|
newPath := dyn.NewPath(
|
||||||
dyn.Key("variables"),
|
dyn.Key("variables"),
|
||||||
path[1],
|
path[1],
|
||||||
dyn.Key("value"),
|
dyn.Key("value"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if len(path) > 2 {
|
||||||
|
newPath = newPath.Append(path[2:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
path = newPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform resolution only if the path starts with one of the specified prefixes.
|
// Perform resolution only if the path starts with one of the specified prefixes.
|
||||||
for _, prefix := range prefixes {
|
for _, prefix := range prefixes {
|
||||||
if path.HasPrefix(prefix) {
|
if path.HasPrefix(prefix) {
|
||||||
|
// Skip resolution if there is a skip function and it returns true.
|
||||||
|
if m.skipFn != nil && m.skipFn(v) {
|
||||||
|
return dyn.InvalidValue, dynvar.ErrSkipResolution
|
||||||
|
}
|
||||||
return m.lookupFn(normalized, path)
|
return m.lookupFn(normalized, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,6 @@ func TestResolveVariableReferences(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveVariableReferencesToBundleVariables(t *testing.T) {
|
func TestResolveVariableReferencesToBundleVariables(t *testing.T) {
|
||||||
s := func(s string) *string {
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
b := &bundle.Bundle{
|
b := &bundle.Bundle{
|
||||||
Config: config.Root{
|
Config: config.Root{
|
||||||
Bundle: config.Bundle{
|
Bundle: config.Bundle{
|
||||||
|
@ -57,7 +53,7 @@ func TestResolveVariableReferencesToBundleVariables(t *testing.T) {
|
||||||
},
|
},
|
||||||
Variables: map[string]*variable.Variable{
|
Variables: map[string]*variable.Variable{
|
||||||
"foo": {
|
"foo": {
|
||||||
Value: s("bar"),
|
Value: "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -195,3 +191,182 @@ func TestResolveVariableReferencesForPrimitiveNonStringFields(t *testing.T) {
|
||||||
assert.Equal(t, 2, b.Config.Resources.Jobs["job1"].JobSettings.Tasks[0].NewCluster.Autoscale.MaxWorkers)
|
assert.Equal(t, 2, b.Config.Resources.Jobs["job1"].JobSettings.Tasks[0].NewCluster.Autoscale.MaxWorkers)
|
||||||
assert.Equal(t, 0.5, b.Config.Resources.Jobs["job1"].JobSettings.Tasks[0].NewCluster.AzureAttributes.SpotBidMaxPrice)
|
assert.Equal(t, 0.5, b.Config.Resources.Jobs["job1"].JobSettings.Tasks[0].NewCluster.AzureAttributes.SpotBidMaxPrice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveComplexVariable(t *testing.T) {
|
||||||
|
b := &bundle.Bundle{
|
||||||
|
Config: config.Root{
|
||||||
|
Bundle: config.Bundle{
|
||||||
|
Name: "example",
|
||||||
|
},
|
||||||
|
Variables: map[string]*variable.Variable{
|
||||||
|
"cluster": {
|
||||||
|
Value: map[string]any{
|
||||||
|
"node_type_id": "Standard_DS3_v2",
|
||||||
|
"num_workers": 2,
|
||||||
|
},
|
||||||
|
Type: variable.VariableTypeComplex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Resources: config.Resources{
|
||||||
|
Jobs: map[string]*resources.Job{
|
||||||
|
"job1": {
|
||||||
|
JobSettings: &jobs.JobSettings{
|
||||||
|
JobClusters: []jobs.JobCluster{
|
||||||
|
{
|
||||||
|
NewCluster: compute.ClusterSpec{
|
||||||
|
NodeTypeId: "random",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Assign the variables to the dynamic configuration.
|
||||||
|
diags := bundle.ApplyFunc(ctx, b, func(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
|
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) {
|
||||||
|
var p dyn.Path
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p = dyn.MustPathFromString("resources.jobs.job1.job_clusters[0]")
|
||||||
|
v, err = dyn.SetByPath(v, p.Append(dyn.Key("new_cluster")), dyn.V("${var.cluster}"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
})
|
||||||
|
return diag.FromErr(err)
|
||||||
|
})
|
||||||
|
require.NoError(t, diags.Error())
|
||||||
|
|
||||||
|
diags = bundle.Apply(ctx, b, ResolveVariableReferences("bundle", "workspace", "variables"))
|
||||||
|
require.NoError(t, diags.Error())
|
||||||
|
require.Equal(t, "Standard_DS3_v2", b.Config.Resources.Jobs["job1"].JobSettings.JobClusters[0].NewCluster.NodeTypeId)
|
||||||
|
require.Equal(t, 2, b.Config.Resources.Jobs["job1"].JobSettings.JobClusters[0].NewCluster.NumWorkers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveComplexVariableReferencesToFields(t *testing.T) {
|
||||||
|
b := &bundle.Bundle{
|
||||||
|
Config: config.Root{
|
||||||
|
Bundle: config.Bundle{
|
||||||
|
Name: "example",
|
||||||
|
},
|
||||||
|
Variables: map[string]*variable.Variable{
|
||||||
|
"cluster": {
|
||||||
|
Value: map[string]any{
|
||||||
|
"node_type_id": "Standard_DS3_v2",
|
||||||
|
"num_workers": 2,
|
||||||
|
},
|
||||||
|
Type: variable.VariableTypeComplex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Resources: config.Resources{
|
||||||
|
Jobs: map[string]*resources.Job{
|
||||||
|
"job1": {
|
||||||
|
JobSettings: &jobs.JobSettings{
|
||||||
|
JobClusters: []jobs.JobCluster{
|
||||||
|
{
|
||||||
|
NewCluster: compute.ClusterSpec{
|
||||||
|
NodeTypeId: "random",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Assign the variables to the dynamic configuration.
|
||||||
|
diags := bundle.ApplyFunc(ctx, b, func(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
|
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) {
|
||||||
|
var p dyn.Path
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p = dyn.MustPathFromString("resources.jobs.job1.job_clusters[0].new_cluster")
|
||||||
|
v, err = dyn.SetByPath(v, p.Append(dyn.Key("node_type_id")), dyn.V("${var.cluster.node_type_id}"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
})
|
||||||
|
return diag.FromErr(err)
|
||||||
|
})
|
||||||
|
require.NoError(t, diags.Error())
|
||||||
|
|
||||||
|
diags = bundle.Apply(ctx, b, ResolveVariableReferences("bundle", "workspace", "variables"))
|
||||||
|
require.NoError(t, diags.Error())
|
||||||
|
require.Equal(t, "Standard_DS3_v2", b.Config.Resources.Jobs["job1"].JobSettings.JobClusters[0].NewCluster.NodeTypeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveComplexVariableReferencesWithComplexVariablesError(t *testing.T) {
|
||||||
|
b := &bundle.Bundle{
|
||||||
|
Config: config.Root{
|
||||||
|
Bundle: config.Bundle{
|
||||||
|
Name: "example",
|
||||||
|
},
|
||||||
|
Variables: map[string]*variable.Variable{
|
||||||
|
"cluster": {
|
||||||
|
Value: map[string]any{
|
||||||
|
"node_type_id": "Standard_DS3_v2",
|
||||||
|
"num_workers": 2,
|
||||||
|
"spark_conf": "${var.spark_conf}",
|
||||||
|
},
|
||||||
|
Type: variable.VariableTypeComplex,
|
||||||
|
},
|
||||||
|
"spark_conf": {
|
||||||
|
Value: map[string]any{
|
||||||
|
"spark.executor.memory": "4g",
|
||||||
|
"spark.executor.cores": "2",
|
||||||
|
},
|
||||||
|
Type: variable.VariableTypeComplex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Resources: config.Resources{
|
||||||
|
Jobs: map[string]*resources.Job{
|
||||||
|
"job1": {
|
||||||
|
JobSettings: &jobs.JobSettings{
|
||||||
|
JobClusters: []jobs.JobCluster{
|
||||||
|
{
|
||||||
|
NewCluster: compute.ClusterSpec{
|
||||||
|
NodeTypeId: "random",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Assign the variables to the dynamic configuration.
|
||||||
|
diags := bundle.ApplyFunc(ctx, b, func(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
|
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) {
|
||||||
|
var p dyn.Path
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p = dyn.MustPathFromString("resources.jobs.job1.job_clusters[0]")
|
||||||
|
v, err = dyn.SetByPath(v, p.Append(dyn.Key("new_cluster")), dyn.V("${var.cluster}"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
})
|
||||||
|
return diag.FromErr(err)
|
||||||
|
})
|
||||||
|
require.NoError(t, diags.Error())
|
||||||
|
|
||||||
|
diags = bundle.Apply(ctx, b, bundle.Seq(ResolveVariableReferencesInComplexVariables(), ResolveVariableReferences("bundle", "workspace", "variables")))
|
||||||
|
require.ErrorContains(t, diags.Error(), "complex variables cannot contain references to another complex variables")
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,10 @@ func setVariable(ctx context.Context, v *variable.Variable, name string) diag.Di
|
||||||
// case: read and set variable value from process environment
|
// case: read and set variable value from process environment
|
||||||
envVarName := bundleVarPrefix + name
|
envVarName := bundleVarPrefix + name
|
||||||
if val, ok := env.Lookup(ctx, envVarName); ok {
|
if val, ok := env.Lookup(ctx, envVarName); ok {
|
||||||
|
if v.IsComplex() {
|
||||||
|
return diag.Errorf(`setting via environment variables (%s) is not supported for complex variable %s`, envVarName, name)
|
||||||
|
}
|
||||||
|
|
||||||
err := v.Set(val)
|
err := v.Set(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf(`failed to assign value "%s" to variable %s from environment variable %s with error: %v`, val, name, envVarName, err)
|
return diag.Errorf(`failed to assign value "%s" to variable %s from environment variable %s with error: %v`, val, name, envVarName, err)
|
||||||
|
@ -45,9 +49,9 @@ func setVariable(ctx context.Context, v *variable.Variable, name string) diag.Di
|
||||||
|
|
||||||
// case: Set the variable to its default value
|
// case: Set the variable to its default value
|
||||||
if v.HasDefault() {
|
if v.HasDefault() {
|
||||||
err := v.Set(*v.Default)
|
err := v.Set(v.Default)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf(`failed to assign default value from config "%s" to variable %s with error: %v`, *v.Default, name, err)
|
return diag.Errorf(`failed to assign default value from config "%s" to variable %s with error: %v`, v.Default, name, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ func TestSetVariableFromProcessEnvVar(t *testing.T) {
|
||||||
defaultVal := "default"
|
defaultVal := "default"
|
||||||
variable := variable.Variable{
|
variable := variable.Variable{
|
||||||
Description: "a test variable",
|
Description: "a test variable",
|
||||||
Default: &defaultVal,
|
Default: defaultVal,
|
||||||
}
|
}
|
||||||
|
|
||||||
// set value for variable as an environment variable
|
// set value for variable as an environment variable
|
||||||
|
@ -23,19 +23,19 @@ func TestSetVariableFromProcessEnvVar(t *testing.T) {
|
||||||
|
|
||||||
diags := setVariable(context.Background(), &variable, "foo")
|
diags := setVariable(context.Background(), &variable, "foo")
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
assert.Equal(t, *variable.Value, "process-env")
|
assert.Equal(t, variable.Value, "process-env")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetVariableUsingDefaultValue(t *testing.T) {
|
func TestSetVariableUsingDefaultValue(t *testing.T) {
|
||||||
defaultVal := "default"
|
defaultVal := "default"
|
||||||
variable := variable.Variable{
|
variable := variable.Variable{
|
||||||
Description: "a test variable",
|
Description: "a test variable",
|
||||||
Default: &defaultVal,
|
Default: defaultVal,
|
||||||
}
|
}
|
||||||
|
|
||||||
diags := setVariable(context.Background(), &variable, "foo")
|
diags := setVariable(context.Background(), &variable, "foo")
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
assert.Equal(t, *variable.Value, "default")
|
assert.Equal(t, variable.Value, "default")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetVariableWhenAlreadyAValueIsAssigned(t *testing.T) {
|
func TestSetVariableWhenAlreadyAValueIsAssigned(t *testing.T) {
|
||||||
|
@ -43,15 +43,15 @@ func TestSetVariableWhenAlreadyAValueIsAssigned(t *testing.T) {
|
||||||
val := "assigned-value"
|
val := "assigned-value"
|
||||||
variable := variable.Variable{
|
variable := variable.Variable{
|
||||||
Description: "a test variable",
|
Description: "a test variable",
|
||||||
Default: &defaultVal,
|
Default: defaultVal,
|
||||||
Value: &val,
|
Value: val,
|
||||||
}
|
}
|
||||||
|
|
||||||
// since a value is already assigned to the variable, it would not be overridden
|
// since a value is already assigned to the variable, it would not be overridden
|
||||||
// by the default value
|
// by the default value
|
||||||
diags := setVariable(context.Background(), &variable, "foo")
|
diags := setVariable(context.Background(), &variable, "foo")
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
assert.Equal(t, *variable.Value, "assigned-value")
|
assert.Equal(t, variable.Value, "assigned-value")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetVariableEnvVarValueDoesNotOverridePresetValue(t *testing.T) {
|
func TestSetVariableEnvVarValueDoesNotOverridePresetValue(t *testing.T) {
|
||||||
|
@ -59,8 +59,8 @@ func TestSetVariableEnvVarValueDoesNotOverridePresetValue(t *testing.T) {
|
||||||
val := "assigned-value"
|
val := "assigned-value"
|
||||||
variable := variable.Variable{
|
variable := variable.Variable{
|
||||||
Description: "a test variable",
|
Description: "a test variable",
|
||||||
Default: &defaultVal,
|
Default: defaultVal,
|
||||||
Value: &val,
|
Value: val,
|
||||||
}
|
}
|
||||||
|
|
||||||
// set value for variable as an environment variable
|
// set value for variable as an environment variable
|
||||||
|
@ -70,7 +70,7 @@ func TestSetVariableEnvVarValueDoesNotOverridePresetValue(t *testing.T) {
|
||||||
// by the value from environment
|
// by the value from environment
|
||||||
diags := setVariable(context.Background(), &variable, "foo")
|
diags := setVariable(context.Background(), &variable, "foo")
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
assert.Equal(t, *variable.Value, "assigned-value")
|
assert.Equal(t, variable.Value, "assigned-value")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetVariablesErrorsIfAValueCouldNotBeResolved(t *testing.T) {
|
func TestSetVariablesErrorsIfAValueCouldNotBeResolved(t *testing.T) {
|
||||||
|
@ -92,15 +92,15 @@ func TestSetVariablesMutator(t *testing.T) {
|
||||||
Variables: map[string]*variable.Variable{
|
Variables: map[string]*variable.Variable{
|
||||||
"a": {
|
"a": {
|
||||||
Description: "resolved to default value",
|
Description: "resolved to default value",
|
||||||
Default: &defaultValForA,
|
Default: defaultValForA,
|
||||||
},
|
},
|
||||||
"b": {
|
"b": {
|
||||||
Description: "resolved from environment vairables",
|
Description: "resolved from environment vairables",
|
||||||
Default: &defaultValForB,
|
Default: defaultValForB,
|
||||||
},
|
},
|
||||||
"c": {
|
"c": {
|
||||||
Description: "has already been assigned a value",
|
Description: "has already been assigned a value",
|
||||||
Value: &valForC,
|
Value: valForC,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -110,7 +110,22 @@ func TestSetVariablesMutator(t *testing.T) {
|
||||||
|
|
||||||
diags := bundle.Apply(context.Background(), b, SetVariables())
|
diags := bundle.Apply(context.Background(), b, SetVariables())
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
assert.Equal(t, "default-a", *b.Config.Variables["a"].Value)
|
assert.Equal(t, "default-a", b.Config.Variables["a"].Value)
|
||||||
assert.Equal(t, "env-var-b", *b.Config.Variables["b"].Value)
|
assert.Equal(t, "env-var-b", b.Config.Variables["b"].Value)
|
||||||
assert.Equal(t, "assigned-val-c", *b.Config.Variables["c"].Value)
|
assert.Equal(t, "assigned-val-c", b.Config.Variables["c"].Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetComplexVariablesViaEnvVariablesIsNotAllowed(t *testing.T) {
|
||||||
|
defaultVal := "default"
|
||||||
|
variable := variable.Variable{
|
||||||
|
Description: "a test variable",
|
||||||
|
Default: defaultVal,
|
||||||
|
Type: variable.VariableTypeComplex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// set value for variable as an environment variable
|
||||||
|
t.Setenv("BUNDLE_VAR_foo", "process-env")
|
||||||
|
|
||||||
|
diags := setVariable(context.Background(), &variable, "foo")
|
||||||
|
assert.ErrorContains(t, diags.Error(), "setting via environment variables (BUNDLE_VAR_foo) is not supported for complex variable foo")
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,6 +267,11 @@ func (r *Root) InitializeVariables(vars []string) error {
|
||||||
if _, ok := r.Variables[name]; !ok {
|
if _, ok := r.Variables[name]; !ok {
|
||||||
return fmt.Errorf("variable %s has not been defined", name)
|
return fmt.Errorf("variable %s has not been defined", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.Variables[name].IsComplex() {
|
||||||
|
return fmt.Errorf("setting variables of complex type via --var flag is not supported: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
err := r.Variables[name].Set(val)
|
err := r.Variables[name].Set(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to assign %s to %s: %s", val, name, err)
|
return fmt.Errorf("failed to assign %s to %s: %s", val, name, err)
|
||||||
|
@ -419,7 +424,7 @@ func rewriteShorthands(v dyn.Value) (dyn.Value, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each variable, normalize its contents if it is a single string.
|
// For each variable, normalize its contents if it is a single string.
|
||||||
return dyn.Map(target, "variables", dyn.Foreach(func(_ dyn.Path, variable dyn.Value) (dyn.Value, error) {
|
return dyn.Map(target, "variables", dyn.Foreach(func(p dyn.Path, variable dyn.Value) (dyn.Value, error) {
|
||||||
switch variable.Kind() {
|
switch variable.Kind() {
|
||||||
|
|
||||||
case dyn.KindString, dyn.KindBool, dyn.KindFloat, dyn.KindInt:
|
case dyn.KindString, dyn.KindBool, dyn.KindFloat, dyn.KindInt:
|
||||||
|
@ -430,6 +435,21 @@ func rewriteShorthands(v dyn.Value) (dyn.Value, error) {
|
||||||
"default": variable,
|
"default": variable,
|
||||||
}, variable.Location()), nil
|
}, variable.Location()), nil
|
||||||
|
|
||||||
|
case dyn.KindMap, dyn.KindSequence:
|
||||||
|
// Check if the original definition of variable has a type field.
|
||||||
|
typeV, err := dyn.GetByPath(v, p.Append(dyn.Key("type")))
|
||||||
|
if err != nil {
|
||||||
|
return variable, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeV.MustString() == "complex" {
|
||||||
|
return dyn.NewValue(map[string]dyn.Value{
|
||||||
|
"default": variable,
|
||||||
|
}, variable.Location()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return variable, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return variable, nil
|
return variable, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ func TestInitializeVariables(t *testing.T) {
|
||||||
root := &Root{
|
root := &Root{
|
||||||
Variables: map[string]*variable.Variable{
|
Variables: map[string]*variable.Variable{
|
||||||
"foo": {
|
"foo": {
|
||||||
Default: &fooDefault,
|
Default: fooDefault,
|
||||||
Description: "an optional variable since default is defined",
|
Description: "an optional variable since default is defined",
|
||||||
},
|
},
|
||||||
"bar": {
|
"bar": {
|
||||||
|
@ -62,8 +62,8 @@ func TestInitializeVariables(t *testing.T) {
|
||||||
|
|
||||||
err := root.InitializeVariables([]string{"foo=123", "bar=456"})
|
err := root.InitializeVariables([]string{"foo=123", "bar=456"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "123", *(root.Variables["foo"].Value))
|
assert.Equal(t, "123", (root.Variables["foo"].Value))
|
||||||
assert.Equal(t, "456", *(root.Variables["bar"].Value))
|
assert.Equal(t, "456", (root.Variables["bar"].Value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInitializeVariablesWithAnEqualSignInValue(t *testing.T) {
|
func TestInitializeVariablesWithAnEqualSignInValue(t *testing.T) {
|
||||||
|
@ -77,7 +77,7 @@ func TestInitializeVariablesWithAnEqualSignInValue(t *testing.T) {
|
||||||
|
|
||||||
err := root.InitializeVariables([]string{"foo=123=567"})
|
err := root.InitializeVariables([]string{"foo=123=567"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "123=567", *(root.Variables["foo"].Value))
|
assert.Equal(t, "123=567", (root.Variables["foo"].Value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInitializeVariablesInvalidFormat(t *testing.T) {
|
func TestInitializeVariablesInvalidFormat(t *testing.T) {
|
||||||
|
@ -119,3 +119,16 @@ func TestRootMergeTargetOverridesWithMode(t *testing.T) {
|
||||||
require.NoError(t, root.MergeTargetOverrides("development"))
|
require.NoError(t, root.MergeTargetOverrides("development"))
|
||||||
assert.Equal(t, Development, root.Bundle.Mode)
|
assert.Equal(t, Development, root.Bundle.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInitializeComplexVariablesViaFlagIsNotAllowed(t *testing.T) {
|
||||||
|
root := &Root{
|
||||||
|
Variables: map[string]*variable.Variable{
|
||||||
|
"foo": {
|
||||||
|
Type: variable.VariableTypeComplex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := root.InitializeVariables([]string{"foo=123"})
|
||||||
|
assert.ErrorContains(t, err, "setting variables of complex type via --var flag is not supported: foo")
|
||||||
|
}
|
||||||
|
|
|
@ -2,12 +2,27 @@ package variable
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// We are using `any` because since introduction of complex variables,
|
||||||
|
// variables can be of any type.
|
||||||
|
// Type alias is used to make it easier to understand the code.
|
||||||
|
type VariableValue = any
|
||||||
|
|
||||||
|
type VariableType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
VariableTypeComplex VariableType = "complex"
|
||||||
)
|
)
|
||||||
|
|
||||||
// An input variable for the bundle config
|
// An input variable for the bundle config
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
|
// A type of the variable. This is used to validate the value of the variable
|
||||||
|
Type VariableType `json:"type,omitempty"`
|
||||||
|
|
||||||
// A default value which then makes the variable optional
|
// A default value which then makes the variable optional
|
||||||
Default *string `json:"default,omitempty"`
|
Default VariableValue `json:"default,omitempty"`
|
||||||
|
|
||||||
// Documentation for this input variable
|
// Documentation for this input variable
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
|
@ -21,7 +36,7 @@ type Variable struct {
|
||||||
// 4. Default value defined in variable definition
|
// 4. Default value defined in variable definition
|
||||||
// 5. Throw error, since if no default value is defined, then the variable
|
// 5. Throw error, since if no default value is defined, then the variable
|
||||||
// is required
|
// is required
|
||||||
Value *string `json:"value,omitempty" bundle:"readonly"`
|
Value VariableValue `json:"value,omitempty" bundle:"readonly"`
|
||||||
|
|
||||||
// The value of this field will be used to lookup the resource by name
|
// The value of this field will be used to lookup the resource by name
|
||||||
// And assign the value of the variable to ID of the resource found.
|
// And assign the value of the variable to ID of the resource found.
|
||||||
|
@ -39,10 +54,24 @@ func (v *Variable) HasValue() bool {
|
||||||
return v.Value != nil
|
return v.Value != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) Set(val string) error {
|
func (v *Variable) Set(val VariableValue) error {
|
||||||
if v.HasValue() {
|
if v.HasValue() {
|
||||||
return fmt.Errorf("variable has already been assigned value: %s", *v.Value)
|
return fmt.Errorf("variable has already been assigned value: %s", v.Value)
|
||||||
}
|
}
|
||||||
v.Value = &val
|
|
||||||
|
rv := reflect.ValueOf(val)
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map:
|
||||||
|
if v.Type != VariableTypeComplex {
|
||||||
|
return fmt.Errorf("variable type is not complex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Value = val
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Variable) IsComplex() bool {
|
||||||
|
return v.Type == VariableTypeComplex
|
||||||
|
}
|
||||||
|
|
|
@ -29,11 +29,13 @@ func Initialize() bundle.Mutator {
|
||||||
mutator.ExpandWorkspaceRoot(),
|
mutator.ExpandWorkspaceRoot(),
|
||||||
mutator.DefineDefaultWorkspacePaths(),
|
mutator.DefineDefaultWorkspacePaths(),
|
||||||
mutator.SetVariables(),
|
mutator.SetVariables(),
|
||||||
// Intentionally placed before ResolveVariableReferencesInLookup, ResolveResourceReferences
|
// Intentionally placed before ResolveVariableReferencesInLookup, ResolveResourceReferences,
|
||||||
// and ResolveVariableReferences. See what is expected in PythonMutatorPhaseInit doc
|
// ResolveVariableReferencesInComplexVariables and ResolveVariableReferences.
|
||||||
|
// See what is expected in PythonMutatorPhaseInit doc
|
||||||
pythonmutator.PythonMutator(pythonmutator.PythonMutatorPhaseInit),
|
pythonmutator.PythonMutator(pythonmutator.PythonMutatorPhaseInit),
|
||||||
mutator.ResolveVariableReferencesInLookup(),
|
mutator.ResolveVariableReferencesInLookup(),
|
||||||
mutator.ResolveResourceReferences(),
|
mutator.ResolveResourceReferences(),
|
||||||
|
mutator.ResolveVariableReferencesInComplexVariables(),
|
||||||
mutator.ResolveVariableReferences(
|
mutator.ResolveVariableReferences(
|
||||||
"bundle",
|
"bundle",
|
||||||
"workspace",
|
"workspace",
|
||||||
|
|
|
@ -20,7 +20,7 @@ func TestIntSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}`
|
}`
|
||||||
|
@ -47,7 +47,7 @@ func TestBooleanSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}`
|
}`
|
||||||
|
@ -123,7 +123,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -134,7 +134,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -145,7 +145,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -156,7 +156,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -167,7 +167,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -178,7 +178,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -189,7 +189,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -200,7 +200,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -214,7 +214,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -225,7 +225,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -236,7 +236,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -247,7 +247,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -258,7 +258,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -326,7 +326,7 @@ func TestStructOfStructsSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -391,7 +391,7 @@ func TestStructOfMapsSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -481,7 +481,7 @@ func TestMapOfPrimitivesSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -518,7 +518,7 @@ func TestMapOfStructSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -556,7 +556,7 @@ func TestMapOfMapSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -661,7 +661,7 @@ func TestSliceOfMapSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -699,7 +699,7 @@ func TestSliceOfStructSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -757,7 +757,7 @@ func TestEmbeddedStructSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -797,7 +797,7 @@ func TestEmbeddedStructSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -892,7 +892,7 @@ func TestNonAnnotatedFieldsAreSkipped(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -934,7 +934,7 @@ func TestDashFieldsAreSkipped(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -987,7 +987,7 @@ func TestPointerInStructSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1004,7 +1004,7 @@ func TestPointerInStructSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1018,7 +1018,7 @@ func TestPointerInStructSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1035,7 +1035,7 @@ func TestPointerInStructSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1106,7 +1106,7 @@ func TestGenericSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1129,7 +1129,7 @@ func TestGenericSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1157,7 +1157,7 @@ func TestGenericSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1180,7 +1180,7 @@ func TestGenericSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1210,7 +1210,7 @@ func TestGenericSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1236,7 +1236,7 @@ func TestGenericSchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1322,7 +1322,7 @@ func TestFieldsWithoutOmitEmptyAreRequired(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1333,7 +1333,7 @@ func TestFieldsWithoutOmitEmptyAreRequired(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1347,7 +1347,7 @@ func TestFieldsWithoutOmitEmptyAreRequired(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1429,7 +1429,7 @@ func TestDocIngestionForObject(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1512,7 +1512,7 @@ func TestDocIngestionForSlice(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1524,7 +1524,7 @@ func TestDocIngestionForSlice(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1611,7 +1611,7 @@ func TestDocIngestionForMap(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1623,7 +1623,7 @@ func TestDocIngestionForMap(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1683,7 +1683,7 @@ func TestDocIngestionForTopLevelPrimitive(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1761,7 +1761,7 @@ func TestInterfaceGeneratesEmptySchema(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1810,7 +1810,7 @@ func TestBundleReadOnlytag(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1870,7 +1870,7 @@ func TestBundleInternalTag(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\\}"
|
"pattern": "\\$\\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)*(\\[[0-9]+\\])*)\\}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package config_tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/databricks/cli/bundle"
|
||||||
|
"github.com/databricks/cli/bundle/config/mutator"
|
||||||
|
"github.com/databricks/databricks-sdk-go/service/compute"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestComplexVariables(t *testing.T) {
|
||||||
|
b, diags := loadTargetWithDiags("variables/complex", "default")
|
||||||
|
require.Empty(t, diags)
|
||||||
|
|
||||||
|
diags = bundle.Apply(context.Background(), b, bundle.Seq(
|
||||||
|
mutator.SetVariables(),
|
||||||
|
mutator.ResolveVariableReferencesInComplexVariables(),
|
||||||
|
mutator.ResolveVariableReferences(
|
||||||
|
"variables",
|
||||||
|
),
|
||||||
|
))
|
||||||
|
require.NoError(t, diags.Error())
|
||||||
|
|
||||||
|
require.Equal(t, "13.2.x-scala2.11", b.Config.Resources.Jobs["my_job"].JobClusters[0].NewCluster.SparkVersion)
|
||||||
|
require.Equal(t, "Standard_DS3_v2", b.Config.Resources.Jobs["my_job"].JobClusters[0].NewCluster.NodeTypeId)
|
||||||
|
require.Equal(t, 2, b.Config.Resources.Jobs["my_job"].JobClusters[0].NewCluster.NumWorkers)
|
||||||
|
require.Equal(t, "true", b.Config.Resources.Jobs["my_job"].JobClusters[0].NewCluster.SparkConf["spark.speculation"])
|
||||||
|
|
||||||
|
require.Equal(t, 3, len(b.Config.Resources.Jobs["my_job"].Tasks[0].Libraries))
|
||||||
|
require.Contains(t, b.Config.Resources.Jobs["my_job"].Tasks[0].Libraries, compute.Library{
|
||||||
|
Jar: "/path/to/jar",
|
||||||
|
})
|
||||||
|
require.Contains(t, b.Config.Resources.Jobs["my_job"].Tasks[0].Libraries, compute.Library{
|
||||||
|
Egg: "/path/to/egg",
|
||||||
|
})
|
||||||
|
require.Contains(t, b.Config.Resources.Jobs["my_job"].Tasks[0].Libraries, compute.Library{
|
||||||
|
Whl: "/path/to/whl",
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Equal(t, "task with spark version 13.2.x-scala2.11 and jar /path/to/jar", b.Config.Resources.Jobs["my_job"].Tasks[0].TaskKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComplexVariablesOverride(t *testing.T) {
|
||||||
|
b, diags := loadTargetWithDiags("variables/complex", "dev")
|
||||||
|
require.Empty(t, diags)
|
||||||
|
|
||||||
|
diags = bundle.Apply(context.Background(), b, bundle.Seq(
|
||||||
|
mutator.SetVariables(),
|
||||||
|
mutator.ResolveVariableReferencesInComplexVariables(),
|
||||||
|
mutator.ResolveVariableReferences(
|
||||||
|
"variables",
|
||||||
|
),
|
||||||
|
))
|
||||||
|
require.NoError(t, diags.Error())
|
||||||
|
|
||||||
|
require.Equal(t, "14.2.x-scala2.11", b.Config.Resources.Jobs["my_job"].JobClusters[0].NewCluster.SparkVersion)
|
||||||
|
require.Equal(t, "Standard_DS3_v3", b.Config.Resources.Jobs["my_job"].JobClusters[0].NewCluster.NodeTypeId)
|
||||||
|
require.Equal(t, 4, b.Config.Resources.Jobs["my_job"].JobClusters[0].NewCluster.NumWorkers)
|
||||||
|
require.Equal(t, "false", b.Config.Resources.Jobs["my_job"].JobClusters[0].NewCluster.SparkConf["spark.speculation"])
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
bundle:
|
||||||
|
name: complex-variables
|
||||||
|
|
||||||
|
resources:
|
||||||
|
jobs:
|
||||||
|
my_job:
|
||||||
|
job_clusters:
|
||||||
|
- job_cluster_key: key
|
||||||
|
new_cluster: ${var.cluster}
|
||||||
|
tasks:
|
||||||
|
- task_key: test
|
||||||
|
job_cluster_key: key
|
||||||
|
libraries: ${variables.libraries.value}
|
||||||
|
task_key: "task with spark version ${var.cluster.spark_version} and jar ${var.libraries[0].jar}"
|
||||||
|
|
||||||
|
variables:
|
||||||
|
node_type:
|
||||||
|
default: "Standard_DS3_v2"
|
||||||
|
cluster:
|
||||||
|
type: complex
|
||||||
|
description: "A cluster definition"
|
||||||
|
default:
|
||||||
|
spark_version: "13.2.x-scala2.11"
|
||||||
|
node_type_id: ${var.node_type}
|
||||||
|
num_workers: 2
|
||||||
|
spark_conf:
|
||||||
|
spark.speculation: true
|
||||||
|
spark.databricks.delta.retentionDurationCheck.enabled: false
|
||||||
|
libraries:
|
||||||
|
type: complex
|
||||||
|
description: "A libraries definition"
|
||||||
|
default:
|
||||||
|
- jar: "/path/to/jar"
|
||||||
|
- egg: "/path/to/egg"
|
||||||
|
- whl: "/path/to/whl"
|
||||||
|
|
||||||
|
|
||||||
|
targets:
|
||||||
|
default:
|
||||||
|
dev:
|
||||||
|
variables:
|
||||||
|
node_type: "Standard_DS3_v3"
|
||||||
|
cluster:
|
||||||
|
spark_version: "14.2.x-scala2.11"
|
||||||
|
node_type_id: ${var.node_type}
|
||||||
|
num_workers: 4
|
||||||
|
spark_conf:
|
||||||
|
spark.speculation: false
|
||||||
|
spark.databricks.delta.retentionDurationCheck.enabled: false
|
|
@ -109,8 +109,8 @@ func TestVariablesWithoutDefinition(t *testing.T) {
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
require.True(t, b.Config.Variables["a"].HasValue())
|
require.True(t, b.Config.Variables["a"].HasValue())
|
||||||
require.True(t, b.Config.Variables["b"].HasValue())
|
require.True(t, b.Config.Variables["b"].HasValue())
|
||||||
assert.Equal(t, "foo", *b.Config.Variables["a"].Value)
|
assert.Equal(t, "foo", b.Config.Variables["a"].Value)
|
||||||
assert.Equal(t, "bar", *b.Config.Variables["b"].Value)
|
assert.Equal(t, "bar", b.Config.Variables["b"].Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVariablesWithTargetLookupOverrides(t *testing.T) {
|
func TestVariablesWithTargetLookupOverrides(t *testing.T) {
|
||||||
|
@ -140,9 +140,9 @@ func TestVariablesWithTargetLookupOverrides(t *testing.T) {
|
||||||
))
|
))
|
||||||
|
|
||||||
require.NoError(t, diags.Error())
|
require.NoError(t, diags.Error())
|
||||||
assert.Equal(t, "4321", *b.Config.Variables["d"].Value)
|
assert.Equal(t, "4321", b.Config.Variables["d"].Value)
|
||||||
assert.Equal(t, "1234", *b.Config.Variables["e"].Value)
|
assert.Equal(t, "1234", b.Config.Variables["e"].Value)
|
||||||
assert.Equal(t, "9876", *b.Config.Variables["f"].Value)
|
assert.Equal(t, "9876", b.Config.Variables["f"].Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVariableTargetOverrides(t *testing.T) {
|
func TestVariableTargetOverrides(t *testing.T) {
|
||||||
|
|
|
@ -81,6 +81,11 @@ func fromTyped(src any, ref dyn.Value, options ...fromTypedOptions) (dyn.Value,
|
||||||
func fromTypedStruct(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) {
|
func fromTypedStruct(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) {
|
||||||
// Check that the reference value is compatible or nil.
|
// Check that the reference value is compatible or nil.
|
||||||
switch ref.Kind() {
|
switch ref.Kind() {
|
||||||
|
case dyn.KindString:
|
||||||
|
// Ignore pure variable references (e.g. ${var.foo}).
|
||||||
|
if dynvar.IsPureVariableReference(ref.MustString()) {
|
||||||
|
return ref, nil
|
||||||
|
}
|
||||||
case dyn.KindMap, dyn.KindNil:
|
case dyn.KindMap, dyn.KindNil:
|
||||||
default:
|
default:
|
||||||
return dyn.InvalidValue, fmt.Errorf("unhandled type: %s", ref.Kind())
|
return dyn.InvalidValue, fmt.Errorf("unhandled type: %s", ref.Kind())
|
||||||
|
@ -100,8 +105,13 @@ func fromTypedStruct(src reflect.Value, ref dyn.Value, options ...fromTypedOptio
|
||||||
refv = dyn.NilValue
|
refv = dyn.NilValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var options []fromTypedOptions
|
||||||
|
if v.Kind() == reflect.Interface {
|
||||||
|
options = append(options, includeZeroValues)
|
||||||
|
}
|
||||||
|
|
||||||
// Convert the field taking into account the reference value (may be equal to config.NilValue).
|
// Convert the field taking into account the reference value (may be equal to config.NilValue).
|
||||||
nv, err := fromTyped(v.Interface(), refv)
|
nv, err := fromTyped(v.Interface(), refv, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dyn.InvalidValue, err
|
return dyn.InvalidValue, err
|
||||||
}
|
}
|
||||||
|
@ -127,6 +137,11 @@ func fromTypedStruct(src reflect.Value, ref dyn.Value, options ...fromTypedOptio
|
||||||
func fromTypedMap(src reflect.Value, ref dyn.Value) (dyn.Value, error) {
|
func fromTypedMap(src reflect.Value, ref dyn.Value) (dyn.Value, error) {
|
||||||
// Check that the reference value is compatible or nil.
|
// Check that the reference value is compatible or nil.
|
||||||
switch ref.Kind() {
|
switch ref.Kind() {
|
||||||
|
case dyn.KindString:
|
||||||
|
// Ignore pure variable references (e.g. ${var.foo}).
|
||||||
|
if dynvar.IsPureVariableReference(ref.MustString()) {
|
||||||
|
return ref, nil
|
||||||
|
}
|
||||||
case dyn.KindMap, dyn.KindNil:
|
case dyn.KindMap, dyn.KindNil:
|
||||||
default:
|
default:
|
||||||
return dyn.InvalidValue, fmt.Errorf("unhandled type: %s", ref.Kind())
|
return dyn.InvalidValue, fmt.Errorf("unhandled type: %s", ref.Kind())
|
||||||
|
@ -170,6 +185,11 @@ func fromTypedMap(src reflect.Value, ref dyn.Value) (dyn.Value, error) {
|
||||||
func fromTypedSlice(src reflect.Value, ref dyn.Value) (dyn.Value, error) {
|
func fromTypedSlice(src reflect.Value, ref dyn.Value) (dyn.Value, error) {
|
||||||
// Check that the reference value is compatible or nil.
|
// Check that the reference value is compatible or nil.
|
||||||
switch ref.Kind() {
|
switch ref.Kind() {
|
||||||
|
case dyn.KindString:
|
||||||
|
// Ignore pure variable references (e.g. ${var.foo}).
|
||||||
|
if dynvar.IsPureVariableReference(ref.MustString()) {
|
||||||
|
return ref, nil
|
||||||
|
}
|
||||||
case dyn.KindSequence, dyn.KindNil:
|
case dyn.KindSequence, dyn.KindNil:
|
||||||
default:
|
default:
|
||||||
return dyn.InvalidValue, fmt.Errorf("unhandled type: %s", ref.Kind())
|
return dyn.InvalidValue, fmt.Errorf("unhandled type: %s", ref.Kind())
|
||||||
|
|
|
@ -662,6 +662,42 @@ func TestFromTypedFloatTypeError(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFromTypedAny(t *testing.T) {
|
||||||
|
type Tmp struct {
|
||||||
|
Foo any `json:"foo"`
|
||||||
|
Bar any `json:"bar"`
|
||||||
|
Foz any `json:"foz"`
|
||||||
|
Baz any `json:"baz"`
|
||||||
|
}
|
||||||
|
|
||||||
|
src := Tmp{
|
||||||
|
Foo: "foo",
|
||||||
|
Bar: false,
|
||||||
|
Foz: 0,
|
||||||
|
Baz: map[string]any{
|
||||||
|
"foo": "foo",
|
||||||
|
"bar": 1234,
|
||||||
|
"qux": 0,
|
||||||
|
"nil": nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := dyn.NilValue
|
||||||
|
nv, err := FromTyped(src, ref)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, dyn.V(map[string]dyn.Value{
|
||||||
|
"foo": dyn.V("foo"),
|
||||||
|
"bar": dyn.V(false),
|
||||||
|
"foz": dyn.V(int64(0)),
|
||||||
|
"baz": dyn.V(map[string]dyn.Value{
|
||||||
|
"foo": dyn.V("foo"),
|
||||||
|
"bar": dyn.V(int64(1234)),
|
||||||
|
"qux": dyn.V(int64(0)),
|
||||||
|
"nil": dyn.V(nil),
|
||||||
|
}),
|
||||||
|
}), nv)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFromTypedAnyNil(t *testing.T) {
|
func TestFromTypedAnyNil(t *testing.T) {
|
||||||
var src any = nil
|
var src any = nil
|
||||||
var ref = dyn.NilValue
|
var ref = dyn.NilValue
|
||||||
|
|
|
@ -56,6 +56,8 @@ func (n normalizeOptions) normalizeType(typ reflect.Type, src dyn.Value, seen []
|
||||||
return n.normalizeInt(typ, src, path)
|
return n.normalizeInt(typ, src, path)
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return n.normalizeFloat(typ, src, path)
|
return n.normalizeFloat(typ, src, path)
|
||||||
|
case reflect.Interface:
|
||||||
|
return n.normalizeInterface(typ, src, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dyn.InvalidValue, diag.Errorf("unsupported type: %s", typ.Kind())
|
return dyn.InvalidValue, diag.Errorf("unsupported type: %s", typ.Kind())
|
||||||
|
@ -166,8 +168,15 @@ func (n normalizeOptions) normalizeStruct(typ reflect.Type, src dyn.Value, seen
|
||||||
return dyn.NewValue(out, src.Location()), diags
|
return dyn.NewValue(out, src.Location()), diags
|
||||||
case dyn.KindNil:
|
case dyn.KindNil:
|
||||||
return src, diags
|
return src, diags
|
||||||
|
|
||||||
|
case dyn.KindString:
|
||||||
|
// Return verbatim if it's a pure variable reference.
|
||||||
|
if dynvar.IsPureVariableReference(src.MustString()) {
|
||||||
|
return src, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cannot interpret as a struct.
|
||||||
return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindMap, src, path))
|
return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindMap, src, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,8 +206,15 @@ func (n normalizeOptions) normalizeMap(typ reflect.Type, src dyn.Value, seen []r
|
||||||
return dyn.NewValue(out, src.Location()), diags
|
return dyn.NewValue(out, src.Location()), diags
|
||||||
case dyn.KindNil:
|
case dyn.KindNil:
|
||||||
return src, diags
|
return src, diags
|
||||||
|
|
||||||
|
case dyn.KindString:
|
||||||
|
// Return verbatim if it's a pure variable reference.
|
||||||
|
if dynvar.IsPureVariableReference(src.MustString()) {
|
||||||
|
return src, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cannot interpret as a map.
|
||||||
return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindMap, src, path))
|
return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindMap, src, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,8 +241,15 @@ func (n normalizeOptions) normalizeSlice(typ reflect.Type, src dyn.Value, seen [
|
||||||
return dyn.NewValue(out, src.Location()), diags
|
return dyn.NewValue(out, src.Location()), diags
|
||||||
case dyn.KindNil:
|
case dyn.KindNil:
|
||||||
return src, diags
|
return src, diags
|
||||||
|
|
||||||
|
case dyn.KindString:
|
||||||
|
// Return verbatim if it's a pure variable reference.
|
||||||
|
if dynvar.IsPureVariableReference(src.MustString()) {
|
||||||
|
return src, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cannot interpret as a slice.
|
||||||
return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindSequence, src, path))
|
return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindSequence, src, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,3 +394,7 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d
|
||||||
|
|
||||||
return dyn.NewValue(out, src.Location()), diags
|
return dyn.NewValue(out, src.Location()), diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n normalizeOptions) normalizeInterface(typ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) {
|
||||||
|
return src, nil
|
||||||
|
}
|
||||||
|
|
|
@ -223,6 +223,52 @@ func TestNormalizeStructIncludeMissingFieldsOnRecursiveType(t *testing.T) {
|
||||||
}), vout)
|
}), vout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizeStructVariableReference(t *testing.T) {
|
||||||
|
type Tmp struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var typ Tmp
|
||||||
|
vin := dyn.NewValue("${var.foo}", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
vout, err := Normalize(typ, vin)
|
||||||
|
assert.Empty(t, err)
|
||||||
|
assert.Equal(t, vin, vout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeStructRandomStringError(t *testing.T) {
|
||||||
|
type Tmp struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var typ Tmp
|
||||||
|
vin := dyn.NewValue("var foo", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
_, err := Normalize(typ, vin)
|
||||||
|
assert.Len(t, err, 1)
|
||||||
|
assert.Equal(t, diag.Diagnostic{
|
||||||
|
Severity: diag.Warning,
|
||||||
|
Summary: `expected map, found string`,
|
||||||
|
Location: vin.Location(),
|
||||||
|
Path: dyn.EmptyPath,
|
||||||
|
}, err[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeStructIntError(t *testing.T) {
|
||||||
|
type Tmp struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var typ Tmp
|
||||||
|
vin := dyn.NewValue(1, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
_, err := Normalize(typ, vin)
|
||||||
|
assert.Len(t, err, 1)
|
||||||
|
assert.Equal(t, diag.Diagnostic{
|
||||||
|
Severity: diag.Warning,
|
||||||
|
Summary: `expected map, found int`,
|
||||||
|
Location: vin.Location(),
|
||||||
|
Path: dyn.EmptyPath,
|
||||||
|
}, err[0])
|
||||||
|
}
|
||||||
|
|
||||||
func TestNormalizeMap(t *testing.T) {
|
func TestNormalizeMap(t *testing.T) {
|
||||||
var typ map[string]string
|
var typ map[string]string
|
||||||
vin := dyn.V(map[string]dyn.Value{
|
vin := dyn.V(map[string]dyn.Value{
|
||||||
|
@ -312,6 +358,40 @@ func TestNormalizeMapNestedError(t *testing.T) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizeMapVariableReference(t *testing.T) {
|
||||||
|
var typ map[string]string
|
||||||
|
vin := dyn.NewValue("${var.foo}", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
vout, err := Normalize(typ, vin)
|
||||||
|
assert.Empty(t, err)
|
||||||
|
assert.Equal(t, vin, vout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeMapRandomStringError(t *testing.T) {
|
||||||
|
var typ map[string]string
|
||||||
|
vin := dyn.NewValue("var foo", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
_, err := Normalize(typ, vin)
|
||||||
|
assert.Len(t, err, 1)
|
||||||
|
assert.Equal(t, diag.Diagnostic{
|
||||||
|
Severity: diag.Warning,
|
||||||
|
Summary: `expected map, found string`,
|
||||||
|
Location: vin.Location(),
|
||||||
|
Path: dyn.EmptyPath,
|
||||||
|
}, err[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeMapIntError(t *testing.T) {
|
||||||
|
var typ map[string]string
|
||||||
|
vin := dyn.NewValue(1, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
_, err := Normalize(typ, vin)
|
||||||
|
assert.Len(t, err, 1)
|
||||||
|
assert.Equal(t, diag.Diagnostic{
|
||||||
|
Severity: diag.Warning,
|
||||||
|
Summary: `expected map, found int`,
|
||||||
|
Location: vin.Location(),
|
||||||
|
Path: dyn.EmptyPath,
|
||||||
|
}, err[0])
|
||||||
|
}
|
||||||
|
|
||||||
func TestNormalizeSlice(t *testing.T) {
|
func TestNormalizeSlice(t *testing.T) {
|
||||||
var typ []string
|
var typ []string
|
||||||
vin := dyn.V([]dyn.Value{
|
vin := dyn.V([]dyn.Value{
|
||||||
|
@ -400,6 +480,40 @@ func TestNormalizeSliceNestedError(t *testing.T) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizeSliceVariableReference(t *testing.T) {
|
||||||
|
var typ []string
|
||||||
|
vin := dyn.NewValue("${var.foo}", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
vout, err := Normalize(typ, vin)
|
||||||
|
assert.Empty(t, err)
|
||||||
|
assert.Equal(t, vin, vout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeSliceRandomStringError(t *testing.T) {
|
||||||
|
var typ []string
|
||||||
|
vin := dyn.NewValue("var foo", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
_, err := Normalize(typ, vin)
|
||||||
|
assert.Len(t, err, 1)
|
||||||
|
assert.Equal(t, diag.Diagnostic{
|
||||||
|
Severity: diag.Warning,
|
||||||
|
Summary: `expected sequence, found string`,
|
||||||
|
Location: vin.Location(),
|
||||||
|
Path: dyn.EmptyPath,
|
||||||
|
}, err[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeSliceIntError(t *testing.T) {
|
||||||
|
var typ []string
|
||||||
|
vin := dyn.NewValue(1, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
_, err := Normalize(typ, vin)
|
||||||
|
assert.Len(t, err, 1)
|
||||||
|
assert.Equal(t, diag.Diagnostic{
|
||||||
|
Severity: diag.Warning,
|
||||||
|
Summary: `expected sequence, found int`,
|
||||||
|
Location: vin.Location(),
|
||||||
|
Path: dyn.EmptyPath,
|
||||||
|
}, err[0])
|
||||||
|
}
|
||||||
|
|
||||||
func TestNormalizeString(t *testing.T) {
|
func TestNormalizeString(t *testing.T) {
|
||||||
var typ string
|
var typ string
|
||||||
vin := dyn.V("string")
|
vin := dyn.V("string")
|
||||||
|
@ -725,3 +839,29 @@ func TestNormalizeAnchors(t *testing.T) {
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}, vout.AsAny())
|
}, vout.AsAny())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizeBoolToAny(t *testing.T) {
|
||||||
|
var typ any
|
||||||
|
vin := dyn.NewValue(false, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
vout, err := Normalize(&typ, vin)
|
||||||
|
assert.Len(t, err, 0)
|
||||||
|
assert.Equal(t, dyn.NewValue(false, dyn.Location{File: "file", Line: 1, Column: 1}), vout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeIntToAny(t *testing.T) {
|
||||||
|
var typ any
|
||||||
|
vin := dyn.NewValue(10, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
vout, err := Normalize(&typ, vin)
|
||||||
|
assert.Len(t, err, 0)
|
||||||
|
assert.Equal(t, dyn.NewValue(10, dyn.Location{File: "file", Line: 1, Column: 1}), vout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeSliceToAny(t *testing.T) {
|
||||||
|
var typ any
|
||||||
|
v1 := dyn.NewValue(1, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
v2 := dyn.NewValue(2, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
vin := dyn.NewValue([]dyn.Value{v1, v2}, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||||
|
vout, err := Normalize(&typ, vin)
|
||||||
|
assert.Len(t, err, 0)
|
||||||
|
assert.Equal(t, dyn.NewValue([]dyn.Value{v1, v2}, dyn.Location{File: "file", Line: 1, Column: 1}), vout)
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,8 @@ func ToTyped(dst any, src dyn.Value) error {
|
||||||
return toTypedInt(dstv, src)
|
return toTypedInt(dstv, src)
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return toTypedFloat(dstv, src)
|
return toTypedFloat(dstv, src)
|
||||||
|
case reflect.Interface:
|
||||||
|
return toTypedInterface(dstv, src)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("unsupported type: %s", dstv.Kind())
|
return fmt.Errorf("unsupported type: %s", dstv.Kind())
|
||||||
|
@ -101,6 +103,12 @@ func toTypedStruct(dst reflect.Value, src dyn.Value) error {
|
||||||
case dyn.KindNil:
|
case dyn.KindNil:
|
||||||
dst.SetZero()
|
dst.SetZero()
|
||||||
return nil
|
return nil
|
||||||
|
case dyn.KindString:
|
||||||
|
// Ignore pure variable references (e.g. ${var.foo}).
|
||||||
|
if dynvar.IsPureVariableReference(src.MustString()) {
|
||||||
|
dst.SetZero()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypeError{
|
return TypeError{
|
||||||
|
@ -132,6 +140,12 @@ func toTypedMap(dst reflect.Value, src dyn.Value) error {
|
||||||
case dyn.KindNil:
|
case dyn.KindNil:
|
||||||
dst.SetZero()
|
dst.SetZero()
|
||||||
return nil
|
return nil
|
||||||
|
case dyn.KindString:
|
||||||
|
// Ignore pure variable references (e.g. ${var.foo}).
|
||||||
|
if dynvar.IsPureVariableReference(src.MustString()) {
|
||||||
|
dst.SetZero()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypeError{
|
return TypeError{
|
||||||
|
@ -157,6 +171,12 @@ func toTypedSlice(dst reflect.Value, src dyn.Value) error {
|
||||||
case dyn.KindNil:
|
case dyn.KindNil:
|
||||||
dst.SetZero()
|
dst.SetZero()
|
||||||
return nil
|
return nil
|
||||||
|
case dyn.KindString:
|
||||||
|
// Ignore pure variable references (e.g. ${var.foo}).
|
||||||
|
if dynvar.IsPureVariableReference(src.MustString()) {
|
||||||
|
dst.SetZero()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypeError{
|
return TypeError{
|
||||||
|
@ -260,3 +280,8 @@ func toTypedFloat(dst reflect.Value, src dyn.Value) error {
|
||||||
msg: fmt.Sprintf("expected a float, found a %s", src.Kind()),
|
msg: fmt.Sprintf("expected a float, found a %s", src.Kind()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toTypedInterface(dst reflect.Value, src dyn.Value) error {
|
||||||
|
dst.Set(reflect.ValueOf(src.AsAny()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -511,3 +511,25 @@ func TestToTypedWithAliasKeyType(t *testing.T) {
|
||||||
assert.Equal(t, "bar", out["foo"])
|
assert.Equal(t, "bar", out["foo"])
|
||||||
assert.Equal(t, "baz", out["bar"])
|
assert.Equal(t, "baz", out["bar"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToTypedAnyWithBool(t *testing.T) {
|
||||||
|
var out any
|
||||||
|
err := ToTyped(&out, dyn.V(false))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, false, out)
|
||||||
|
|
||||||
|
err = ToTyped(&out, dyn.V(true))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, true, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToTypedAnyWithMap(t *testing.T) {
|
||||||
|
var out any
|
||||||
|
v := dyn.V(map[string]dyn.Value{
|
||||||
|
"foo": dyn.V("bar"),
|
||||||
|
"bar": dyn.V("baz"),
|
||||||
|
})
|
||||||
|
err := ToTyped(&out, v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, map[string]any{"foo": "bar", "bar": "baz"}, out)
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/databricks/cli/libs/dyn"
|
"github.com/databricks/cli/libs/dyn"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VariableRegex = `\$\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*)*)\}`
|
const VariableRegex = `\$\{([a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\[[0-9]+\])*)*(\[[0-9]+\])*)\}`
|
||||||
|
|
||||||
var re = regexp.MustCompile(VariableRegex)
|
var re = regexp.MustCompile(VariableRegex)
|
||||||
|
|
||||||
|
|
|
@ -247,3 +247,63 @@ func TestResolveWithInterpolateAliasedRef(t *testing.T) {
|
||||||
assert.Equal(t, "a", getByPath(t, out, "b").MustString())
|
assert.Equal(t, "a", getByPath(t, out, "b").MustString())
|
||||||
assert.Equal(t, "a", getByPath(t, out, "c").MustString())
|
assert.Equal(t, "a", getByPath(t, out, "c").MustString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveIndexedRefs(t *testing.T) {
|
||||||
|
in := dyn.V(map[string]dyn.Value{
|
||||||
|
"slice": dyn.V([]dyn.Value{dyn.V("a"), dyn.V("b")}),
|
||||||
|
"a": dyn.V("a: ${slice[0]}"),
|
||||||
|
})
|
||||||
|
|
||||||
|
out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "a: a", getByPath(t, out, "a").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveIndexedRefsFromMap(t *testing.T) {
|
||||||
|
in := dyn.V(map[string]dyn.Value{
|
||||||
|
"map": dyn.V(
|
||||||
|
map[string]dyn.Value{
|
||||||
|
"slice": dyn.V([]dyn.Value{dyn.V("a")}),
|
||||||
|
}),
|
||||||
|
"a": dyn.V("a: ${map.slice[0]}"),
|
||||||
|
})
|
||||||
|
|
||||||
|
out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "a: a", getByPath(t, out, "a").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveMapFieldFromIndexedRefs(t *testing.T) {
|
||||||
|
in := dyn.V(map[string]dyn.Value{
|
||||||
|
"map": dyn.V(
|
||||||
|
map[string]dyn.Value{
|
||||||
|
"slice": dyn.V([]dyn.Value{
|
||||||
|
dyn.V(map[string]dyn.Value{
|
||||||
|
"value": dyn.V("a"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
"a": dyn.V("a: ${map.slice[0].value}"),
|
||||||
|
})
|
||||||
|
|
||||||
|
out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "a: a", getByPath(t, out, "a").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveNestedIndexedRefs(t *testing.T) {
|
||||||
|
in := dyn.V(map[string]dyn.Value{
|
||||||
|
"slice": dyn.V([]dyn.Value{
|
||||||
|
dyn.V([]dyn.Value{dyn.V("a")}),
|
||||||
|
}),
|
||||||
|
"a": dyn.V("a: ${slice[0][0]}"),
|
||||||
|
})
|
||||||
|
|
||||||
|
out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "a: a", getByPath(t, out, "a").MustString())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue