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