| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| 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 | |
| } |