| 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 |
| } |