mirror of https://github.com/databricks/cli.git
Remove unused package bundle/deployer (#1607)
## Changes This has been superseded by individual mutators under `bundle/deploy/terraform`. ## Tests n/a
This commit is contained in:
parent
6953a5d5af
commit
2aeea5e384
|
@ -1,192 +0,0 @@
|
||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/databricks/cli/libs/locker"
|
|
||||||
"github.com/databricks/cli/libs/log"
|
|
||||||
"github.com/databricks/databricks-sdk-go"
|
|
||||||
"github.com/hashicorp/terraform-exec/tfexec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DeploymentStatus int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Empty plan produced on terraform plan. No changes need to be applied
|
|
||||||
NoChanges DeploymentStatus = iota
|
|
||||||
|
|
||||||
// Deployment failed. No databricks assets were deployed
|
|
||||||
Failed
|
|
||||||
|
|
||||||
// Deployment failed/partially succeeded. failed to update remote terraform
|
|
||||||
// state file.
|
|
||||||
// The partially deployed resources are thus untracked and in most cases
|
|
||||||
// will need to be cleaned up manually
|
|
||||||
PartialButUntracked
|
|
||||||
|
|
||||||
// Deployment failed/partially succeeded. Remote terraform state file is
|
|
||||||
// updated with any partially deployed resources
|
|
||||||
Partial
|
|
||||||
|
|
||||||
// Deployment succeeded however the remote terraform state was not updated.
|
|
||||||
// The deployed resources are thus untracked and in most cases will need to
|
|
||||||
// be cleaned up manually
|
|
||||||
CompleteButUntracked
|
|
||||||
|
|
||||||
// Deployment succeeeded with remote terraform state file updated
|
|
||||||
Complete
|
|
||||||
)
|
|
||||||
|
|
||||||
// Deployer is a struct to deploy a DAB to a databricks workspace
|
|
||||||
//
|
|
||||||
// Here's a high level description of what a deploy looks like:
|
|
||||||
//
|
|
||||||
// 1. Client compiles the bundle configuration to a terraform HCL config file
|
|
||||||
//
|
|
||||||
// 2. Client tries to acquire a lock on the remote root of the project.
|
|
||||||
// -- If FAIL: print details about current holder of the deployment lock on
|
|
||||||
// remote root and terminate deployment
|
|
||||||
//
|
|
||||||
// 3. Client reads terraform state from remote root
|
|
||||||
//
|
|
||||||
// 4. Client applies the diff in terraform config to the databricks workspace
|
|
||||||
//
|
|
||||||
// 5. Client updates terraform state file in remote root
|
|
||||||
//
|
|
||||||
// 6. Client releases the deploy lock on remote root
|
|
||||||
type Deployer struct {
|
|
||||||
localRoot string
|
|
||||||
remoteRoot string
|
|
||||||
env string
|
|
||||||
locker *locker.Locker
|
|
||||||
wsc *databricks.WorkspaceClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func Create(ctx context.Context, env, localRoot, remoteRoot string, wsc *databricks.WorkspaceClient) (*Deployer, error) {
|
|
||||||
user, err := wsc.CurrentUser.Me(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newLocker, err := locker.CreateLocker(user.UserName, remoteRoot, wsc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Deployer{
|
|
||||||
localRoot: localRoot,
|
|
||||||
remoteRoot: remoteRoot,
|
|
||||||
env: env,
|
|
||||||
locker: newLocker,
|
|
||||||
wsc: wsc,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Deployer) DefaultTerraformRoot() string {
|
|
||||||
return filepath.Join(b.localRoot, ".databricks/bundle", b.env)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Deployer) tfStateRemotePath() string {
|
|
||||||
// Note: remote paths are scoped to `remoteRoot` through the locker. Also see [Create].
|
|
||||||
return ".bundle/terraform.tfstate"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Deployer) tfStateLocalPath() string {
|
|
||||||
return filepath.Join(b.DefaultTerraformRoot(), "terraform.tfstate")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Deployer) LoadTerraformState(ctx context.Context) error {
|
|
||||||
r, err := d.locker.Read(ctx, d.tfStateRemotePath())
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
// If remote tf state is absent, use local tf state
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
err = os.MkdirAll(d.DefaultTerraformRoot(), os.ModeDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b, err := io.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile(d.tfStateLocalPath(), b, os.ModePerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Deployer) SaveTerraformState(ctx context.Context) error {
|
|
||||||
bytes, err := os.ReadFile(b.tfStateLocalPath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return b.locker.Write(ctx, b.tfStateRemotePath(), bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Deployer) Lock(ctx context.Context, isForced bool) error {
|
|
||||||
return d.locker.Lock(ctx, isForced)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Deployer) Unlock(ctx context.Context) error {
|
|
||||||
return d.locker.Unlock(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Deployer) ApplyTerraformConfig(ctx context.Context, configPath, terraformBinaryPath string, isForced bool) (DeploymentStatus, error) {
|
|
||||||
applyErr := d.Lock(ctx, isForced)
|
|
||||||
if applyErr != nil {
|
|
||||||
return Failed, applyErr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
applyErr = d.Unlock(ctx)
|
|
||||||
if applyErr != nil {
|
|
||||||
log.Errorf(ctx, "failed to unlock deployment mutex: %s", applyErr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
applyErr = d.LoadTerraformState(ctx)
|
|
||||||
if applyErr != nil {
|
|
||||||
log.Debugf(ctx, "failed to load terraform state from workspace: %s", applyErr)
|
|
||||||
return Failed, applyErr
|
|
||||||
}
|
|
||||||
|
|
||||||
tf, applyErr := tfexec.NewTerraform(configPath, terraformBinaryPath)
|
|
||||||
if applyErr != nil {
|
|
||||||
log.Debugf(ctx, "failed to construct terraform object: %s", applyErr)
|
|
||||||
return Failed, applyErr
|
|
||||||
}
|
|
||||||
|
|
||||||
isPlanNotEmpty, applyErr := tf.Plan(ctx)
|
|
||||||
if applyErr != nil {
|
|
||||||
log.Debugf(ctx, "failed to compute terraform plan: %s", applyErr)
|
|
||||||
return Failed, applyErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isPlanNotEmpty {
|
|
||||||
log.Debugf(ctx, "terraform plan returned a empty diff")
|
|
||||||
return NoChanges, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
applyErr = tf.Apply(ctx)
|
|
||||||
// upload state even if apply fails to handle partial deployments
|
|
||||||
saveStateErr := d.SaveTerraformState(ctx)
|
|
||||||
|
|
||||||
if applyErr != nil && saveStateErr != nil {
|
|
||||||
log.Errorf(ctx, "terraform apply failed: %s", applyErr)
|
|
||||||
log.Errorf(ctx, "failed to upload terraform state after partial terraform apply: %s", saveStateErr)
|
|
||||||
return PartialButUntracked, fmt.Errorf("deploymented failed: %s", applyErr)
|
|
||||||
}
|
|
||||||
if applyErr != nil {
|
|
||||||
log.Errorf(ctx, "terraform apply failed: %s", applyErr)
|
|
||||||
return Partial, fmt.Errorf("deploymented failed: %s", applyErr)
|
|
||||||
}
|
|
||||||
if saveStateErr != nil {
|
|
||||||
log.Errorf(ctx, "failed to upload terraform state after completing terraform apply: %s", saveStateErr)
|
|
||||||
return CompleteButUntracked, fmt.Errorf("failed to upload terraform state file: %s", saveStateErr)
|
|
||||||
}
|
|
||||||
return Complete, nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue