github.com/renier/terraform@v0.7.8-0.20161024133817-eb8a9ef5471a/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.stateOutPath, "backup", "", "backup") 24 cmdFlags.StringVar(&meta1.statePath, "state", DefaultStateFilename, "path") 25 cmdFlags.StringVar(&meta2.stateOutPath, "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. 197 198 -backup-out=PATH Path where Terraform should write the backup for the destination 199 state. This can't be disabled. If not set, Terraform 200 will write it to the same path as the destination state 201 file with a backup extension. This only needs 202 to be specified if -state-out is set to a different path 203 than -state. 204 205 -state=PATH Path to a Terraform state file to use to look 206 up Terraform-managed resources. By default it will 207 use the state "terraform.tfstate" if it exists. 208 209 -state-out=PATH Path to the destination state file to move the item 210 to. This defaults to the same statefile. This will 211 overwrite the destination state file. 212 213 ` 214 return strings.TrimSpace(helpText) 215 } 216 217 func (c *StateMvCommand) Synopsis() string { 218 return "Move an item in the state" 219 } 220 221 const errStateMv = `Error moving state: %[1]s 222 223 Please ensure your addresses and state paths are valid. No 224 state was persisted. Your existing states are untouched.` 225 226 const errStateMvPersist = `Error saving the state: %s 227 228 The state wasn't saved properly. If the error happening after a partial 229 write occurred, a backup file will have been created. Otherwise, the state 230 is in the same state it was when the operation started.`