github.com/vic3lord/terraform@v0.8.0-rc1.0.20170626102919-16c6dd2cb372/command/state_mv.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/terraform/terraform" 8 "github.com/mitchellh/cli" 9 ) 10 11 // StateMvCommand is a Command implementation that shows a single resource. 12 type StateMvCommand struct { 13 Meta 14 StateMeta 15 } 16 17 func (c *StateMvCommand) Run(args []string) int { 18 args = c.Meta.process(args, true) 19 20 // We create two metas to track the two states 21 var meta1, meta2 Meta 22 cmdFlags := c.Meta.flagSet("state mv") 23 cmdFlags.StringVar(&meta1.backupPath, "backup", "-", "backup") 24 cmdFlags.StringVar(&meta1.statePath, "state", DefaultStateFilename, "path") 25 cmdFlags.StringVar(&meta2.backupPath, "backup-out", "-", "backup") 26 cmdFlags.StringVar(&meta2.statePath, "state-out", "", "path") 27 if err := cmdFlags.Parse(args); err != nil { 28 return cli.RunResultHelp 29 } 30 args = cmdFlags.Args() 31 if len(args) != 2 { 32 c.Ui.Error("Exactly two arguments expected.\n") 33 return cli.RunResultHelp 34 } 35 36 // Copy the `-state` flag for output if we weren't given a custom one 37 if meta2.statePath == "" { 38 meta2.statePath = meta1.statePath 39 } 40 41 // Read the from state 42 stateFrom, err := c.StateMeta.State(&meta1) 43 if err != nil { 44 c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) 45 return cli.RunResultHelp 46 } 47 48 if err := stateFrom.RefreshState(); err != nil { 49 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 50 return 1 51 } 52 53 stateFromReal := stateFrom.State() 54 if stateFromReal == nil { 55 c.Ui.Error(fmt.Sprintf(errStateNotFound)) 56 return 1 57 } 58 59 // Read the destination state 60 stateTo := stateFrom 61 stateToReal := stateFromReal 62 if meta2.statePath != meta1.statePath { 63 stateTo, err = c.StateMeta.State(&meta2) 64 if err != nil { 65 c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) 66 return cli.RunResultHelp 67 } 68 69 if err := stateTo.RefreshState(); err != nil { 70 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 71 return 1 72 } 73 74 stateToReal = stateTo.State() 75 if stateToReal == nil { 76 stateToReal = terraform.NewState() 77 } 78 } 79 80 // Filter what we're moving 81 filter := &terraform.StateFilter{State: stateFromReal} 82 results, err := filter.Filter(args[0]) 83 if err != nil { 84 c.Ui.Error(fmt.Sprintf(errStateMv, err)) 85 return cli.RunResultHelp 86 } 87 if len(results) == 0 { 88 c.Ui.Output(fmt.Sprintf("Item to move doesn't exist: %s", args[0])) 89 return 1 90 } 91 92 // Get the item to add to the state 93 add := c.addableResult(results) 94 95 // Do the actual move 96 if err := stateFromReal.Remove(args[0]); err != nil { 97 c.Ui.Error(fmt.Sprintf(errStateMv, err)) 98 return 1 99 } 100 101 if err := stateToReal.Add(args[0], args[1], add); err != nil { 102 c.Ui.Error(fmt.Sprintf(errStateMv, err)) 103 return 1 104 } 105 106 // Write the new state 107 if err := stateTo.WriteState(stateToReal); err != nil { 108 c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) 109 return 1 110 } 111 112 if err := stateTo.PersistState(); err != nil { 113 c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) 114 return 1 115 } 116 117 // Write the old state if it is different 118 if stateTo != stateFrom { 119 if err := stateFrom.WriteState(stateFromReal); err != nil { 120 c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) 121 return 1 122 } 123 124 if err := stateFrom.PersistState(); err != nil { 125 c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) 126 return 1 127 } 128 } 129 130 c.Ui.Output(fmt.Sprintf( 131 "Moved %s to %s", args[0], args[1])) 132 return 0 133 } 134 135 // addableResult takes the result from a filter operation and returns what to 136 // call State.Add with. The reason we do this is because in the module case 137 // we must add the list of all modules returned versus just the root module. 138 func (c *StateMvCommand) addableResult(results []*terraform.StateFilterResult) interface{} { 139 switch v := results[0].Value.(type) { 140 case *terraform.ModuleState: 141 // If a module state then we should add the full list of modules 142 result := []*terraform.ModuleState{v} 143 if len(results) > 1 { 144 for _, r := range results[1:] { 145 if ms, ok := r.Value.(*terraform.ModuleState); ok { 146 result = append(result, ms) 147 } 148 } 149 } 150 151 return result 152 153 case *terraform.ResourceState: 154 // If a resource state with more than one result, it has a multi-count 155 // and we need to add all of them. 156 result := []*terraform.ResourceState{v} 157 if len(results) > 1 { 158 for _, r := range results[1:] { 159 rs, ok := r.Value.(*terraform.ResourceState) 160 if !ok { 161 continue 162 } 163 164 if rs.Type == v.Type { 165 result = append(result, rs) 166 } 167 } 168 } 169 170 // If we only have one item, add it directly 171 if len(result) == 1 { 172 return result[0] 173 } 174 175 return result 176 177 default: 178 // By default just add the first result 179 return v 180 } 181 } 182 183 func (c *StateMvCommand) Help() string { 184 helpText := ` 185 Usage: terraform state mv [options] ADDRESS ADDRESS 186 187 Move an item in the state to another location or to a completely different 188 state file. 189 190 This command is useful for module refactors (moving items into a module), 191 configuration refactors (moving items to a completely different or new 192 state file), or generally renaming of resources. 193 194 This command creates a timestamped backup of the state on every invocation. 195 This can't be disabled. Due to the destructive nature of this command, 196 the backup is ensured by Terraform for safety reasons. 197 198 If you're moving from one state file to a different state file, a backup 199 will be created for each state file. 200 201 Options: 202 203 -backup=PATH Path where Terraform should write the backup for the original 204 state. This can't be disabled. If not set, Terraform 205 will write it to the same path as the statefile with 206 a backup extension. 207 208 -backup-out=PATH Path where Terraform should write the backup for the destination 209 state. This can't be disabled. If not set, Terraform 210 will write it to the same path as the destination state 211 file with a backup extension. This only needs 212 to be specified if -state-out is set to a different path 213 than -state. 214 215 -state=PATH Path to a Terraform state file to use to look 216 up Terraform-managed resources. By default it will 217 use the state "terraform.tfstate" if it exists. 218 219 -state-out=PATH Path to the destination state file to move the item 220 to. This defaults to the same statefile. This will 221 overwrite the destination state file. 222 223 ` 224 return strings.TrimSpace(helpText) 225 } 226 227 func (c *StateMvCommand) Synopsis() string { 228 return "Move an item in the state" 229 } 230 231 const errStateMv = `Error moving state: %[1]s 232 233 Please ensure your addresses and state paths are valid. No 234 state was persisted. Your existing states are untouched.` 235 236 const errStateMvPersist = `Error saving the state: %s 237 238 The state wasn't saved properly. If the error happening after a partial 239 write occurred, a backup file will have been created. Otherwise, the state 240 is in the same state it was when the operation started.`