Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions bundle/config/resources.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package config

import (
"context"
"fmt"

"github.com/databricks/cli/bundle/config/resources"
"github.com/databricks/databricks-sdk-go"
)

// Resources defines Databricks resources associated with the bundle.
Expand Down Expand Up @@ -168,3 +170,23 @@ func (r *Resources) Merge() error {
}
return nil
}

type ConfigResource interface {
Exists(ctx context.Context, w *databricks.WorkspaceClient, id string) bool
Comment thread
andrewnester marked this conversation as resolved.
Outdated
Type() string
}

func (r *Resources) FindResourceByConfigKey(key string) (ConfigResource, error) {
for k := range r.Jobs {
if k == key {
return r.Jobs[k], nil
}
}
for k := range r.Pipelines {
if k == key {
return r.Pipelines[k], nil
}
}

return nil, fmt.Errorf("no such resource: %s", key)
}
Comment thread
andrewnester marked this conversation as resolved.
19 changes: 19 additions & 0 deletions bundle/config/resources/job.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package resources

import (
"context"
"strconv"

"github.com/databricks/cli/bundle/config/paths"
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/marshal"
"github.com/databricks/databricks-sdk-go/service/jobs"
"github.com/imdario/mergo"
Expand Down Expand Up @@ -89,3 +93,18 @@ func (j *Job) MergeTasks() error {
j.Tasks = tasks
return nil
}

func (j *Job) Exists(ctx context.Context, w *databricks.WorkspaceClient, id string) bool {
jobId, err := strconv.Atoi(id)
if err != nil {
return false
}
_, err = w.Jobs.Get(ctx, jobs.GetJobRequest{
JobId: int64(jobId),
})
Comment thread
andrewnester marked this conversation as resolved.
return err == nil
}

func (j *Job) Type() string {
Comment thread
andrewnester marked this conversation as resolved.
Outdated
return "databricks_job"
}
13 changes: 13 additions & 0 deletions bundle/config/resources/pipeline.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package resources

import (
"context"
"strings"

"github.com/databricks/cli/bundle/config/paths"
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/marshal"
"github.com/databricks/databricks-sdk-go/service/pipelines"
"github.com/imdario/mergo"
Expand Down Expand Up @@ -72,3 +74,14 @@ func (p *Pipeline) MergeClusters() error {
p.Clusters = output
return nil
}

func (p *Pipeline) Exists(ctx context.Context, w *databricks.WorkspaceClient, id string) bool {
_, err := w.Pipelines.Get(ctx, pipelines.GetPipelineRequest{
PipelineId: id,
})
Comment thread
andrewnester marked this conversation as resolved.
return err == nil
}

func (p *Pipeline) Type() string {
return "databricks_pipeline"
}
3 changes: 3 additions & 0 deletions bundle/deploy/lock/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
type Goal string

const (
GoalBind = Goal("bind")
GoalDeploy = Goal("deploy")
GoalDestroy = Goal("destroy")
)
Expand Down Expand Up @@ -46,6 +47,8 @@ func (m *release) Apply(ctx context.Context, b *bundle.Bundle) error {
switch m.goal {
case GoalDeploy:
return b.Locker.Unlock(ctx)
case GoalBind:
return b.Locker.Unlock(ctx)
case GoalDestroy:
return b.Locker.Unlock(ctx, locker.AllowLockFileNotExist)
default:
Expand Down
105 changes: 105 additions & 0 deletions bundle/deploy/terraform/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package terraform

import (
"bytes"
"context"
"fmt"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/cmdio"
"github.com/hashicorp/terraform-exec/tfexec"
)

type BindOptions struct {
AutoApprove bool
ResourceType string
ResourceKey string
ResourceId string
}

type importResource struct {
opts *BindOptions
}

// Apply implements bundle.Mutator.
func (m *importResource) Apply(ctx context.Context, b *bundle.Bundle) error {
tf := b.Terraform
if tf == nil {
return fmt.Errorf("terraform not initialized")
}

err := tf.Init(ctx, tfexec.Upgrade(true))
if err != nil {
return fmt.Errorf("terraform init: %w", err)
}

err = tf.Import(ctx, fmt.Sprintf("%s.%s", m.opts.ResourceType, m.opts.ResourceKey), m.opts.ResourceId)
if err != nil {
return fmt.Errorf("terraform import: %w", err)
}

buf := bytes.NewBuffer(nil)
tf.SetStdout(buf)
changed, err := tf.Plan(ctx)
Comment thread
andrewnester marked this conversation as resolved.
Outdated
Comment thread
andrewnester marked this conversation as resolved.
Outdated
if err != nil {
return fmt.Errorf("terraform plan: %w", err)
}

if changed && !m.opts.AutoApprove {
cmdio.LogString(ctx, buf.String())
ans, err := cmdio.AskYesOrNo(ctx, "Confirm import changes? Changes will be remotely only after running 'bundle deploy'.")
Comment thread
andrewnester marked this conversation as resolved.
Outdated
Comment thread
andrewnester marked this conversation as resolved.
Outdated
if err != nil {
return err
Comment thread
andrewnester marked this conversation as resolved.
}
if !ans {
err = tf.StateRm(ctx, fmt.Sprintf("%s.%s", m.opts.ResourceType, m.opts.ResourceKey))
Comment thread
andrewnester marked this conversation as resolved.
Outdated
if err != nil {
return err
}
return fmt.Errorf("import aborted")
}
}

return nil
}

// Name implements bundle.Mutator.
func (*importResource) Name() string {
return "terraform.Import"
}

func Import(opts *BindOptions) bundle.Mutator {
return &importResource{opts: opts}
}

type unbind struct {
resourceType string
resourceKey string
}

func (m *unbind) Apply(ctx context.Context, b *bundle.Bundle) error {
tf := b.Terraform
if tf == nil {
return fmt.Errorf("terraform not initialized")
}

err := tf.Init(ctx, tfexec.Upgrade(true))
if err != nil {
return fmt.Errorf("terraform init: %w", err)
}

err = tf.StateRm(ctx, fmt.Sprintf("%s.%s", m.resourceType, m.resourceKey))
if err != nil {
return fmt.Errorf("terraform state rm: %w", err)
}

return nil
}

func (*unbind) Name() string {
return "terraform.Unbind"
}

func Unbind(resourceType string, resourceKey string) bundle.Mutator {
return &unbind{resourceType: resourceType, resourceKey: resourceKey}
}
45 changes: 45 additions & 0 deletions bundle/phases/bind.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package phases

import (
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/deploy/lock"
"github.com/databricks/cli/bundle/deploy/terraform"
)

func Bind(opts *terraform.BindOptions) bundle.Mutator {
return newPhase(
"bind",
[]bundle.Mutator{
lock.Acquire(),
bundle.Defer(
bundle.Seq(
terraform.Interpolate(),
terraform.Write(),
terraform.StatePull(),
terraform.Import(opts),
Comment thread
andrewnester marked this conversation as resolved.
terraform.StatePush(),
),
lock.Release(lock.GoalBind),
),
},
)
}

func Unbind(resourceType string, resourceKey string) bundle.Mutator {
return newPhase(
"unbind",
[]bundle.Mutator{
lock.Acquire(),
bundle.Defer(
bundle.Seq(
terraform.Interpolate(),
terraform.Write(),
terraform.StatePull(),
terraform.Unbind(resourceType, resourceKey),
terraform.StatePush(),
),
lock.Release(lock.GoalBind),
Comment thread
andrewnester marked this conversation as resolved.
Outdated
),
},
)
}
2 changes: 2 additions & 0 deletions cmd/bundle/bundle.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bundle

import (
"github.com/databricks/cli/cmd/bundle/deployment"
"github.com/spf13/cobra"
)

Expand All @@ -23,5 +24,6 @@ func New() *cobra.Command {
cmd.AddCommand(newValidateCommand())
cmd.AddCommand(newInitCommand())
cmd.AddCommand(newGenerateCommand())
cmd.AddCommand(deployment.NewDeploymentCommand())
return cmd
}
3 changes: 2 additions & 1 deletion cmd/bundle/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package bundle
import (
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/phases"
"github.com/databricks/cli/cmd/bundle/utils"
"github.com/spf13/cobra"
)

func newDeployCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "deploy",
Short: "Deploy bundle",
PreRunE: ConfigureBundleWithVariables,
PreRunE: utils.ConfigureBundleWithVariables,
}

var force bool
Expand Down
63 changes: 63 additions & 0 deletions cmd/bundle/deployment/bind.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package deployment

import (
"fmt"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/deploy/terraform"
"github.com/databricks/cli/bundle/phases"
"github.com/databricks/cli/cmd/bundle/utils"
"github.com/databricks/cli/libs/cmdio"
"github.com/spf13/cobra"
)

func newBindCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "bind KEY RESOURCE_ID",
Short: "Bind bundle-defined resources to existing resources",
Args: cobra.ExactArgs(2),
PreRunE: utils.ConfigureBundleWithVariables,
}

var autoApprove bool
var forceLock bool
cmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Automatically approve the binding")
cmd.Flags().BoolVar(&forceLock, "force-lock", false, "Force acquisition of deployment lock.")

cmd.RunE = func(cmd *cobra.Command, args []string) error {
b := bundle.Get(cmd.Context())
r := b.Config.Resources
resource, err := r.FindResourceByConfigKey(args[0])
if err != nil {
return err
}

w := b.WorkspaceClient()
ctx := cmd.Context()
if !resource.Exists(ctx, w, args[1]) {
return fmt.Errorf("%s with an id '%s' is not found", resource.Type(), args[1])
}

if !autoApprove {
answer, err := cmdio.AskYesOrNo(ctx, "Binding to existing resource means that the resource will be managed by the bundle which can lead to changes in the resource. Do you want to continue?")
Comment thread
andrewnester marked this conversation as resolved.
Outdated
if err != nil {
return err
}
if !answer {
return nil
}
}

b.Config.Bundle.Lock.Force = forceLock
return bundle.Apply(cmd.Context(), b, bundle.Seq(
phases.Initialize(),
phases.Bind(&terraform.BindOptions{
ResourceType: resource.Type(),
Comment thread
andrewnester marked this conversation as resolved.
Outdated
ResourceKey: args[0],
ResourceId: args[1],
}),
))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this succeeds we should display a message (if in text output mode) to confirm it did.

This can also include a call to action to hint at running a deploy next.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juliacrawf-db Could you shine your light on how to best convey this?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no! I missed this ping! That string looks great. But wouldn't we want a similar error message in unbind.go if the unbinding fails?

}

return cmd
}
17 changes: 17 additions & 0 deletions cmd/bundle/deployment/deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package deployment

import (
"github.com/spf13/cobra"
)

func NewDeploymentCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "deployment",
Short: "Deployment related commands",
Long: "Deployment related commands",
}

cmd.AddCommand(newBindCommand())
cmd.AddCommand(newUnbindCommand())
return cmd
}
Loading