Skip to content

Commit b327a7f

Browse files
authored
config-remote-sync: skip stale --select-ids selectors instead of failing the batch (#5980)
## Changes `bundle config-remote-sync --select-ids <type:id>,...` is how DABs in the Workspace syncs edited resources — the UI batches every edited resource into one call. Previously, if any one selector matched no deployed resource in state, the command errored with `no deployed <type> resource with id <id>` and the whole batch failed, dropping the valid resources' edits too. A selector that matches no deployed resource (deleted remotely, or deploy-state drift) is now skipped — a resource with no deployed state has no remote change to pull back into config. A malformed selector (bad `<type>:<id>` shape) is still an error. ## Why A single stale resource id was causing every sync to fail for the affected workspace, so no UI edit could be written back. ## Tests Extended the `select_basic` acceptance test: a batch with one stale selector still syncs the valid resource; an all-stale sync is a clean no-op; a malformed selector is still rejected.
1 parent 7f23bd2 commit b327a7f

4 files changed

Lines changed: 71 additions & 17 deletions

File tree

acceptance/bundle/config-remote-sync/select_basic/output.txt

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,45 @@ Resource: resources.jobs.job_two
3737

3838

3939

40-
=== An unknown resource id is rejected
40+
=== A stale selector is skipped, not fatal: job_two still syncs alongside it
41+
Detected changes in 1 resource(s):
42+
43+
Resource: resources.jobs.job_two
44+
max_concurrent_runs: replace
45+
46+
47+
48+
>>> diff.py databricks.yml.backup databricks.yml
49+
--- databricks.yml.backup
50+
+++ databricks.yml
51+
@@ -16,5 +16,5 @@
52+
53+
job_two:
54+
- max_concurrent_runs: 2
55+
+ max_concurrent_runs: 10
56+
tasks:
57+
- task_key: main
58+
59+
=== An unknown resource id alone is rejected (no match at all must not silently succeed)
60+
4161
>>> [CLI] bundle config-remote-sync --select-ids jobs:no-such-id-123
4262
Error: no deployed jobs resource with id no-such-id-123
4363

4464
Exit code: 1
4565

46-
=== A selector without a type is rejected
47-
>>> [CLI] bundle config-remote-sync --select-ids no-such-id-123
48-
Error: invalid --select-ids value "no-such-id-123", expected <type>:<id> (e.g. jobs:[NUMID])
49-
50-
Exit code: 1
66+
=== An id that exists under a different type matches nothing and is rejected too
5167

52-
=== An id that exists under a different type is rejected (no cross-type collision)
5368
>>> [CLI] bundle config-remote-sync --select-ids pipelines:[JOB_ONE_ID]
5469
Error: no deployed pipelines resource with id [JOB_ONE_ID]
5570

5671
Exit code: 1
5772

73+
=== A selector without a type is still rejected
74+
>>> [CLI] bundle config-remote-sync --select-ids no-such-id-123
75+
Error: invalid --select-ids value "no-such-id-123", expected <type>:<id> (e.g. jobs:[NUMID])
76+
77+
Exit code: 1
78+
5879
>>> [CLI] bundle destroy --auto-approve
5980
The following resources will be deleted:
6081
delete resources.jobs.job_one

acceptance/bundle/config-remote-sync/select_basic/script

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,20 @@ title "Unfiltered sync still detects the job_two drift (no lost updates)"
3838
echo
3939
$CLI bundle config-remote-sync
4040

41-
title "An unknown resource id is rejected"
42-
errcode trace $CLI bundle config-remote-sync --select-ids jobs:no-such-id-123
41+
title "A stale selector is skipped, not fatal: job_two still syncs alongside it"
42+
echo
43+
cp databricks.yml databricks.yml.backup
44+
$CLI bundle config-remote-sync --select-ids "jobs:no-such-id-123,jobs:$job_two_id" --save
45+
trace diff.py databricks.yml.backup databricks.yml
46+
rm databricks.yml.backup
4347

44-
title "A selector without a type is rejected"
45-
errcode trace $CLI bundle config-remote-sync --select-ids no-such-id-123
48+
title "An unknown resource id alone is rejected (no match at all must not silently succeed)"
49+
echo
50+
errcode trace $CLI bundle config-remote-sync --select-ids jobs:no-such-id-123
4651

47-
title "An id that exists under a different type is rejected (no cross-type collision)"
52+
title "An id that exists under a different type matches nothing and is rejected too"
53+
echo
4854
errcode trace $CLI bundle config-remote-sync --select-ids "pipelines:$job_one_id"
55+
56+
title "A selector without a type is still rejected"
57+
errcode trace $CLI bundle config-remote-sync --select-ids no-such-id-123

bundle/configsync/select.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package configsync
22

33
import (
4+
"context"
45
"fmt"
56
"slices"
67
"strings"
78

89
"github.com/databricks/cli/bundle/direct/dstate"
10+
"github.com/databricks/cli/libs/log"
911
)
1012

1113
// ResolveResourceSelectors maps "<type>:<id>" selectors to their plan keys
@@ -21,11 +23,18 @@ import (
2123
// is also why selection is independent from `bundle deploy --select`, which
2224
// matches "type.name" keys.
2325
//
24-
// A selector that matches no deployed resource is an error: only deployed
25-
// resources have an id, so a selector matching nothing is a caller mistake.
26+
// A selector that matches no deployed resource is skipped rather than failing
27+
// the run — but only when at least one other selector did match. The workspace
28+
// UI batches every edited resource into one sync, so a single stale selector
29+
// (a resource deleted remotely, or whose deploy state has drifted) must not
30+
// drop the valid resources' edits. When NO selector matches, the error is
31+
// returned instead: silently reporting "no changes" would let the UI show a
32+
// success while nothing synced, hiding a real state problem. A malformed
33+
// selector (missing "<type>:<id>" shape) is always an error, because that is a
34+
// caller mistake rather than drift.
2635
// Duplicate selectors are deduplicated; the returned keys preserve the order in
2736
// which their selectors first appear.
28-
func ResolveResourceSelectors(state *dstate.DeploymentState, selectors []string) ([]string, error) {
37+
func ResolveResourceSelectors(ctx context.Context, state *dstate.DeploymentState, selectors []string) ([]string, error) {
2938
// Index deployed resources by "<type>:<id>". State keys have the form
3039
// "resources.<type>.<name>"; indexing by the <type> component means a
3140
// selector can only ever match a resource of that exact type, never an id
@@ -48,19 +57,34 @@ func ResolveResourceSelectors(state *dstate.DeploymentState, selectors []string)
4857
}
4958

5059
keys := make([]string, 0, len(selectors))
60+
var missing []string
5161
for _, selector := range selectors {
5262
resourceType, id, ok := strings.Cut(selector, ":")
5363
if !ok || resourceType == "" || id == "" {
5464
return nil, fmt.Errorf("invalid --select-ids value %q, expected <type>:<id> (e.g. jobs:123456789)", selector)
5565
}
5666
key, ok := byTypeID[selector]
5767
if !ok {
58-
return nil, fmt.Errorf("no deployed %s resource with id %s", resourceType, id)
68+
missing = append(missing, selector)
69+
continue
5970
}
6071
if !slices.Contains(keys, key) {
6172
keys = append(keys, key)
6273
}
6374
}
75+
76+
// No selector matched a deployed resource: fail loudly instead of syncing
77+
// nothing, so the caller does not report a spurious success.
78+
if len(keys) == 0 {
79+
resourceType, id, _ := strings.Cut(missing[0], ":")
80+
return nil, fmt.Errorf("no deployed %s resource with id %s", resourceType, id)
81+
}
82+
83+
// Some selectors matched: skip the stale ones so the matched resources still
84+
// sync (the UI batches several resources into one run).
85+
for _, selector := range missing {
86+
log.Debugf(ctx, "config-remote-sync: skipping selector %q, no deployed resource with that id", selector)
87+
}
6488
return keys, nil
6589
}
6690

cmd/bundle/config_remote_sync.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ Examples:
104104
// Filter after planning, never before: the plan must cover every
105105
// resource so ${resources.*} references resolve; only the emitted
106106
// changes are restricted to the selected resources.
107-
selected, err := configsync.ResolveResourceSelectors(&deployBundle.StateDB, selectIDs)
107+
selected, err := configsync.ResolveResourceSelectors(ctx, &deployBundle.StateDB, selectIDs)
108108
if err != nil {
109109
return err
110110
}

0 commit comments

Comments
 (0)