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