github.com/opentofu/opentofu@v1.7.1/internal/plans/planfile/writer.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package planfile 7 8 import ( 9 "archive/zip" 10 "bytes" 11 "fmt" 12 "os" 13 "time" 14 15 "github.com/opentofu/opentofu/internal/configs/configload" 16 "github.com/opentofu/opentofu/internal/depsfile" 17 "github.com/opentofu/opentofu/internal/encryption" 18 "github.com/opentofu/opentofu/internal/plans" 19 "github.com/opentofu/opentofu/internal/states/statefile" 20 ) 21 22 type CreateArgs struct { 23 // ConfigSnapshot is a snapshot of the configuration that the plan 24 // was created from. 25 ConfigSnapshot *configload.Snapshot 26 27 // PreviousRunStateFile is a representation of the state snapshot we used 28 // as the original input when creating this plan, containing the same 29 // information as recorded at the end of the previous apply except for 30 // upgrading managed resource instance data to the provider's latest 31 // schema versions. 32 PreviousRunStateFile *statefile.File 33 34 // BaseStateFile is a representation of the state snapshot we used to 35 // create the plan, which is the result of asking the providers to refresh 36 // all previously-stored objects to match the current situation in the 37 // remote system. (If this plan was created with refreshing disabled, 38 // this should be the same as PreviousRunStateFile.) 39 StateFile *statefile.File 40 41 // Plan records the plan itself, which is the main artifact inside a 42 // saved plan file. 43 Plan *plans.Plan 44 45 // DependencyLocks records the dependency lock information that we 46 // checked prior to creating the plan, so we can make sure that all of the 47 // same dependencies are still available when applying the plan. 48 DependencyLocks *depsfile.Locks 49 } 50 51 // Create creates a new plan file with the given filename, overwriting any 52 // file that might already exist there. 53 // 54 // A plan file contains both a snapshot of the configuration and of the latest 55 // state file in addition to the plan itself, so that OpenTofu can detect 56 // if the world has changed since the plan was created and thus refuse to 57 // apply it. 58 func Create(filename string, args CreateArgs, enc encryption.PlanEncryption) error { 59 buff := bytes.NewBuffer(make([]byte, 0)) 60 zw := zip.NewWriter(buff) 61 62 // tfplan file 63 { 64 w, err := zw.CreateHeader(&zip.FileHeader{ 65 Name: tfplanFilename, 66 Method: zip.Deflate, 67 Modified: time.Now(), 68 }) 69 if err != nil { 70 return fmt.Errorf("failed to create tfplan file: %w", err) 71 } 72 err = writeTfplan(args.Plan, w) 73 if err != nil { 74 return fmt.Errorf("failed to write plan: %w", err) 75 } 76 } 77 78 // tfstate file 79 { 80 w, err := zw.CreateHeader(&zip.FileHeader{ 81 Name: tfstateFilename, 82 Method: zip.Deflate, 83 Modified: time.Now(), 84 }) 85 if err != nil { 86 return fmt.Errorf("failed to create embedded tfstate file: %w", err) 87 } 88 err = statefile.Write(args.StateFile, w, encryption.StateEncryptionDisabled()) 89 if err != nil { 90 return fmt.Errorf("failed to write state snapshot: %w", err) 91 } 92 } 93 94 // tfstate-prev file 95 { 96 w, err := zw.CreateHeader(&zip.FileHeader{ 97 Name: tfstatePreviousFilename, 98 Method: zip.Deflate, 99 Modified: time.Now(), 100 }) 101 if err != nil { 102 return fmt.Errorf("failed to create embedded tfstate-prev file: %w", err) 103 } 104 err = statefile.Write(args.PreviousRunStateFile, w, encryption.StateEncryptionDisabled()) 105 if err != nil { 106 return fmt.Errorf("failed to write previous state snapshot: %w", err) 107 } 108 } 109 110 // tfconfig directory 111 { 112 err := writeConfigSnapshot(args.ConfigSnapshot, zw) 113 if err != nil { 114 return fmt.Errorf("failed to write config snapshot: %w", err) 115 } 116 } 117 118 // .terraform.lock.hcl file, containing dependency lock information 119 if args.DependencyLocks != nil { // (this was a later addition, so not all callers set it, but main callers should) 120 src, diags := depsfile.SaveLocksToBytes(args.DependencyLocks) 121 if diags.HasErrors() { 122 return fmt.Errorf("failed to write embedded dependency lock file: %w", diags.Err()) 123 } 124 125 w, err := zw.CreateHeader(&zip.FileHeader{ 126 Name: dependencyLocksFilename, 127 Method: zip.Deflate, 128 Modified: time.Now(), 129 }) 130 if err != nil { 131 return fmt.Errorf("failed to create embedded dependency lock file: %w", err) 132 } 133 _, err = w.Write(src) 134 if err != nil { 135 return fmt.Errorf("failed to write embedded dependency lock file: %w", err) 136 } 137 } 138 139 // Finish zip file 140 zw.Close() 141 // Encrypt payload 142 encrypted, err := enc.EncryptPlan(buff.Bytes()) 143 if err != nil { 144 return err 145 } 146 return os.WriteFile(filename, encrypted, 0644) 147 }