Skip to content

Commit ed84a33

Browse files
authored
Reuse resource resolution code for the run command (#1858)
## Changes As of #1846 we have a generalized package for doing resource lookups and completion. This change updates the run command to use this instead of more specific code under `bundle/run`. ## Tests * Unit tests pass * Manually confirmed that completion and prompting works
1 parent eaea308 commit ed84a33

File tree

9 files changed

+173
-224
lines changed

9 files changed

+173
-224
lines changed

bundle/resources/completion.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import "github.com/databricks/cli/bundle"
44

55
// Completions returns the same as [References] except
66
// that every key maps directly to a single reference.
7-
func Completions(b *bundle.Bundle) map[string]Reference {
7+
func Completions(b *bundle.Bundle, filters ...Filter) map[string]Reference {
88
out := make(map[string]Reference)
9-
keyOnlyRefs, _ := References(b)
9+
keyOnlyRefs, _ := References(b, filters...)
1010
for k, refs := range keyOnlyRefs {
1111
if len(refs) != 1 {
1212
continue

bundle/resources/completion_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,29 @@ func TestCompletions_SkipDuplicates(t *testing.T) {
3030
assert.Contains(t, out, "bar")
3131
}
3232
}
33+
34+
func TestCompletions_Filter(t *testing.T) {
35+
b := &bundle.Bundle{
36+
Config: config.Root{
37+
Resources: config.Resources{
38+
Jobs: map[string]*resources.Job{
39+
"foo": {},
40+
},
41+
Pipelines: map[string]*resources.Pipeline{
42+
"bar": {},
43+
},
44+
},
45+
},
46+
}
47+
48+
includeJobs := func(ref Reference) bool {
49+
_, ok := ref.Resource.(*resources.Job)
50+
return ok
51+
}
52+
53+
// Test that this does not include the pipeline.
54+
out := Completions(b, includeJobs)
55+
if assert.Len(t, out, 1) {
56+
assert.Contains(t, out, "foo")
57+
}
58+
}

bundle/resources/lookup.go

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,64 @@ import (
1010
// Reference is a reference to a resource.
1111
// It includes the resource type description, and a reference to the resource itself.
1212
type Reference struct {
13+
// Key is the unique key of the resource, e.g. "my_job".
14+
Key string
15+
16+
// KeyWithType is the unique key of the resource, including the resource type, e.g. "jobs.my_job".
17+
KeyWithType string
18+
19+
// Description is the resource type description.
1320
Description config.ResourceDescription
14-
Resource config.ConfigResource
21+
22+
// Resource is the resource itself.
23+
Resource config.ConfigResource
1524
}
1625

1726
// Map is the core type for resource lookup and completion.
1827
type Map map[string][]Reference
1928

29+
// Filter defines the function signature for filtering resources.
30+
type Filter func(Reference) bool
31+
32+
// includeReference checks if the specified reference passes all filters.
33+
// If the list of filters is empty, the reference is always included.
34+
func includeReference(filters []Filter, ref Reference) bool {
35+
for _, filter := range filters {
36+
if !filter(ref) {
37+
return false
38+
}
39+
}
40+
return true
41+
}
42+
2043
// References returns maps of resource keys to a slice of [Reference].
2144
//
2245
// The first map is indexed by the resource key only.
2346
// The second map is indexed by the resource type name and its key.
2447
//
2548
// While the return types allows for multiple resources to share the same key,
2649
// this is confirmed not to happen in the [validate.UniqueResourceKeys] mutator.
27-
func References(b *bundle.Bundle) (Map, Map) {
50+
func References(b *bundle.Bundle, filters ...Filter) (Map, Map) {
2851
keyOnly := make(Map)
2952
keyWithType := make(Map)
3053

3154
// Collect map of resource references indexed by their keys.
3255
for _, group := range b.Config.Resources.AllResources() {
3356
for k, v := range group.Resources {
3457
ref := Reference{
58+
Key: k,
59+
KeyWithType: fmt.Sprintf("%s.%s", group.Description.PluralName, k),
3560
Description: group.Description,
3661
Resource: v,
3762
}
3863

39-
kt := fmt.Sprintf("%s.%s", group.Description.PluralName, k)
40-
keyOnly[k] = append(keyOnly[k], ref)
41-
keyWithType[kt] = append(keyWithType[kt], ref)
64+
// Skip resources that do not pass all filters.
65+
if !includeReference(filters, ref) {
66+
continue
67+
}
68+
69+
keyOnly[ref.Key] = append(keyOnly[ref.Key], ref)
70+
keyWithType[ref.KeyWithType] = append(keyWithType[ref.KeyWithType], ref)
4271
}
4372
}
4473

@@ -48,8 +77,8 @@ func References(b *bundle.Bundle) (Map, Map) {
4877
// Lookup returns the resource with the specified key.
4978
// If the key maps to more than one resource, an error is returned.
5079
// If the key does not map to any resource, an error is returned.
51-
func Lookup(b *bundle.Bundle, key string) (Reference, error) {
52-
keyOnlyRefs, keyWithTypeRefs := References(b)
80+
func Lookup(b *bundle.Bundle, key string, filters ...Filter) (Reference, error) {
81+
keyOnlyRefs, keyWithTypeRefs := References(b, filters...)
5382
refs, ok := keyOnlyRefs[key]
5483
if !ok {
5584
refs, ok = keyWithTypeRefs[key]

bundle/resources/lookup_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,32 @@ func TestLookup_Nominal(t *testing.T) {
8686
assert.Equal(t, "Foo job", out.Resource.GetName())
8787
}
8888
}
89+
90+
func TestLookup_NominalWithFilters(t *testing.T) {
91+
b := &bundle.Bundle{
92+
Config: config.Root{
93+
Resources: config.Resources{
94+
Jobs: map[string]*resources.Job{
95+
"foo": {},
96+
},
97+
Pipelines: map[string]*resources.Pipeline{
98+
"bar": {},
99+
},
100+
},
101+
},
102+
}
103+
104+
includeJobs := func(ref Reference) bool {
105+
_, ok := ref.Resource.(*resources.Job)
106+
return ok
107+
}
108+
109+
// This should succeed because the filter includes jobs.
110+
_, err := Lookup(b, "foo", includeJobs)
111+
require.NoError(t, err)
112+
113+
// This should fail because the filter excludes pipelines.
114+
_, err = Lookup(b, "bar", includeJobs)
115+
require.Error(t, err)
116+
assert.ErrorContains(t, err, `resource with key "bar" not found`)
117+
}

bundle/run/keys.go

Lines changed: 0 additions & 69 deletions
This file was deleted.

bundle/run/keys_test.go

Lines changed: 0 additions & 25 deletions
This file was deleted.

bundle/run/runner.go

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package run
33
import (
44
"context"
55
"fmt"
6-
"strings"
76

87
"github.com/databricks/cli/bundle"
8+
"github.com/databricks/cli/bundle/config/resources"
9+
refs "github.com/databricks/cli/bundle/resources"
910
"github.com/databricks/cli/bundle/run/output"
1011
)
1112

@@ -38,34 +39,24 @@ type Runner interface {
3839
argsHandler
3940
}
4041

41-
// Find locates a runner matching the specified argument.
42-
//
43-
// Its behavior is as follows:
44-
// 1. Try to find a resource with <key> identical to the argument.
45-
// 2. Try to find a resource with <type>.<key> identical to the argument.
46-
//
47-
// If an argument resolves to multiple resources, it returns an error.
48-
func Find(b *bundle.Bundle, arg string) (Runner, error) {
49-
keyOnly, keyWithType := ResourceKeys(b)
50-
if len(keyWithType) == 0 {
51-
return nil, fmt.Errorf("bundle defines no resources")
52-
}
53-
54-
runners, ok := keyOnly[arg]
55-
if !ok {
56-
runners, ok = keyWithType[arg]
57-
if !ok {
58-
return nil, fmt.Errorf("no such resource: %s", arg)
59-
}
42+
// IsRunnable returns a filter that only allows runnable resources.
43+
func IsRunnable(ref refs.Reference) bool {
44+
switch ref.Resource.(type) {
45+
case *resources.Job, *resources.Pipeline:
46+
return true
47+
default:
48+
return false
6049
}
50+
}
6151

62-
if len(runners) != 1 {
63-
var keys []string
64-
for _, runner := range runners {
65-
keys = append(keys, runner.Key())
66-
}
67-
return nil, fmt.Errorf("ambiguous: %s (can resolve to all of %s)", arg, strings.Join(keys, ", "))
52+
// ToRunner converts a resource reference to a runnable resource.
53+
func ToRunner(b *bundle.Bundle, ref refs.Reference) (Runner, error) {
54+
switch resource := ref.Resource.(type) {
55+
case *resources.Job:
56+
return &jobRunner{key: key(ref.KeyWithType), bundle: b, job: resource}, nil
57+
case *resources.Pipeline:
58+
return &pipelineRunner{key: key(ref.KeyWithType), bundle: b, pipeline: resource}, nil
59+
default:
60+
return nil, fmt.Errorf("unsupported resource type: %T", resource)
6861
}
69-
70-
return runners[0], nil
7162
}

0 commit comments

Comments
 (0)