Skip to content

Commit e8236ca

Browse files
pieternhectorcast-db
authored andcommitted
Enable target overrides for pipeline clusters (#792)
This is a follow-up to #658 and #779 for jobs. This change applies label normalization the same way the backend does. Unit and config loading tests.
1 parent d54828f commit e8236ca

6 files changed

Lines changed: 199 additions & 17 deletions

File tree

bundle/config/resources.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,24 +131,23 @@ func (r *Resources) SetConfigFilePath(path string) {
131131
}
132132
}
133133

134-
// MergeJobClusters iterates over all jobs and merges their job clusters.
135-
// This is called after applying the target overrides.
136-
func (r *Resources) MergeJobClusters() error {
134+
// Merge iterates over all resources and merges chunks of the
135+
// resource configuration that can be merged. For example, for
136+
// jobs, this merges job cluster definitions and tasks that
137+
// use the same `job_cluster_key`, or `task_key`, respectively.
138+
func (r *Resources) Merge() error {
137139
for _, job := range r.Jobs {
138140
if err := job.MergeJobClusters(); err != nil {
139141
return err
140142
}
141-
}
142-
return nil
143-
}
144-
145-
// MergeTasks iterates over all jobs and merges their tasks.
146-
// This is called after applying the target overrides.
147-
func (r *Resources) MergeTasks() error {
148-
for _, job := range r.Jobs {
149143
if err := job.MergeTasks(); err != nil {
150144
return err
151145
}
152146
}
147+
for _, pipeline := range r.Pipelines {
148+
if err := pipeline.MergeClusters(); err != nil {
149+
return err
150+
}
151+
}
153152
return nil
154153
}

bundle/config/resources/pipeline.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package resources
22

33
import (
4+
"strings"
5+
46
"github.com/databricks/cli/bundle/config/paths"
57
"github.com/databricks/databricks-sdk-go/marshal"
68
"github.com/databricks/databricks-sdk-go/service/pipelines"
9+
"github.com/imdario/mergo"
710
)
811

912
type Pipeline struct {
@@ -22,3 +25,50 @@ func (s *Pipeline) UnmarshalJSON(b []byte) error {
2225
func (s Pipeline) MarshalJSON() ([]byte, error) {
2326
return marshal.Marshal(s)
2427
}
28+
29+
// MergeClusters merges cluster definitions with same label.
30+
// The clusters field is a slice, and as such, overrides are appended to it.
31+
// We can identify a cluster by its label, however, so we can use this label
32+
// to figure out which definitions are actually overrides and merge them.
33+
//
34+
// Note: the cluster label is optional and defaults to 'default'.
35+
// We therefore ALSO merge all clusters without a label.
36+
func (p *Pipeline) MergeClusters() error {
37+
clusters := make(map[string]*pipelines.PipelineCluster)
38+
output := make([]pipelines.PipelineCluster, 0, len(p.Clusters))
39+
40+
// Normalize cluster labels.
41+
// If empty, this defaults to "default".
42+
// To make matching case insensitive, labels are lowercased.
43+
for i := range p.Clusters {
44+
label := p.Clusters[i].Label
45+
if label == "" {
46+
label = "default"
47+
}
48+
p.Clusters[i].Label = strings.ToLower(label)
49+
}
50+
51+
// Target overrides are always appended, so we can iterate in natural order to
52+
// first find the base definition, and merge instances we encounter later.
53+
for i := range p.Clusters {
54+
label := p.Clusters[i].Label
55+
56+
// Register pipeline cluster with label if not yet seen before.
57+
ref, ok := clusters[label]
58+
if !ok {
59+
output = append(output, p.Clusters[i])
60+
clusters[label] = &output[len(output)-1]
61+
continue
62+
}
63+
64+
// Merge this instance into the reference.
65+
err := mergo.Merge(ref, &p.Clusters[i], mergo.WithOverride, mergo.WithAppendSlice)
66+
if err != nil {
67+
return err
68+
}
69+
}
70+
71+
// Overwrite resulting slice.
72+
p.Clusters = output
73+
return nil
74+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package resources
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/databricks/databricks-sdk-go/service/pipelines"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestPipelineMergeClusters(t *testing.T) {
13+
p := &Pipeline{
14+
PipelineSpec: &pipelines.PipelineSpec{
15+
Clusters: []pipelines.PipelineCluster{
16+
{
17+
NodeTypeId: "i3.xlarge",
18+
NumWorkers: 2,
19+
PolicyId: "1234",
20+
},
21+
{
22+
Label: "maintenance",
23+
NodeTypeId: "i3.2xlarge",
24+
},
25+
{
26+
NodeTypeId: "i3.2xlarge",
27+
NumWorkers: 4,
28+
},
29+
},
30+
},
31+
}
32+
33+
err := p.MergeClusters()
34+
require.NoError(t, err)
35+
36+
assert.Len(t, p.Clusters, 2)
37+
assert.Equal(t, "default", p.Clusters[0].Label)
38+
assert.Equal(t, "maintenance", p.Clusters[1].Label)
39+
40+
// The default cluster was merged with a subsequent one.
41+
pc0 := p.Clusters[0]
42+
assert.Equal(t, "i3.2xlarge", pc0.NodeTypeId)
43+
assert.Equal(t, 4, pc0.NumWorkers)
44+
assert.Equal(t, "1234", pc0.PolicyId)
45+
46+
// The maintenance cluster was left untouched.
47+
pc1 := p.Clusters[1]
48+
assert.Equal(t, "i3.2xlarge", pc1.NodeTypeId)
49+
}
50+
51+
func TestPipelineMergeClustersCaseInsensitive(t *testing.T) {
52+
p := &Pipeline{
53+
PipelineSpec: &pipelines.PipelineSpec{
54+
Clusters: []pipelines.PipelineCluster{
55+
{
56+
Label: "default",
57+
NumWorkers: 2,
58+
},
59+
{
60+
Label: "DEFAULT",
61+
NumWorkers: 4,
62+
},
63+
},
64+
},
65+
}
66+
67+
err := p.MergeClusters()
68+
require.NoError(t, err)
69+
70+
assert.Len(t, p.Clusters, 1)
71+
72+
// The default cluster was merged with a subsequent one.
73+
pc0 := p.Clusters[0]
74+
assert.Equal(t, "default", strings.ToLower(pc0.Label))
75+
assert.Equal(t, 4, pc0.NumWorkers)
76+
}

bundle/config/root.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,7 @@ func (r *Root) MergeTargetOverrides(target *Target) error {
238238
return err
239239
}
240240

241-
err = r.Resources.MergeJobClusters()
242-
if err != nil {
243-
return err
244-
}
245-
246-
err = r.Resources.MergeTasks()
241+
err = r.Resources.Merge()
247242
if err != nil {
248243
return err
249244
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
bundle:
2+
name: override_pipeline_cluster
3+
4+
workspace:
5+
host: https://acme.cloud.databricks.com/
6+
7+
resources:
8+
pipelines:
9+
foo:
10+
name: job
11+
clusters:
12+
- label: default
13+
spark_conf:
14+
foo: bar
15+
16+
targets:
17+
development:
18+
resources:
19+
pipelines:
20+
foo:
21+
clusters:
22+
- label: default
23+
node_type_id: i3.xlarge
24+
num_workers: 1
25+
26+
staging:
27+
resources:
28+
pipelines:
29+
foo:
30+
clusters:
31+
- label: default
32+
node_type_id: i3.2xlarge
33+
num_workers: 4
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package config_tests
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestOverridePipelineClusterDev(t *testing.T) {
10+
b := loadTarget(t, "./override_pipeline_cluster", "development")
11+
assert.Equal(t, "job", b.Config.Resources.Pipelines["foo"].Name)
12+
assert.Len(t, b.Config.Resources.Pipelines["foo"].Clusters, 1)
13+
14+
c := b.Config.Resources.Pipelines["foo"].Clusters[0]
15+
assert.Equal(t, map[string]string{"foo": "bar"}, c.SparkConf)
16+
assert.Equal(t, "i3.xlarge", c.NodeTypeId)
17+
assert.Equal(t, 1, c.NumWorkers)
18+
}
19+
20+
func TestOverridePipelineClusterStaging(t *testing.T) {
21+
b := loadTarget(t, "./override_pipeline_cluster", "staging")
22+
assert.Equal(t, "job", b.Config.Resources.Pipelines["foo"].Name)
23+
assert.Len(t, b.Config.Resources.Pipelines["foo"].Clusters, 1)
24+
25+
c := b.Config.Resources.Pipelines["foo"].Clusters[0]
26+
assert.Equal(t, map[string]string{"foo": "bar"}, c.SparkConf)
27+
assert.Equal(t, "i3.2xlarge", c.NodeTypeId)
28+
assert.Equal(t, 4, c.NumWorkers)
29+
}

0 commit comments

Comments
 (0)