Add mutators to pull and push Terraform state (#288)

## Changes

Pull state before deploying and push state after deploying.

Note: the run command was missing mutators to initialize Terraform. This
is necessary if the cache directory is removed between running "deploy"
and "run" (which is valid now that we synchronize state).

## Tests

Manually.
This commit is contained in:
Pieter Noordhuis 2023-03-30 12:01:09 +02:00 committed by GitHub
parent cb2eb7cad6
commit 04e77102c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 130 additions and 0 deletions

View File

@ -2,8 +2,10 @@ package terraform
import (
"context"
"fmt"
"github.com/databricks/bricks/bundle"
"github.com/hashicorp/terraform-exec/tfexec"
)
type load struct{}
@ -13,6 +15,16 @@ func (l *load) Name() string {
}
func (l *load) Apply(ctx context.Context, b *bundle.Bundle) ([]bundle.Mutator, error) {
tf := b.Terraform
if tf == nil {
return nil, fmt.Errorf("terraform not initialized")
}
err := tf.Init(ctx, tfexec.Upgrade(true))
if err != nil {
return nil, fmt.Errorf("terraform init: %w", err)
}
state, err := b.Terraform.Show(ctx)
if err != nil {
return nil, err

View File

@ -0,0 +1,3 @@
package terraform
const TerraformStateFileName = "terraform.tfstate"

View File

@ -0,0 +1,62 @@
package terraform
import (
"context"
"io"
"os"
"path/filepath"
"github.com/databricks/bricks/bundle"
"github.com/databricks/bricks/libs/filer"
"github.com/databricks/bricks/libs/log"
"github.com/databricks/databricks-sdk-go/apierr"
)
type statePull struct{}
func (l *statePull) Name() string {
return "terraform:state-pull"
}
func (l *statePull) Apply(ctx context.Context, b *bundle.Bundle) ([]bundle.Mutator, error) {
f, err := filer.NewWorkspaceFilesClient(b.WorkspaceClient(), b.Config.Workspace.StatePath.Workspace)
if err != nil {
return nil, err
}
dir, err := Dir(b)
if err != nil {
return nil, err
}
// Download state file from filer to local cache directory.
log.Infof(ctx, "Opening remote state file")
remote, err := f.Read(ctx, TerraformStateFileName)
if err != nil {
// On first deploy this state file doesn't yet exist.
if apierr.IsMissing(err) {
log.Infof(ctx, "Remote state file does not exist")
return nil, nil
}
return nil, err
}
// Expect the state file to live under dir.
local, err := os.OpenFile(filepath.Join(dir, TerraformStateFileName), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return nil, err
}
// Write file to disk.
log.Infof(ctx, "Writing remote state file to local cache directory")
_, err = io.Copy(local, remote)
if err != nil {
return nil, err
}
return nil, nil
}
func StatePull() bundle.Mutator {
return &statePull{}
}

View File

@ -0,0 +1,48 @@
package terraform
import (
"context"
"os"
"path/filepath"
"github.com/databricks/bricks/bundle"
"github.com/databricks/bricks/libs/filer"
"github.com/databricks/bricks/libs/log"
)
type statePush struct{}
func (l *statePush) Name() string {
return "terraform:state-push"
}
func (l *statePush) Apply(ctx context.Context, b *bundle.Bundle) ([]bundle.Mutator, error) {
f, err := filer.NewWorkspaceFilesClient(b.WorkspaceClient(), b.Config.Workspace.StatePath.Workspace)
if err != nil {
return nil, err
}
dir, err := Dir(b)
if err != nil {
return nil, err
}
// Expect the state file to live under dir.
local, err := os.Open(filepath.Join(dir, TerraformStateFileName))
if err != nil {
return nil, err
}
// Upload state file from local cache directory to filer.
log.Infof(ctx, "Writing local state file to remote state directory")
err = f.Write(ctx, TerraformStateFileName, local, filer.CreateParentDirectories, filer.OverwriteIfExists)
if err != nil {
return nil, err
}
return nil, nil
}
func StatePush() bundle.Mutator {
return &statePush{}
}

View File

@ -18,7 +18,9 @@ func Deploy() bundle.Mutator {
artifacts.UploadAll(),
terraform.Interpolate(),
terraform.Write(),
terraform.StatePull(),
terraform.Apply(),
terraform.StatePush(),
lock.Release(),
},
)

View File

@ -25,6 +25,9 @@ var runCmd = &cobra.Command{
b := bundle.Get(cmd.Context())
err := bundle.Apply(cmd.Context(), b, []bundle.Mutator{
phases.Initialize(),
terraform.Interpolate(),
terraform.Write(),
terraform.StatePull(),
terraform.Load(),
})
if err != nil {