github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/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 StateMeta 14 } 15 16 func (c *StateMvCommand) Run(args []string) int { 17 args, err := c.Meta.process(args, true) 18 if err != nil { 19 return 1 20 } 21 22 // We create two metas to track the two states 23 var backupPathOut, statePathOut string 24 25 cmdFlags := c.Meta.flagSet("state mv") 26 cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup") 27 cmdFlags.StringVar(&c.statePath, "state", "", "path") 28 cmdFlags.StringVar(&backupPathOut, "backup-out", "-", "backup") 29 cmdFlags.StringVar(&statePathOut, "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 // Read the from state 40 stateFrom, err := c.State() 41 if err != nil { 42 c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) 43 return 1 44 } 45 46 if err := stateFrom.RefreshState(); err != nil { 47 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 48 return 1 49 } 50 51 stateFromReal := stateFrom.State() 52 if stateFromReal == nil { 53 c.Ui.Error(fmt.Sprintf(errStateNotFound)) 54 return 1 55 } 56 57 // Read the destination state 58 stateTo := stateFrom 59 stateToReal := stateFromReal 60 61 if statePathOut != "" { 62 c.statePath = statePathOut 63 c.backupPath = backupPathOut 64 stateTo, err = c.State() 65 if err != nil { 66 c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) 67 return 1 68 } 69 70 if err := stateTo.RefreshState(); err != nil { 71 c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) 72 return 1 73 } 74 75 stateToReal = stateTo.State() 76 if stateToReal == nil { 77 stateToReal = terraform.NewState() 78 } 79 } 80 81 // Filter what we're moving 82 filter := &terraform.StateFilter{State: stateFromReal} 83 results, err := filter.Filter(args[0]) 84 if err != nil { 85 c.Ui.Error(fmt.Sprintf(errStateMv, err)) 86 return cli.RunResultHelp 87 } 88 if len(results) == 0 { 89 c.Ui.Output(fmt.Sprintf("Item to move doesn't exist: %s", args[0])) 90 return 1 91 } 92 93 // Get the item to add to the state 94 add := c.addableResult(results) 95 96 // Do the actual move 97 if err := stateFromReal.Remove(args[0]); err != nil { 98 c.Ui.Error(fmt.Sprintf(errStateMv, err)) 99 return 1 100 } 101 102 if err := stateToReal.Add(args[0], args[1], add); err != nil { 103 c.Ui.Error(fmt.Sprintf(errStateMv, err)) 104 return 1 105 } 106 107 // Write the new state 108 if err := stateTo.WriteState(stateToReal); err != nil { 109 c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) 110 return 1 111 } 112 113 if err := stateTo.PersistState(); err != nil { 114 c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) 115 return 1 116 } 117 118 // Write the old state if it is different 119 if stateTo != stateFrom { 120 if err := stateFrom.WriteState(stateFromReal); err != nil { 121 c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) 122 return 1 123 } 124 125 if err := stateFrom.PersistState(); err != nil { 126 c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) 127 return 1 128 } 129 } 130 131 c.Ui.Output(fmt.Sprintf( 132 "Moved %s to %s", args[0], args[1])) 133 return 0 134 } 135 136 // addableResult takes the result from a filter operation and returns what to 137 // call State.Add with. The reason we do this is because in the module case 138 // we must add the list of all modules returned versus just the root module. 139 func (c *StateMvCommand) addableResult(results []*terraform.StateFilterResult) interface{} { 140 switch v := results[0].Value.(type) { 141 case *terraform.ModuleState: 142 // If a module state then we should add the full list of modules 143 result := []*terraform.ModuleState{v} 144 if len(results) > 1 { 145 for _, r := range results[1:] { 146 if ms, ok := r.Value.(*terraform.ModuleState); ok { 147 result = append(result, ms) 148 } 149 } 150 } 151 152 return result 153 154 case *terraform.ResourceState: 155 // If a resource state with more than one result, it has a multi-count 156 // and we need to add all of them. 157 result := []*terraform.ResourceState{v} 158 if len(results) > 1 { 159 for _, r := range results[1:] { 160 rs, ok := r.Value.(*terraform.ResourceState) 161 if !ok { 162 continue 163 } 164 165 if rs.Type == v.Type { 166 result = append(result, rs) 167 } 168 } 169 } 170 171 // If we only have one item, add it directly 172 if len(result) == 1 { 173 return result[0] 174 } 175 176 return result 177 178 default: 179 // By default just add the first result 180 return v 181 } 182 } 183 184 func (c *StateMvCommand) Help() string { 185 helpText := ` 186 Usage: terraform state mv [options] SOURCE DESTINATION 187 188 This command will move an item matched by the address given to the 189 destination address. This command can also move to a destination address 190 in a completely different state file. 191 192 This can be used for simple resource renaming, moving items to and from 193 a module, moving entire modules, and more. And because this command can also 194 move data to a completely new state, it can also be used for refactoring 195 one configuration into multiple separately managed Terraform configurations. 196 197 This command will output a backup copy of the state prior to saving any 198 changes. The backup cannot be disabled. Due to the destructive nature 199 of this command, backups are required. 200 201 If you're moving an item to a different state file, a backup will be created 202 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 the source state file. Defaults to the configured 219 backend, or "terraform.tfstate" 220 221 -state-out=PATH Path to the destination state file to write to. If this 222 isn't specified, the source state file will be used. This 223 can be a new or existing path. 224 225 ` 226 return strings.TrimSpace(helpText) 227 } 228 229 func (c *StateMvCommand) Synopsis() string { 230 return "Move an item in the state" 231 } 232 233 const errStateMv = `Error moving state: %[1]s 234 235 Please ensure your addresses and state paths are valid. No 236 state was persisted. Your existing states are untouched.` 237 238 const errStateMvPersist = `Error saving the state: %s 239 240 The state wasn't saved properly. If the error happening after a partial 241 write occurred, a backup file will have been created. Otherwise, the state 242 is in the same state it was when the operation started.`