github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/plans/planfile/reader.go (about) 1 package planfile 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "fmt" 7 "io/ioutil" 8 9 "github.com/hashicorp/terraform/internal/configs" 10 "github.com/hashicorp/terraform/internal/configs/configload" 11 "github.com/hashicorp/terraform/internal/depsfile" 12 "github.com/hashicorp/terraform/internal/plans" 13 "github.com/hashicorp/terraform/internal/states/statefile" 14 "github.com/hashicorp/terraform/internal/tfdiags" 15 ) 16 17 const tfstateFilename = "tfstate" 18 const tfstatePreviousFilename = "tfstate-prev" 19 const dependencyLocksFilename = ".terraform.lock.hcl" // matches the conventional name in an input configuration 20 21 // Reader is the main type used to read plan files. Create a Reader by calling 22 // Open. 23 // 24 // A plan file is a random-access file format, so methods of Reader must 25 // be used to access the individual portions of the file for further 26 // processing. 27 type Reader struct { 28 zip *zip.ReadCloser 29 } 30 31 // Open creates a Reader for the file at the given filename, or returns an 32 // error if the file doesn't seem to be a planfile. 33 func Open(filename string) (*Reader, error) { 34 r, err := zip.OpenReader(filename) 35 if err != nil { 36 // To give a better error message, we'll sniff to see if this looks 37 // like our old plan format from versions prior to 0.12. 38 if b, sErr := ioutil.ReadFile(filename); sErr == nil { 39 if bytes.HasPrefix(b, []byte("tfplan")) { 40 return nil, fmt.Errorf("the given plan file was created by an earlier version of Terraform; plan files cannot be shared between different Terraform versions") 41 } 42 } 43 return nil, err 44 } 45 46 // Sniff to make sure this looks like a plan file, as opposed to any other 47 // random zip file the user might have around. 48 var planFile *zip.File 49 for _, file := range r.File { 50 if file.Name == tfplanFilename { 51 planFile = file 52 break 53 } 54 } 55 if planFile == nil { 56 return nil, fmt.Errorf("the given file is not a valid plan file") 57 } 58 59 // For now, we'll just accept the presence of the tfplan file as enough, 60 // and wait to validate the version when the caller requests the plan 61 // itself. 62 63 return &Reader{ 64 zip: r, 65 }, nil 66 } 67 68 // ReadPlan reads the plan embedded in the plan file. 69 // 70 // Errors can be returned for various reasons, including if the plan file 71 // is not of an appropriate format version, if it was created by a different 72 // version of Terraform, if it is invalid, etc. 73 func (r *Reader) ReadPlan() (*plans.Plan, error) { 74 var planFile *zip.File 75 for _, file := range r.zip.File { 76 if file.Name == tfplanFilename { 77 planFile = file 78 break 79 } 80 } 81 if planFile == nil { 82 // This should never happen because we checked for this file during 83 // Open, but we'll check anyway to be safe. 84 return nil, fmt.Errorf("the plan file is invalid") 85 } 86 87 pr, err := planFile.Open() 88 if err != nil { 89 return nil, fmt.Errorf("failed to retrieve plan from plan file: %s", err) 90 } 91 defer pr.Close() 92 93 // There's a slight mismatch in how plans.Plan is modeled vs. how 94 // the underlying plan file format works, because the "tfplan" embedded 95 // file contains only some top-level metadata and the planned changes, 96 // and not the previous run or prior states. Therefore we need to 97 // build this up in multiple steps. 98 // This is some technical debt because historically we considered the 99 // planned changes and prior state as totally separate, but later realized 100 // that it made sense for a plans.Plan to include the prior state directly 101 // so we can see what state the plan applies to. Hopefully later we'll 102 // clean this up some more so that we don't have two different ways to 103 // access the prior state (this and the ReadStateFile method). 104 ret, err := readTfplan(pr) 105 if err != nil { 106 return nil, err 107 } 108 109 prevRunStateFile, err := r.ReadPrevStateFile() 110 if err != nil { 111 return nil, fmt.Errorf("failed to read previous run state from plan file: %s", err) 112 } 113 priorStateFile, err := r.ReadStateFile() 114 if err != nil { 115 return nil, fmt.Errorf("failed to read prior state from plan file: %s", err) 116 } 117 118 ret.PrevRunState = prevRunStateFile.State 119 ret.PriorState = priorStateFile.State 120 121 return ret, nil 122 } 123 124 // ReadStateFile reads the state file embedded in the plan file, which 125 // represents the "PriorState" as defined in plans.Plan. 126 // 127 // If the plan file contains no embedded state file, the returned error is 128 // statefile.ErrNoState. 129 func (r *Reader) ReadStateFile() (*statefile.File, error) { 130 for _, file := range r.zip.File { 131 if file.Name == tfstateFilename { 132 r, err := file.Open() 133 if err != nil { 134 return nil, fmt.Errorf("failed to extract state from plan file: %s", err) 135 } 136 return statefile.Read(r) 137 } 138 } 139 return nil, statefile.ErrNoState 140 } 141 142 // ReadPrevStateFile reads the previous state file embedded in the plan file, which 143 // represents the "PrevRunState" as defined in plans.Plan. 144 // 145 // If the plan file contains no embedded previous state file, the returned error is 146 // statefile.ErrNoState. 147 func (r *Reader) ReadPrevStateFile() (*statefile.File, error) { 148 for _, file := range r.zip.File { 149 if file.Name == tfstatePreviousFilename { 150 r, err := file.Open() 151 if err != nil { 152 return nil, fmt.Errorf("failed to extract previous state from plan file: %s", err) 153 } 154 return statefile.Read(r) 155 } 156 } 157 return nil, statefile.ErrNoState 158 } 159 160 // ReadConfigSnapshot reads the configuration snapshot embedded in the plan 161 // file. 162 // 163 // This is a lower-level alternative to ReadConfig that just extracts the 164 // source files, without attempting to parse them. 165 func (r *Reader) ReadConfigSnapshot() (*configload.Snapshot, error) { 166 return readConfigSnapshot(&r.zip.Reader) 167 } 168 169 // ReadConfig reads the configuration embedded in the plan file. 170 // 171 // Internally this function delegates to the configs/configload package to 172 // parse the embedded configuration and so it returns diagnostics (rather than 173 // a native Go error as with other methods on Reader). 174 func (r *Reader) ReadConfig() (*configs.Config, tfdiags.Diagnostics) { 175 var diags tfdiags.Diagnostics 176 177 snap, err := r.ReadConfigSnapshot() 178 if err != nil { 179 diags = diags.Append(tfdiags.Sourceless( 180 tfdiags.Error, 181 "Failed to read configuration from plan file", 182 fmt.Sprintf("The configuration file snapshot in the plan file could not be read: %s.", err), 183 )) 184 return nil, diags 185 } 186 187 loader := configload.NewLoaderFromSnapshot(snap) 188 rootDir := snap.Modules[""].Dir // Root module base directory 189 config, configDiags := loader.LoadConfig(rootDir) 190 diags = diags.Append(configDiags) 191 192 return config, diags 193 } 194 195 // ReadDependencyLocks reads the dependency lock information embedded in 196 // the plan file. 197 // 198 // Some test codepaths create plan files without dependency lock information, 199 // but all main codepaths should populate this. If reading a file without 200 // the dependency information, this will return error diagnostics. 201 func (r *Reader) ReadDependencyLocks() (*depsfile.Locks, tfdiags.Diagnostics) { 202 var diags tfdiags.Diagnostics 203 204 for _, file := range r.zip.File { 205 if file.Name == dependencyLocksFilename { 206 r, err := file.Open() 207 if err != nil { 208 diags = diags.Append(tfdiags.Sourceless( 209 tfdiags.Error, 210 "Failed to read dependency locks from plan file", 211 fmt.Sprintf("Couldn't read the dependency lock information embedded in the plan file: %s.", err), 212 )) 213 return nil, diags 214 } 215 src, err := ioutil.ReadAll(r) 216 if err != nil { 217 diags = diags.Append(tfdiags.Sourceless( 218 tfdiags.Error, 219 "Failed to read dependency locks from plan file", 220 fmt.Sprintf("Couldn't read the dependency lock information embedded in the plan file: %s.", err), 221 )) 222 return nil, diags 223 } 224 locks, moreDiags := depsfile.LoadLocksFromBytes(src, "<saved-plan>") 225 diags = diags.Append(moreDiags) 226 return locks, diags 227 } 228 } 229 230 // If we fall out here then this is a file without dependency information. 231 diags = diags.Append(tfdiags.Sourceless( 232 tfdiags.Error, 233 "Saved plan has no dependency lock information", 234 "The specified saved plan file does not include any dependency lock information. This is a bug in the previous run of Terraform that created this file.", 235 )) 236 return nil, diags 237 } 238 239 // Close closes the file, after which no other operations may be performed. 240 func (r *Reader) Close() error { 241 return r.zip.Close() 242 }