mirror of https://github.com/databricks/cli.git
113 lines
2.9 KiB
Go
113 lines
2.9 KiB
Go
package validate
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/databricks/cli/bundle"
|
|
"github.com/databricks/cli/libs/diag"
|
|
"github.com/databricks/cli/libs/dyn"
|
|
)
|
|
|
|
// This mutator validates that:
|
|
//
|
|
// 1. Each resource key is unique across different resource types. No two resources
|
|
// of the same type can have the same key. This is because command like "bundle run"
|
|
// rely on the resource key to identify the resource to run.
|
|
// Eg: jobs.foo and pipelines.foo are not allowed simultaneously.
|
|
//
|
|
// 2. Each resource definition is contained within a single file, and is not spread
|
|
// across multiple files. Note: This is not applicable to resource configuration
|
|
// defined in a target override. That is why this mutator MUST run before the target
|
|
// overrides are merged.
|
|
func UniqueResourceKeys() bundle.Mutator {
|
|
return &uniqueResourceKeys{}
|
|
}
|
|
|
|
type uniqueResourceKeys struct{}
|
|
|
|
func (m *uniqueResourceKeys) Name() string {
|
|
return "validate:unique_resource_keys"
|
|
}
|
|
|
|
func (m *uniqueResourceKeys) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
|
diags := diag.Diagnostics{}
|
|
|
|
type metadata struct {
|
|
locations []dyn.Location
|
|
paths []dyn.Path
|
|
}
|
|
|
|
// Maps of resource key to the paths and locations the resource is defined at.
|
|
resourceMetadata := map[string]*metadata{}
|
|
|
|
rv := b.Config.Value().Get("resources")
|
|
|
|
// return early if no resources are defined or the resources block is empty.
|
|
if rv.Kind() == dyn.KindInvalid || rv.Kind() == dyn.KindNil {
|
|
return diags
|
|
}
|
|
|
|
// Gather the paths and locations of all resources.
|
|
_, err := dyn.MapByPattern(
|
|
rv,
|
|
dyn.NewPattern(dyn.AnyKey(), dyn.AnyKey()),
|
|
func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
|
// The key for the resource. Eg: "my_job" for jobs.my_job.
|
|
k := p[1].Key()
|
|
|
|
m, ok := resourceMetadata[k]
|
|
if !ok {
|
|
m = &metadata{
|
|
paths: []dyn.Path{},
|
|
locations: []dyn.Location{},
|
|
}
|
|
}
|
|
|
|
m.paths = append(m.paths, p)
|
|
m.locations = append(m.locations, v.Locations()...)
|
|
|
|
resourceMetadata[k] = m
|
|
return v, nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return diag.FromErr(err)
|
|
}
|
|
|
|
for k, v := range resourceMetadata {
|
|
if len(v.locations) <= 1 {
|
|
continue
|
|
}
|
|
|
|
// Sort the locations and paths for consistent error messages. This helps
|
|
// with unit testing.
|
|
sort.Slice(v.locations, func(i, j int) bool {
|
|
l1 := v.locations[i]
|
|
l2 := v.locations[j]
|
|
|
|
if l1.File != l2.File {
|
|
return l1.File < l2.File
|
|
}
|
|
if l1.Line != l2.Line {
|
|
return l1.Line < l2.Line
|
|
}
|
|
return l1.Column < l2.Column
|
|
})
|
|
sort.Slice(v.paths, func(i, j int) bool {
|
|
return v.paths[i].String() < v.paths[j].String()
|
|
})
|
|
|
|
// If there are multiple resources with the same key, report an error.
|
|
diags = append(diags, diag.Diagnostic{
|
|
Severity: diag.Error,
|
|
Summary: fmt.Sprintf("multiple resources have been defined with the same key: %s", k),
|
|
Locations: v.locations,
|
|
Paths: v.paths,
|
|
})
|
|
}
|
|
|
|
return diags
|
|
}
|