Skip to content

Commit

Permalink
Serialize dynamic value for bundle validate output (#1499)
Browse files Browse the repository at this point in the history
## Changes
Using dynamic values allows us to retain references like
`${resources.jobs...}` even when the type of field is not integer, eg:
`run_job_task`, or in general values that do not map to the Go types for
a field.

## Tests
Integration test
  • Loading branch information
shreyas-goenka authored Jun 18, 2024
1 parent 274688d commit 553fdd1
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 27 deletions.
6 changes: 6 additions & 0 deletions bundle/config/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,3 +471,9 @@ func (r Root) GetLocation(path string) dyn.Location {
}
return v.Location()
}

// Value returns the dynamic configuration value of the root object. This value
// is the source of truth and is kept in sync with values in the typed configuration.
func (r Root) Value() dyn.Value {
return r.value
}
2 changes: 1 addition & 1 deletion cmd/bundle/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func renderTextOutput(cmd *cobra.Command, b *bundle.Bundle, diags diag.Diagnosti
}

func renderJsonOutput(cmd *cobra.Command, b *bundle.Bundle, diags diag.Diagnostics) error {
buf, err := json.MarshalIndent(b.Config, "", " ")
buf, err := json.MarshalIndent(b.Config.Value().AsAny(), "", " ")
if err != nil {
return err
}
Expand Down
7 changes: 7 additions & 0 deletions internal/bundle/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ func writeConfigFile(t *testing.T, config map[string]any) (string, error) {
return filepath, err
}

func validateBundle(t *testing.T, ctx context.Context, path string) ([]byte, error) {
t.Setenv("BUNDLE_ROOT", path)
c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "validate", "--output", "json")
stdout, _, err := c.Run()
return stdout.Bytes(), err
}

func deployBundle(t *testing.T, ctx context.Context, path string) error {
t.Setenv("BUNDLE_ROOT", path)
c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--force-lock")
Expand Down
60 changes: 60 additions & 0 deletions internal/bundle/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package bundle

import (
"context"
"encoding/json"
"testing"

"github.com/databricks/cli/internal/testutil"
"github.com/databricks/cli/libs/dyn"
"github.com/databricks/cli/libs/dyn/convert"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAccBundleValidate(t *testing.T) {
testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")

tmpDir := t.TempDir()
testutil.WriteFile(t,
`
bundle:
name: "foobar"
resources:
jobs:
outer_loop:
name: outer loop
tasks:
- task_key: my task
run_job_task:
job_id: ${resources.jobs.inner_loop.id}
inner_loop:
name: inner loop
`, tmpDir, "databricks.yml")

ctx := context.Background()
stdout, err := validateBundle(t, ctx, tmpDir)
require.NoError(t, err)

config := make(map[string]any)
err = json.Unmarshal(stdout, &config)
require.NoError(t, err)

getValue := func(key string) any {
v, err := convert.FromTyped(config, dyn.NilValue)
require.NoError(t, err)
v, err = dyn.GetByPath(v, dyn.MustPathFromString(key))
require.NoError(t, err)
return v.AsAny()
}

assert.Equal(t, "foobar", getValue("bundle.name"))
assert.Equal(t, "outer loop", getValue("resources.jobs.outer_loop.name"))
assert.Equal(t, "inner loop", getValue("resources.jobs.inner_loop.name"))
assert.Equal(t, "my task", getValue("resources.jobs.outer_loop.tasks[0].task_key"))
// Assert resource references are retained in the output.
assert.Equal(t, "${resources.jobs.inner_loop.id}", getValue("resources.jobs.outer_loop.tasks[0].run_job_task.job_id"))
}
48 changes: 48 additions & 0 deletions internal/testutil/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package testutil

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func TouchNotebook(t *testing.T, elems ...string) string {
path := filepath.Join(elems...)
err := os.MkdirAll(filepath.Dir(path), 0755)
require.NoError(t, err)

err = os.WriteFile(path, []byte("# Databricks notebook source"), 0644)
require.NoError(t, err)
return path
}

func Touch(t *testing.T, elems ...string) string {
path := filepath.Join(elems...)
err := os.MkdirAll(filepath.Dir(path), 0755)
require.NoError(t, err)

f, err := os.Create(path)
require.NoError(t, err)

err = f.Close()
require.NoError(t, err)
return path
}

func WriteFile(t *testing.T, content string, elems ...string) string {
path := filepath.Join(elems...)
err := os.MkdirAll(filepath.Dir(path), 0755)
require.NoError(t, err)

f, err := os.Create(path)
require.NoError(t, err)

_, err = f.WriteString(content)
require.NoError(t, err)

err = f.Close()
require.NoError(t, err)
return path
}
26 changes: 0 additions & 26 deletions internal/testutil/touch.go

This file was deleted.

0 comments on commit 553fdd1

Please sign in to comment.