2018-06-20 01:30:59 +02:00
|
|
|
package planfile
|
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/zip"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
2021-05-17 21:17:09 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/configs/configload"
|
2021-09-30 02:03:38 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/depsfile"
|
2021-05-17 21:33:17 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/plans"
|
2021-05-17 21:43:35 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/states/statefile"
|
2018-06-20 01:30:59 +02:00
|
|
|
)
|
|
|
|
|
2021-09-30 01:03:10 +02:00
|
|
|
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
|
2021-09-30 02:03:38 +02:00
|
|
|
|
|
|
|
// 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
|
2021-09-30 01:03:10 +02:00
|
|
|
}
|
|
|
|
|
2018-06-20 01:30:59 +02:00
|
|
|
// 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.
|
2021-09-30 01:03:10 +02:00
|
|
|
func Create(filename string, args CreateArgs) error {
|
2018-06-20 01:30:59 +02:00
|
|
|
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)
|
|
|
|
}
|
2021-09-30 01:03:10 +02:00
|
|
|
err = writeTfplan(args.Plan, w)
|
2018-06-20 01:30:59 +02:00
|
|
|
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)
|
|
|
|
}
|
2021-09-30 01:03:10 +02:00
|
|
|
err = statefile.Write(args.StateFile, w)
|
2018-06-20 01:30:59 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to write state snapshot: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 00:59:58 +02:00
|
|
|
// 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)
|
|
|
|
}
|
2021-09-30 01:03:10 +02:00
|
|
|
err = statefile.Write(args.PreviousRunStateFile, w)
|
2021-05-05 00:59:58 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to write previous state snapshot: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-20 01:30:59 +02:00
|
|
|
// tfconfig directory
|
|
|
|
{
|
2021-09-30 01:03:10 +02:00
|
|
|
err := writeConfigSnapshot(args.ConfigSnapshot, zw)
|
2018-06-20 01:30:59 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to write config snapshot: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-30 02:03:38 +02:00
|
|
|
// .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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-20 01:30:59 +02:00
|
|
|
return nil
|
|
|
|
}
|