diff --git a/bundle/direct/bundle_plan.go b/bundle/direct/bundle_plan.go index 3fab4c3f4f..e5e4123011 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -763,6 +763,17 @@ func (b *DeploymentBundle) makePlan(ctx context.Context, configRoot *config.Root pat, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { s := p.String() + // Resource keys are used as identifiers in plan paths and looked up + // via dyn.NewPathFromString, which splits on '.'. A variable reference + // in a resource key (e.g. resources.schemas.${var.schema}) cannot be + // resolved at this stage and would be split into multiple path + // components, causing a nil dereference downstream in PrepareState. + // Reject early with an actionable error. + for _, c := range p { + if dynvar.ContainsVariableReference(c.Key()) { + return v, fmt.Errorf("resource key %q cannot contain variable references; use a literal key and parameterize fields like 'name' instead", s) + } + } resourceType := config.GetResourceTypeFromKey(s) if resourceType == "" { return v, fmt.Errorf("cannot parse resource key: %q", s) diff --git a/bundle/direct/bundle_plan_test.go b/bundle/direct/bundle_plan_test.go index ccfb7cb517..08cac22363 100644 --- a/bundle/direct/bundle_plan_test.go +++ b/bundle/direct/bundle_plan_test.go @@ -3,8 +3,13 @@ package direct import ( "testing" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/direct/dresources" + "github.com/databricks/cli/bundle/direct/dstate" "github.com/databricks/cli/libs/dyn" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestDynPathToStructPath(t *testing.T) { @@ -35,3 +40,28 @@ func TestDynPathToStructPath(t *testing.T) { assert.Equal(t, tc.expected, node.String()) } } + +// TestMakePlanRejectsVariableInResourceKey verifies that a variable reference +// in a resource map key (e.g. resources.schemas.${var.schema}) is rejected +// with a clear error rather than panicking with a nil pointer dereference +// inside PrepareState. Regression test for issue #5098. +func TestMakePlanRejectsVariableInResourceKey(t *testing.T) { + rootCfg := config.Root{ + Resources: config.Resources{ + Schemas: map[string]*resources.Schema{ + "${var.schema}": {}, + }, + }, + } + require.NoError(t, rootCfg.Mutate(func(v dyn.Value) (dyn.Value, error) { return v, nil })) + + db := dstate.NewDatabase("", 0) + adapters, err := dresources.InitAll(nil) + require.NoError(t, err) + b := &DeploymentBundle{Adapters: adapters} + + _, err = b.makePlan(t.Context(), &rootCfg, &db) + require.Error(t, err) + assert.Contains(t, err.Error(), "${var.schema}") + assert.Contains(t, err.Error(), "cannot contain variable references") +}