terraform/internal/plans/planfile/writer.go

140 lines
4.0 KiB
Go

package planfile
import (
"archive/zip"
"fmt"
"os"
"time"
"github.com/hashicorp/terraform/internal/configs/configload"
"github.com/hashicorp/terraform/internal/depsfile"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/states/statefile"
)
type CreateArgs struct {
// ConfigSnapshot is a snapshot of the configuration that the plan
// was created from.
ConfigSnapshot *configload.Snapshot
// PreviousRunStateFile is a representation of the state snapshot we used
// as the original input when creating this plan, containing the same
// information as recorded at the end of the previous apply except for
// upgrading managed resource instance data to the provider's latest
// schema versions.
PreviousRunStateFile *statefile.File
// BaseStateFile is a representation of the state snapshot we used to
// create the plan, which is the result of asking the providers to refresh
// all previously-stored objects to match the current situation in the
// remote system. (If this plan was created with refreshing disabled,
// this should be the same as PreviousRunStateFile.)
StateFile *statefile.File
// Plan records the plan itself, which is the main artifact inside a
// saved plan file.
Plan *plans.Plan
// DependencyLocks records the dependency lock information that we
// checked prior to creating the plan, so we can make sure that all of the
// same dependencies are still available when applying the plan.
DependencyLocks *depsfile.Locks
}
// Create creates a new plan file with the given filename, overwriting any
// file that might already exist there.
//
// A plan file contains both a snapshot of the configuration and of the latest
// state file in addition to the plan itself, so that Terraform can detect
// if the world has changed since the plan was created and thus refuse to
// apply it.
func Create(filename string, args CreateArgs) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
zw := zip.NewWriter(f)
defer zw.Close()
// tfplan file
{
w, err := zw.CreateHeader(&zip.FileHeader{
Name: tfplanFilename,
Method: zip.Deflate,
Modified: time.Now(),
})
if err != nil {
return fmt.Errorf("failed to create tfplan file: %s", err)
}
err = writeTfplan(args.Plan, w)
if err != nil {
return fmt.Errorf("failed to write plan: %s", err)
}
}
// tfstate file
{
w, err := zw.CreateHeader(&zip.FileHeader{
Name: tfstateFilename,
Method: zip.Deflate,
Modified: time.Now(),
})
if err != nil {
return fmt.Errorf("failed to create embedded tfstate file: %s", err)
}
err = statefile.Write(args.StateFile, w)
if err != nil {
return fmt.Errorf("failed to write state snapshot: %s", err)
}
}
// tfstate-prev file
{
w, err := zw.CreateHeader(&zip.FileHeader{
Name: tfstatePreviousFilename,
Method: zip.Deflate,
Modified: time.Now(),
})
if err != nil {
return fmt.Errorf("failed to create embedded tfstate-prev file: %s", err)
}
err = statefile.Write(args.PreviousRunStateFile, w)
if err != nil {
return fmt.Errorf("failed to write previous state snapshot: %s", err)
}
}
// tfconfig directory
{
err := writeConfigSnapshot(args.ConfigSnapshot, zw)
if err != nil {
return fmt.Errorf("failed to write config snapshot: %s", err)
}
}
// .terraform.lock.hcl file, containing dependency lock information
if args.DependencyLocks != nil { // (this was a later addition, so not all callers set it, but main callers should)
src, diags := depsfile.SaveLocksToBytes(args.DependencyLocks)
if diags.HasErrors() {
return fmt.Errorf("failed to write embedded dependency lock file: %s", diags.Err().Error())
}
w, err := zw.CreateHeader(&zip.FileHeader{
Name: dependencyLocksFilename,
Method: zip.Deflate,
Modified: time.Now(),
})
if err != nil {
return fmt.Errorf("failed to create embedded dependency lock file: %s", err)
}
_, err = w.Write(src)
if err != nil {
return fmt.Errorf("failed to write embedded dependency lock file: %s", err)
}
}
return nil
}