github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/command/state_meta.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "sort" 6 "time" 7 8 "github.com/iaas-resource-provision/iaas-rpc/internal/addrs" 9 "github.com/iaas-resource-provision/iaas-rpc/internal/states" 10 "github.com/iaas-resource-provision/iaas-rpc/internal/states/statemgr" 11 "github.com/iaas-resource-provision/iaas-rpc/internal/tfdiags" 12 13 backendLocal "github.com/iaas-resource-provision/iaas-rpc/internal/backend/local" 14 ) 15 16 // StateMeta is the meta struct that should be embedded in state subcommands. 17 type StateMeta struct { 18 Meta 19 } 20 21 // State returns the state for this meta. This gets the appropriate state from 22 // the backend, but changes the way that backups are done. This configures 23 // backups to be timestamped rather than just the original state path plus a 24 // backup path. 25 func (c *StateMeta) State() (statemgr.Full, error) { 26 var realState statemgr.Full 27 backupPath := c.backupPath 28 stateOutPath := c.statePath 29 30 // use the specified state 31 if c.statePath != "" { 32 realState = statemgr.NewFilesystem(c.statePath) 33 } else { 34 // Load the backend 35 b, backendDiags := c.Backend(nil) 36 if backendDiags.HasErrors() { 37 return nil, backendDiags.Err() 38 } 39 40 workspace, err := c.Workspace() 41 if err != nil { 42 return nil, err 43 } 44 45 // Check remote Terraform version is compatible 46 remoteVersionDiags := c.remoteBackendVersionCheck(b, workspace) 47 c.showDiagnostics(remoteVersionDiags) 48 if remoteVersionDiags.HasErrors() { 49 return nil, fmt.Errorf("Error checking remote Terraform version") 50 } 51 52 // Get the state 53 s, err := b.StateMgr(workspace) 54 if err != nil { 55 return nil, err 56 } 57 58 // Get a local backend 59 localRaw, backendDiags := c.Backend(&BackendOpts{ForceLocal: true}) 60 if backendDiags.HasErrors() { 61 // This should never fail 62 panic(backendDiags.Err()) 63 } 64 localB := localRaw.(*backendLocal.Local) 65 _, stateOutPath, _ = localB.StatePaths(workspace) 66 if err != nil { 67 return nil, err 68 } 69 70 realState = s 71 } 72 73 // We always backup state commands, so set the back if none was specified 74 // (the default is "-", but some tests bypass the flag parsing). 75 if backupPath == "-" || backupPath == "" { 76 // Determine the backup path. stateOutPath is set to the resulting 77 // file where state is written (cached in the case of remote state) 78 backupPath = fmt.Sprintf( 79 "%s.%d%s", 80 stateOutPath, 81 time.Now().UTC().Unix(), 82 DefaultBackupExtension) 83 } 84 85 // If the backend is local (which it should always be, given our asserting 86 // of it above) we can now enable backups for it. 87 if lb, ok := realState.(*statemgr.Filesystem); ok { 88 lb.SetBackupPath(backupPath) 89 } 90 91 return realState, nil 92 } 93 94 func (c *StateMeta) lookupResourceInstanceAddr(state *states.State, allowMissing bool, addrStr string) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) { 95 target, diags := addrs.ParseTargetStr(addrStr) 96 if diags.HasErrors() { 97 return nil, diags 98 } 99 100 targetAddr := target.Subject 101 var ret []addrs.AbsResourceInstance 102 switch addr := targetAddr.(type) { 103 case addrs.ModuleInstance: 104 // Matches all instances within the indicated module and all of its 105 // descendent modules. 106 107 // found is used to identify cases where the selected module has no 108 // resources, but one or more of its submodules does. 109 found := false 110 ms := state.Module(addr) 111 if ms != nil { 112 found = true 113 ret = append(ret, c.collectModuleResourceInstances(ms)...) 114 } 115 for _, cms := range state.Modules { 116 if !addr.Equal(cms.Addr) { 117 if addr.IsAncestor(cms.Addr) || addr.TargetContains(cms.Addr) { 118 found = true 119 ret = append(ret, c.collectModuleResourceInstances(cms)...) 120 } 121 } 122 } 123 124 if found == false && !allowMissing { 125 diags = diags.Append(tfdiags.Sourceless( 126 tfdiags.Error, 127 "Unknown module", 128 fmt.Sprintf(`The current state contains no module at %s. If you've just added this module to the configuration, you must run "terraform apply" first to create the module's entry in the state.`, addr), 129 )) 130 } 131 132 case addrs.AbsResource: 133 // Matches all instances of the specific selected resource. 134 rs := state.Resource(addr) 135 if rs == nil { 136 if !allowMissing { 137 diags = diags.Append(tfdiags.Sourceless( 138 tfdiags.Error, 139 "Unknown resource", 140 fmt.Sprintf(`The current state contains no resource %s. If you've just added this resource to the configuration, you must run "terraform apply" first to create the resource's entry in the state.`, addr), 141 )) 142 } 143 break 144 } 145 ret = append(ret, c.collectResourceInstances(addr.Module, rs)...) 146 case addrs.AbsResourceInstance: 147 is := state.ResourceInstance(addr) 148 if is == nil { 149 if !allowMissing { 150 diags = diags.Append(tfdiags.Sourceless( 151 tfdiags.Error, 152 "Unknown resource instance", 153 fmt.Sprintf(`The current state contains no resource instance %s. If you've just added its resource to the configuration or have changed the count or for_each arguments, you must run "terraform apply" first to update the resource's entry in the state.`, addr), 154 )) 155 } 156 break 157 } 158 ret = append(ret, addr) 159 } 160 sort.Slice(ret, func(i, j int) bool { 161 return ret[i].Less(ret[j]) 162 }) 163 164 return ret, diags 165 } 166 167 func (c *StateMeta) lookupSingleStateObjectAddr(state *states.State, addrStr string) (addrs.Targetable, tfdiags.Diagnostics) { 168 target, diags := addrs.ParseTargetStr(addrStr) 169 if diags.HasErrors() { 170 return nil, diags 171 } 172 return target.Subject, diags 173 } 174 175 func (c *StateMeta) lookupResourceInstanceAddrs(state *states.State, addrStrs ...string) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) { 176 var ret []addrs.AbsResourceInstance 177 var diags tfdiags.Diagnostics 178 for _, addrStr := range addrStrs { 179 moreAddrs, moreDiags := c.lookupResourceInstanceAddr(state, false, addrStr) 180 ret = append(ret, moreAddrs...) 181 diags = diags.Append(moreDiags) 182 } 183 return ret, diags 184 } 185 186 func (c *StateMeta) lookupAllResourceInstanceAddrs(state *states.State) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) { 187 var ret []addrs.AbsResourceInstance 188 var diags tfdiags.Diagnostics 189 for _, ms := range state.Modules { 190 ret = append(ret, c.collectModuleResourceInstances(ms)...) 191 } 192 sort.Slice(ret, func(i, j int) bool { 193 return ret[i].Less(ret[j]) 194 }) 195 return ret, diags 196 } 197 198 func (c *StateMeta) collectModuleResourceInstances(ms *states.Module) []addrs.AbsResourceInstance { 199 var ret []addrs.AbsResourceInstance 200 for _, rs := range ms.Resources { 201 ret = append(ret, c.collectResourceInstances(ms.Addr, rs)...) 202 } 203 return ret 204 } 205 206 func (c *StateMeta) collectResourceInstances(moduleAddr addrs.ModuleInstance, rs *states.Resource) []addrs.AbsResourceInstance { 207 var ret []addrs.AbsResourceInstance 208 for key := range rs.Instances { 209 ret = append(ret, rs.Addr.Instance(key)) 210 } 211 return ret 212 } 213 214 func (c *StateMeta) lookupAllResources(state *states.State) ([]*states.Resource, tfdiags.Diagnostics) { 215 var ret []*states.Resource 216 var diags tfdiags.Diagnostics 217 for _, ms := range state.Modules { 218 for _, resource := range ms.Resources { 219 ret = append(ret, resource) 220 } 221 } 222 return ret, diags 223 }