Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 214
Added bundle deployment bind and unbind command#1131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
ceb62c0952d6608075ae1c65aa6cd8fc45e852e7bc2cfee73b318a427f795ec4e8d030637ceb3b837d0b48228061962a6804071d66cbdde2022ebc47f8e3b4File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| 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. | ||
| @@ -168,3 +170,36 @@ func (r *Resources) Merge() error { | ||
| } | ||
| return nil | ||
| } | ||
| type ConfigResource interface { | ||
| Exists(ctx context.Context, w *databricks.WorkspaceClient, id string) (bool, error) | ||
| TerraformResourceName() string | ||
| } | ||
| func (r *Resources) FindResourceByConfigKey(key string) (ConfigResource, error) { | ||
| found := make([]ConfigResource, 0) | ||
| for k := range r.Jobs { | ||
| if k == key { | ||
| found = append(found, r.Jobs[k]) | ||
| } | ||
| } | ||
| for k := range r.Pipelines { | ||
| if k == key { | ||
| found = append(found, r.Pipelines[k]) | ||
| } | ||
| } | ||
| if len(found) == 0 { | ||
| return nil, fmt.Errorf("no such resource: %s", key) | ||
| } | ||
| if len(found) > 1 { | ||
| keys := make([]string, 0, len(found)) | ||
| for _, r := range found { | ||
| keys = append(keys, fmt.Sprintf("%s:%s", r.TerraformResourceName(), key)) | ||
| } | ||
| return nil, fmt.Errorf("ambiguous: %s (can resolve to all of %s)", key, keys) | ||
| } | ||
| return found[0], nil | ||
| } | ||
andrewnester marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,12 @@ | ||
| package resources | ||
| import ( | ||
| "context" | ||
| "strconv" | ||
| "github.com/databricks/cli/bundle/config/paths" | ||
| "github.com/databricks/cli/libs/log" | ||
| "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" | ||
| @@ -90,3 +95,22 @@ func (j *Job) MergeTasks() error { | ||
| j.Tasks = tasks | ||
| return nil | ||
| } | ||
| func (j *Job) Exists(ctx context.Context, w *databricks.WorkspaceClient, id string) (bool, error) { | ||
| jobId, err := strconv.Atoi(id) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
| _, err = w.Jobs.Get(ctx, jobs.GetJobRequest{ | ||
| JobId: int64(jobId), | ||
| }) | ||
andrewnester marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if err != nil { | ||
| log.Debugf(ctx, "job %s does not exist", id) | ||
| return false, err | ||
| } | ||
| return true, nil | ||
| } | ||
| func (j *Job) TerraformResourceName() string { | ||
| return "databricks_job" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,12 @@ | ||
| package resources | ||
| import ( | ||
| "context" | ||
| "strings" | ||
| "github.com/databricks/cli/bundle/config/paths" | ||
| "github.com/databricks/cli/libs/log" | ||
| "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" | ||
| @@ -73,3 +76,18 @@ func (p *Pipeline) MergeClusters() error { | ||
| p.Clusters = output | ||
| return nil | ||
| } | ||
| func (p *Pipeline) Exists(ctx context.Context, w *databricks.WorkspaceClient, id string) (bool, error) { | ||
| _, err := w.Pipelines.Get(ctx, pipelines.GetPipelineRequest{ | ||
| PipelineId: id, | ||
| }) | ||
andrewnester marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if err != nil { | ||
| log.Debugf(ctx, "pipeline %s does not exist", id) | ||
| return false, err | ||
| } | ||
| return true, nil | ||
| } | ||
| func (p *Pipeline) TerraformResourceName() string { | ||
| return "databricks_pipeline" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| package terraform | ||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
| "path/filepath" | ||
| "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 { | ||
| dir, err := Dir(ctx, b) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| 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) | ||
| } | ||
| tmpDir, err := os.MkdirTemp("", "state-*") | ||
| if err != nil { | ||
| return fmt.Errorf("terraform init: %w", err) | ||
| } | ||
| tmpState := filepath.Join(tmpDir, TerraformStateFileName) | ||
| importAddress := fmt.Sprintf("%s.%s", m.opts.ResourceType, m.opts.ResourceKey) | ||
| err = tf.Import(ctx, importAddress, m.opts.ResourceId, tfexec.StateOut(tmpState)) | ||
| if err != nil { | ||
| return fmt.Errorf("terraform import: %w", err) | ||
| } | ||
| buf := bytes.NewBuffer(nil) | ||
| tf.SetStdout(buf) | ||
| //lint:ignore SA1019 We use legacy -state flag for now to plan the import changes based on temporary state file | ||
| changed, err := tf.Plan(ctx, tfexec.State(tmpState), tfexec.Target(importAddress)) | ||
| if err != nil { | ||
| return fmt.Errorf("terraform plan: %w", err) | ||
| } | ||
| defer os.RemoveAll(tmpDir) | ||
| if changed && !m.opts.AutoApprove { | ||
| output := buf.String() | ||
| // Remove output starting from Warning until end of output | ||
| output = output[:bytes.Index([]byte(output), []byte("Warning:"))] | ||
| cmdio.LogString(ctx, output) | ||
| ans, err := cmdio.AskYesOrNo(ctx, "Confirm import changes? Changes will be remotely applied only after running 'bundle deploy'.") | ||
| if err != nil { | ||
| return err | ||
andrewnester marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| if !ans { | ||
| return fmt.Errorf("import aborted") | ||
| } | ||
| } | ||
| // If user confirmed changes, move the state file from temp dir to state location | ||
| f, err := os.Create(filepath.Join(dir, TerraformStateFileName)) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer f.Close() | ||
| tmpF, err := os.Open(tmpState) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer tmpF.Close() | ||
| _, err = io.Copy(f, tmpF) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return nil | ||
| } | ||
| // Name implements bundle.Mutator. | ||
| func (*importResource) Name() string { | ||
| return "terraform.Import" | ||
| } | ||
| func Import(opts *BindOptions) bundle.Mutator { | ||
| return &importResource{opts: opts} | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package terraform | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/databricks/cli/bundle" | ||
| "github.com/hashicorp/terraform-exec/tfexec" | ||
| ) | ||
| 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} | ||
| } |
| 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.StatePull(), | ||
| terraform.Interpolate(), | ||
| terraform.Write(), | ||
| terraform.Import(opts), | ||
andrewnester marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| 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.StatePull(), | ||
| terraform.Interpolate(), | ||
| terraform.Write(), | ||
| terraform.Unbind(resourceType, resourceKey), | ||
| terraform.StatePush(), | ||
| ), | ||
| lock.Release(lock.GoalUnbind), | ||
| ), | ||
| }, | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -14,9 +14,9 @@ func Destroy() bundle.Mutator { | ||
| lock.Acquire(), | ||
| bundle.Defer( | ||
| bundle.Seq( | ||
| terraform.StatePull(), | ||
| terraform.Interpolate(), | ||
| terraform.Write(), | ||
| terraform.StatePull(), | ||
andrewnester marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| terraform.Plan(terraform.PlanGoal("destroy")), | ||
| terraform.Destroy(), | ||
| terraform.StatePush(), | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For later; we shouldn't externalize the TF resource name as it doesn't correspond with the name in the configuration. In error messages we should refer to
jobs.job_keylike we do for the run args.