mirror of https://github.com/databricks/cli.git
377 lines
11 KiB
Go
377 lines
11 KiB
Go
package loader
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/databricks/cli/bundle"
|
|
"github.com/databricks/cli/bundle/config"
|
|
"github.com/databricks/cli/bundle/config/resources"
|
|
"github.com/databricks/cli/bundle/internal/bundletest"
|
|
"github.com/databricks/cli/libs/diag"
|
|
"github.com/databricks/cli/libs/dyn"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestProcessInclude(t *testing.T) {
|
|
b := &bundle.Bundle{
|
|
RootPath: "testdata/basic",
|
|
Config: config.Root{
|
|
Workspace: config.Workspace{
|
|
Host: "foo",
|
|
},
|
|
},
|
|
}
|
|
|
|
m := ProcessInclude(filepath.Join(b.RootPath, "host.yml"), "host.yml")
|
|
assert.Equal(t, "ProcessInclude(host.yml)", m.Name())
|
|
|
|
// Assert the host value prior to applying the mutator
|
|
assert.Equal(t, "foo", b.Config.Workspace.Host)
|
|
|
|
// Apply the mutator and assert that the host value has been updated
|
|
diags := bundle.Apply(context.Background(), b, m)
|
|
require.NoError(t, diags.Error())
|
|
assert.Equal(t, "bar", b.Config.Workspace.Host)
|
|
}
|
|
|
|
func TestProcessIncludeValidatesFileFormat(t *testing.T) {
|
|
b := &bundle.Bundle{
|
|
RootPath: "testdata/format",
|
|
Config: config.Root{
|
|
Bundle: config.Bundle{
|
|
Name: "format_test",
|
|
},
|
|
},
|
|
}
|
|
|
|
m := ProcessInclude(filepath.Join(b.RootPath, "foo.job.yml"), "foo.job.yml")
|
|
diags := bundle.Apply(context.Background(), b, m)
|
|
require.NoError(t, diags.Error())
|
|
|
|
// Assert that the diagnostics contain the expected information
|
|
assert.Len(t, diags, 1)
|
|
assert.Equal(t, diag.Diagnostics{
|
|
{
|
|
Severity: diag.Info,
|
|
Summary: "We recommend only defining a single job in a file with the .job.yml extension.\nThe following resources are defined or configured in this file:\n - bar (job)\n - foo (job)\n",
|
|
Locations: []dyn.Location{
|
|
{File: "testdata/format/foo.job.yml", Line: 4, Column: 7},
|
|
{File: "testdata/format/foo.job.yml", Line: 7, Column: 7},
|
|
},
|
|
Paths: []dyn.Path{
|
|
dyn.MustPathFromString("resources.jobs.bar"),
|
|
dyn.MustPathFromString("resources.jobs.foo"),
|
|
},
|
|
},
|
|
}, diags)
|
|
}
|
|
|
|
func TestResourceNames(t *testing.T) {
|
|
names := []string{}
|
|
typ := reflect.TypeOf(config.Resources{})
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
field := typ.Field(i)
|
|
jsonTags := strings.Split(field.Tag.Get("json"), ",")
|
|
singularName := strings.TrimSuffix(jsonTags[0], "s")
|
|
names = append(names, singularName)
|
|
}
|
|
|
|
// Assert the contents of the two lists are equal. Please add the singular
|
|
// name of your resource to resourceNames global if you are adding a new
|
|
// resource.
|
|
assert.Equal(t, len(resourceTypes), len(names))
|
|
for _, name := range names {
|
|
assert.Contains(t, resourceTypes, name)
|
|
}
|
|
}
|
|
|
|
func TestValidateFileFormat(t *testing.T) {
|
|
onlyJob := config.Root{
|
|
Resources: config.Resources{
|
|
Jobs: map[string]*resources.Job{
|
|
"job1": {},
|
|
},
|
|
},
|
|
Targets: map[string]*config.Target{
|
|
"target1": {
|
|
Resources: &config.Resources{
|
|
Jobs: map[string]*resources.Job{
|
|
"job1": {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
onlyJobBundle := bundle.Bundle{Config: onlyJob}
|
|
|
|
onlyPipeline := config.Root{
|
|
Resources: config.Resources{
|
|
Pipelines: map[string]*resources.Pipeline{
|
|
"pipeline1": {},
|
|
},
|
|
},
|
|
}
|
|
onlyPipelineBundle := bundle.Bundle{Config: onlyPipeline}
|
|
|
|
bothJobAndPipeline := config.Root{
|
|
Resources: config.Resources{
|
|
Jobs: map[string]*resources.Job{
|
|
"job1": {},
|
|
},
|
|
},
|
|
Targets: map[string]*config.Target{
|
|
"target1": {
|
|
Resources: &config.Resources{
|
|
Pipelines: map[string]*resources.Pipeline{
|
|
"pipeline1": {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
bothJobAndPipelineBundle := bundle.Bundle{Config: bothJobAndPipeline}
|
|
|
|
twoJobs := config.Root{
|
|
Resources: config.Resources{
|
|
Jobs: map[string]*resources.Job{
|
|
"job1": {},
|
|
"job2": {},
|
|
},
|
|
},
|
|
}
|
|
twoJobsBundle := bundle.Bundle{Config: twoJobs}
|
|
|
|
twoJobsTopLevelAndTarget := config.Root{
|
|
Resources: config.Resources{
|
|
Jobs: map[string]*resources.Job{
|
|
"job1": {},
|
|
},
|
|
},
|
|
Targets: map[string]*config.Target{
|
|
"target1": {
|
|
Resources: &config.Resources{
|
|
Jobs: map[string]*resources.Job{
|
|
"job2": {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
twoJobsTopLevelAndTargetBundle := bundle.Bundle{Config: twoJobsTopLevelAndTarget}
|
|
|
|
twoJobsInTarget := config.Root{
|
|
Targets: map[string]*config.Target{
|
|
"target1": {
|
|
Resources: &config.Resources{
|
|
Jobs: map[string]*resources.Job{
|
|
"job1": {},
|
|
"job2": {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
twoJobsInTargetBundle := bundle.Bundle{Config: twoJobsInTarget}
|
|
|
|
tcases := []struct {
|
|
name string
|
|
bundle *bundle.Bundle
|
|
expected diag.Diagnostics
|
|
fileName string
|
|
locations map[string]dyn.Location
|
|
}{
|
|
{
|
|
name: "single job",
|
|
bundle: &onlyJobBundle,
|
|
expected: nil,
|
|
fileName: "foo.job.yml",
|
|
locations: map[string]dyn.Location{
|
|
"resources.jobs.job1": {File: "foo.job.yml", Line: 1, Column: 1},
|
|
},
|
|
},
|
|
{
|
|
name: "single pipeline",
|
|
bundle: &onlyPipelineBundle,
|
|
expected: nil,
|
|
fileName: "foo.pipeline.yml",
|
|
locations: map[string]dyn.Location{
|
|
"resources.pipelines.pipeline1": {File: "foo.pipeline.yaml", Line: 1, Column: 1},
|
|
},
|
|
},
|
|
{
|
|
name: "single job but extension is pipeline",
|
|
bundle: &onlyJobBundle,
|
|
expected: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Info,
|
|
Summary: "We recommend only defining a single pipeline in a file with the .pipeline.yml extension.\nThe following resources are defined or configured in this file:\n - job1 (job)\n",
|
|
Locations: []dyn.Location{
|
|
{File: "foo.pipeline.yml", Line: 1, Column: 1},
|
|
{File: "foo.pipeline.yml", Line: 2, Column: 2},
|
|
},
|
|
Paths: []dyn.Path{
|
|
dyn.MustPathFromString("resources.jobs.job1"),
|
|
dyn.MustPathFromString("targets.target1.resources.jobs.job1"),
|
|
},
|
|
},
|
|
},
|
|
fileName: "foo.pipeline.yml",
|
|
locations: map[string]dyn.Location{
|
|
"resources.jobs.job1": {File: "foo.pipeline.yml", Line: 1, Column: 1},
|
|
"targets.target1.resources.jobs.job1": {File: "foo.pipeline.yml", Line: 2, Column: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "job and pipeline",
|
|
bundle: &bothJobAndPipelineBundle,
|
|
expected: nil,
|
|
fileName: "foo.yml",
|
|
locations: map[string]dyn.Location{
|
|
"resources.jobs.job1": {File: "foo.yml", Line: 1, Column: 1},
|
|
"targets.target1.resources.pipelines.pipeline1": {File: "foo.yml", Line: 2, Column: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "job and pipeline but extension is job",
|
|
bundle: &bothJobAndPipelineBundle,
|
|
expected: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Info,
|
|
Summary: "We recommend only defining a single job in a file with the .job.yml extension.\nThe following resources are defined or configured in this file:\n - job1 (job)\n - pipeline1 (pipeline)\n",
|
|
Locations: []dyn.Location{
|
|
{File: "foo.job.yml", Line: 1, Column: 1},
|
|
{File: "foo.job.yml", Line: 2, Column: 2},
|
|
},
|
|
Paths: []dyn.Path{
|
|
dyn.MustPathFromString("resources.jobs.job1"),
|
|
dyn.MustPathFromString("targets.target1.resources.pipelines.pipeline1"),
|
|
},
|
|
},
|
|
},
|
|
fileName: "foo.job.yml",
|
|
locations: map[string]dyn.Location{
|
|
"resources.jobs.job1": {File: "foo.job.yml", Line: 1, Column: 1},
|
|
"targets.target1.resources.pipelines.pipeline1": {File: "foo.job.yml", Line: 2, Column: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "job and pipeline but extension is experiment",
|
|
bundle: &bothJobAndPipelineBundle,
|
|
expected: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Info,
|
|
Summary: "We recommend only defining a single experiment in a file with the .experiment.yml extension.\nThe following resources are defined or configured in this file:\n - job1 (job)\n - pipeline1 (pipeline)\n",
|
|
Locations: []dyn.Location{
|
|
{File: "foo.experiment.yml", Line: 1, Column: 1},
|
|
{File: "foo.experiment.yml", Line: 2, Column: 2},
|
|
},
|
|
Paths: []dyn.Path{
|
|
dyn.MustPathFromString("resources.jobs.job1"),
|
|
dyn.MustPathFromString("targets.target1.resources.pipelines.pipeline1"),
|
|
},
|
|
},
|
|
},
|
|
fileName: "foo.experiment.yml",
|
|
locations: map[string]dyn.Location{
|
|
"resources.jobs.job1": {File: "foo.experiment.yml", Line: 1, Column: 1},
|
|
"targets.target1.resources.pipelines.pipeline1": {File: "foo.experiment.yml", Line: 2, Column: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "two jobs",
|
|
bundle: &twoJobsBundle,
|
|
expected: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Info,
|
|
Summary: "We recommend only defining a single job in a file with the .job.yml extension.\nThe following resources are defined or configured in this file:\n - job1 (job)\n - job2 (job)\n",
|
|
Locations: []dyn.Location{
|
|
{File: "foo.job.yml", Line: 1, Column: 1},
|
|
{File: "foo.job.yml", Line: 2, Column: 2},
|
|
},
|
|
Paths: []dyn.Path{
|
|
dyn.MustPathFromString("resources.jobs.job1"),
|
|
dyn.MustPathFromString("resources.jobs.job2"),
|
|
},
|
|
},
|
|
},
|
|
fileName: "foo.job.yml",
|
|
locations: map[string]dyn.Location{
|
|
"resources.jobs.job1": {File: "foo.job.yml", Line: 1, Column: 1},
|
|
"resources.jobs.job2": {File: "foo.job.yml", Line: 2, Column: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "two jobs but extension is simple yaml",
|
|
bundle: &twoJobsBundle,
|
|
expected: nil,
|
|
fileName: "foo.yml",
|
|
locations: map[string]dyn.Location{
|
|
"resources.jobs.job1": {File: "foo.yml", Line: 1, Column: 1},
|
|
"resources.jobs.job2": {File: "foo.yml", Line: 2, Column: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "two jobs in top level and target",
|
|
bundle: &twoJobsTopLevelAndTargetBundle,
|
|
expected: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Info,
|
|
Summary: "We recommend only defining a single job in a file with the .job.yml extension.\nThe following resources are defined or configured in this file:\n - job1 (job)\n - job2 (job)\n",
|
|
Locations: []dyn.Location{
|
|
{File: "foo.job.yml", Line: 1, Column: 1},
|
|
{File: "foo.job.yml", Line: 2, Column: 2},
|
|
},
|
|
Paths: []dyn.Path{
|
|
dyn.MustPathFromString("resources.jobs.job1"),
|
|
dyn.MustPathFromString("targets.target1.resources.jobs.job2"),
|
|
},
|
|
},
|
|
},
|
|
fileName: "foo.job.yml",
|
|
locations: map[string]dyn.Location{
|
|
"resources.jobs.job1": {File: "foo.job.yml", Line: 1, Column: 1},
|
|
"targets.target1.resources.jobs.job2": {File: "foo.job.yml", Line: 2, Column: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "two jobs in target",
|
|
bundle: &twoJobsInTargetBundle,
|
|
expected: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Info,
|
|
Summary: "We recommend only defining a single job in a file with the .job.yml extension.\nThe following resources are defined or configured in this file:\n - job1 (job)\n - job2 (job)\n",
|
|
Locations: []dyn.Location{
|
|
{File: "foo.job.yml", Line: 1, Column: 1},
|
|
{File: "foo.job.yml", Line: 2, Column: 2},
|
|
},
|
|
Paths: []dyn.Path{
|
|
dyn.MustPathFromString(("targets.target1.resources.jobs.job1")),
|
|
dyn.MustPathFromString("targets.target1.resources.jobs.job2"),
|
|
},
|
|
},
|
|
},
|
|
fileName: "foo.job.yml",
|
|
locations: map[string]dyn.Location{
|
|
"targets.target1.resources.jobs.job1": {File: "foo.job.yml", Line: 1, Column: 1},
|
|
"targets.target1.resources.jobs.job2": {File: "foo.job.yml", Line: 2, Column: 2},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
for k, v := range tc.locations {
|
|
bundletest.SetLocation(tc.bundle, k, []dyn.Location{v})
|
|
}
|
|
|
|
diags := validateFileFormat(&tc.bundle.Config, tc.fileName)
|
|
assert.Equal(t, tc.expected, diags)
|
|
})
|
|
}
|
|
}
|