github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/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 beacuse 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. This backup will be made in addition 207 to the timestamped backup. 208 209 -backup-out=PATH Path where Terraform should write the backup for the destination 210 state. This can't be disabled. If not set, Terraform 211 will write it to the same path as the destination state 212 file with a backup extension. This only needs 213 to be specified if -state-out is set to a different path 214 than -state. 215 216 -state=PATH Path to a Terraform state file to use to look 217 up Terraform-managed resources. By default it will 218 use the state "terraform.tfstate" if it exists. 219 220 -state-out=PATH Path to the destination state file to move the item 221 to. This defaults to the same statefile. This will 222 overwrite the destination state file. 223 224 ` 225 return strings.TrimSpace(helpText) 226 } 227 228 func (c *StateMvCommand) Synopsis() string { 229 return "Move an item in the state" 230 } 231 232 const errStateMv = `Error moving state: %[1]s 233 234 Please ensure your addresses and state paths are valid. No 235 state was persisted. Your existing states are untouched.` 236 237 const errStateMvPersist = `Error saving the state: %s 238 239 The state wasn't saved properly. If the error happening after a partial 240 write occurred, a backup file will have been created. Otherwise, the state 241 is in the same state it was when the operation started.`