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