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