Skip to content

Commit e043d91

Browse files
authored
Fix yaml config-remote-sync snapshot creation when bundle is deployed without any resources (#4889)
## Changes UploadStateForYamlSync now handles the case where the terraform state file doesn't exist. When ParseResourcesState returns nil (no state), the mutator skips snapshot creation and returns successfully. Errors in snapshot creation/upload are now warnings instead of fatal errors. ## Why Bug: First deploy of an empty bundle with yaml sync enabled and terraform engine failed with open .../terraform.tfstate: no such file or directory. Terraform skips apply when there are no resources, so the state file is never created. The mutator tried to read it unconditionally. ## Tests <!-- How have you tested the changes? --> Added acceptance/bundle/config-remote-sync/empty_deploy — deploys an empty bundle with yaml sync + terraform engine. <!-- If your PR needs to be included in the release notes for next release, add a separate entry in NEXT_CHANGELOG.md as part of your PR. -->
1 parent bcf3c04 commit e043d91

File tree

3 files changed

+39
-24
lines changed

3 files changed

+39
-24
lines changed

acceptance/bundle/deploy/empty-bundle/out.test.toml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
Cloud=true
1+
Cloud = true
2+
3+
[EnvMatrix]
4+
DATABRICKS_BUNDLE_ENABLE_EXPERIMENTAL_YAML_SYNC = ["", "true"]

bundle/statemgmt/upload_state_for_yaml_sync.go

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,23 @@ func (m *uploadStateForYamlSync) Apply(ctx context.Context, b *bundle.Bundle) di
5656

5757
_, snapshotPath := b.StateFilenameConfigSnapshot(ctx)
5858

59-
diags := m.convertState(ctx, b, snapshotPath)
60-
if diags.HasError() {
61-
return diags
59+
created, err := m.convertState(ctx, b, snapshotPath)
60+
if err != nil {
61+
log.Warnf(ctx, "Failed to create config snapshot: %v", err)
62+
return nil
63+
}
64+
if !created {
65+
return nil
6266
}
6367

64-
err := uploadState(ctx, b)
68+
err = uploadState(ctx, b)
6569
if err != nil {
66-
return diags.Extend(diag.Warningf("Failed to upload config snapshot to workspace: %v", err))
70+
log.Warnf(ctx, "Failed to upload config snapshot: %v", err)
71+
return nil
6772
}
6873

6974
log.Infof(ctx, "Config snapshot created at %s", snapshotPath)
70-
return diags
75+
return nil
7176
}
7277

7378
func uploadState(ctx context.Context, b *bundle.Bundle) error {
@@ -94,10 +99,22 @@ func uploadState(ctx context.Context, b *bundle.Bundle) error {
9499
return nil
95100
}
96101

97-
func (m *uploadStateForYamlSync) convertState(ctx context.Context, b *bundle.Bundle, snapshotPath string) (diags diag.Diagnostics) {
102+
func (m *uploadStateForYamlSync) convertState(ctx context.Context, b *bundle.Bundle, snapshotPath string) (bool, error) {
98103
terraformResources, err := terraform.ParseResourcesState(ctx, b)
99104
if err != nil {
100-
return diag.FromErr(err)
105+
return false, fmt.Errorf("failed to parse terraform state: %w", err)
106+
}
107+
108+
// ParseResourcesState returns nil when the terraform state file doesn't exist
109+
// (e.g. first deploy with no resources).
110+
if terraformResources == nil {
111+
return false, nil
112+
}
113+
114+
_, localTerraformPath := b.StateFilenameTerraform(ctx)
115+
data, err := os.ReadFile(localTerraformPath)
116+
if err != nil {
117+
return false, fmt.Errorf("failed to read terraform state: %w", err)
101118
}
102119

103120
state := make(map[string]dstate.ResourceEntry)
@@ -113,18 +130,12 @@ func (m *uploadStateForYamlSync) convertState(ctx context.Context, b *bundle.Bun
113130
}
114131
}
115132

116-
_, localTerraformPath := b.StateFilenameTerraform(ctx)
117-
data, err := os.ReadFile(localTerraformPath)
118-
if err != nil {
119-
return diag.FromErr(err)
120-
}
121-
122133
var tfState struct {
123134
Lineage string `json:"lineage"`
124135
Serial int `json:"serial"`
125136
}
126137
if err := json.Unmarshal(data, &tfState); err != nil {
127-
return diag.FromErr(err)
138+
return false, err
128139
}
129140

130141
migratedDB := dstate.NewDatabase(tfState.Lineage, tfState.Serial+1)
@@ -142,29 +153,29 @@ func (m *uploadStateForYamlSync) convertState(ctx context.Context, b *bundle.Bun
142153
// the migrated state and config agree on .permissions entries.
143154
bundle.ApplyContext(ctx, b, resourcemutator.SecretScopeFixups(engine.EngineDirect))
144155
if logdiag.HasError(ctx) {
145-
return diag.Errorf("failed to apply secret scope fixups")
156+
return false, errors.New("failed to apply secret scope fixups")
146157
}
147158

148-
// Get the dynamic value from b.Config and reverse the interpolation
159+
// Get the dynamic value from b.Config and reverse the interpolation.
149160
// b.Config has been modified by terraform.Interpolate which converts bundle-style
150-
// references (${resources.pipelines.x.id}) to terraform-style (${databricks_pipeline.x.id})
161+
// references (${resources.pipelines.x.id}) to terraform-style (${databricks_pipeline.x.id}).
151162
interpolatedRoot := b.Config.Value()
152163
uninterpolatedRoot, err := reverseInterpolate(interpolatedRoot)
153164
if err != nil {
154-
return diag.FromErr(fmt.Errorf("failed to reverse interpolation: %w", err))
165+
return false, fmt.Errorf("failed to reverse interpolation: %w", err)
155166
}
156167

157168
var uninterpolatedConfig config.Root
158169
err = uninterpolatedConfig.Mutate(func(_ dyn.Value) (dyn.Value, error) {
159170
return uninterpolatedRoot, nil
160171
})
161172
if err != nil {
162-
return diag.FromErr(fmt.Errorf("failed to create uninterpolated config: %w", err))
173+
return false, fmt.Errorf("failed to create uninterpolated config: %w", err)
163174
}
164175

165176
plan, err := deploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(), &uninterpolatedConfig, snapshotPath)
166177
if err != nil {
167-
return diag.FromErr(err)
178+
return false, err
168179
}
169180

170181
for _, entry := range plan.Plan {
@@ -182,13 +193,13 @@ func (m *uploadStateForYamlSync) convertState(ctx context.Context, b *bundle.Bun
182193
}
183194
err := structaccess.Set(sv.Value, structpath.NewStringKey(nil, "etag"), etag)
184195
if err != nil {
185-
diags = diags.Extend(diag.Warningf("Failed to set etag on %q: %v", key, err))
196+
log.Warnf(ctx, "Failed to set etag on %q: %v", key, err)
186197
}
187198
}
188199

189200
deploymentBundle.Apply(ctx, b.WorkspaceClient(), plan, direct.MigrateMode(true))
190201

191-
return diags
202+
return true, nil
192203
}
193204

194205
// reverseInterpolate reverses the terraform.Interpolate transformation.

0 commit comments

Comments
 (0)