github.com/opentofu/opentofu@v1.7.1/internal/plans/planfile/wrapped.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 "errors" 10 "fmt" 11 12 "github.com/opentofu/opentofu/internal/cloud/cloudplan" 13 "github.com/opentofu/opentofu/internal/encryption" 14 ) 15 16 // WrappedPlanFile is a sum type that represents a saved plan, loaded from a 17 // file path passed on the command line. If the specified file was a thick local 18 // plan file, the Local field will be populated; if it was a bookmark for a 19 // remote cloud plan, the Cloud field will be populated. In both cases, the 20 // other field is expected to be nil. Finally, the outer struct is also expected 21 // to be used as a pointer, so that a nil value can represent the absence of any 22 // plan file. 23 type WrappedPlanFile struct { 24 local *Reader 25 cloud *cloudplan.SavedPlanBookmark 26 } 27 28 func (w *WrappedPlanFile) IsLocal() bool { 29 return w != nil && w.local != nil 30 } 31 32 func (w *WrappedPlanFile) IsCloud() bool { 33 return w != nil && w.cloud != nil 34 } 35 36 // Local checks whether the wrapped value is a local plan file, and returns it if available. 37 func (w *WrappedPlanFile) Local() (*Reader, bool) { 38 if w != nil && w.local != nil { 39 return w.local, true 40 } else { 41 return nil, false 42 } 43 } 44 45 // Cloud checks whether the wrapped value is a cloud plan file, and returns it if available. 46 func (w *WrappedPlanFile) Cloud() (*cloudplan.SavedPlanBookmark, bool) { 47 if w != nil && w.cloud != nil { 48 return w.cloud, true 49 } else { 50 return nil, false 51 } 52 } 53 54 // NewWrappedLocal constructs a WrappedPlanFile from an already loaded local 55 // plan file reader. Most cases should use OpenWrapped to load from disk 56 // instead. If the provided reader is nil, the returned pointer is nil. 57 func NewWrappedLocal(l *Reader) *WrappedPlanFile { 58 if l != nil { 59 return &WrappedPlanFile{local: l} 60 } else { 61 return nil 62 } 63 } 64 65 // NewWrappedCloud constructs a WrappedPlanFile from an already loaded cloud 66 // plan file. Most cases should use OpenWrapped to load from disk 67 // instead. If the provided plan file is nil, the returned pointer is nil. 68 func NewWrappedCloud(c *cloudplan.SavedPlanBookmark) *WrappedPlanFile { 69 if c != nil { 70 return &WrappedPlanFile{cloud: c} 71 } else { 72 return nil 73 } 74 } 75 76 // OpenWrapped loads a local or cloud plan file from a specified file path, or 77 // returns an error if the file doesn't seem to be a plan file of either kind. 78 // Most consumers should use this and switch behaviors based on the kind of plan 79 // they expected, rather than directly using Open. 80 func OpenWrapped(filename string, enc encryption.PlanEncryption) (*WrappedPlanFile, error) { 81 // First, try to load it as a local planfile. 82 local, localErr := Open(filename, enc) 83 if localErr == nil { 84 return &WrappedPlanFile{local: local}, nil 85 } 86 // Then, try to load it as a cloud plan. 87 cloud, cloudErr := cloudplan.LoadSavedPlanBookmark(filename) 88 if cloudErr == nil { 89 return &WrappedPlanFile{cloud: &cloud}, nil 90 } 91 // If neither worked, prioritize definitive "confirmed the format but can't 92 // use it" errors, then fall back to dumping everything we know. 93 var ulp *ErrUnusableLocalPlan 94 if errors.As(localErr, &ulp) { 95 return nil, ulp 96 } 97 98 combinedErr := fmt.Errorf("couldn't load the provided path as either a local plan file (%s) or a saved cloud plan (%s)", localErr, cloudErr) 99 return nil, combinedErr 100 }