github.com/atsaki/terraform@v0.4.3-0.20150919165407-25bba5967654/command/remote_config.go (about) 1 package command 2 3 import ( 4 "flag" 5 "fmt" 6 "log" 7 "os" 8 "strings" 9 10 "github.com/hashicorp/terraform/state" 11 "github.com/hashicorp/terraform/state/remote" 12 "github.com/hashicorp/terraform/terraform" 13 ) 14 15 // remoteCommandConfig is used to encapsulate our configuration 16 type remoteCommandConfig struct { 17 disableRemote bool 18 pullOnDisable bool 19 20 statePath string 21 backupPath string 22 } 23 24 // RemoteConfigCommand is a Command implementation that is used to 25 // enable and disable remote state management 26 type RemoteConfigCommand struct { 27 Meta 28 conf remoteCommandConfig 29 remoteConf terraform.RemoteState 30 } 31 32 func (c *RemoteConfigCommand) Run(args []string) int { 33 args = c.Meta.process(args, false) 34 config := make(map[string]string) 35 cmdFlags := flag.NewFlagSet("remote", flag.ContinueOnError) 36 cmdFlags.BoolVar(&c.conf.disableRemote, "disable", false, "") 37 cmdFlags.BoolVar(&c.conf.pullOnDisable, "pull", true, "") 38 cmdFlags.StringVar(&c.conf.statePath, "state", DefaultStateFilename, "path") 39 cmdFlags.StringVar(&c.conf.backupPath, "backup", "", "path") 40 cmdFlags.StringVar(&c.remoteConf.Type, "backend", "atlas", "") 41 cmdFlags.Var((*FlagKV)(&config), "backend-config", "config") 42 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 43 if err := cmdFlags.Parse(args); err != nil { 44 c.Ui.Error(fmt.Sprintf("\nError parsing CLI flags: %s", err)) 45 return 1 46 } 47 48 // Lowercase the type 49 c.remoteConf.Type = strings.ToLower(c.remoteConf.Type) 50 51 // Set the local state path 52 c.statePath = c.conf.statePath 53 54 // Populate the various configurations 55 c.remoteConf.Config = config 56 57 // Get the state information. We specifically request the cache only 58 // for the remote state here because it is possible the remote state 59 // is invalid and we don't want to error. 60 stateOpts := c.StateOpts() 61 stateOpts.RemoteCacheOnly = true 62 if _, err := c.StateRaw(stateOpts); err != nil { 63 c.Ui.Error(fmt.Sprintf("Error loading local state: %s", err)) 64 return 1 65 } 66 67 // Get the local and remote [cached] state 68 localState := c.stateResult.Local.State() 69 var remoteState *terraform.State 70 if remote := c.stateResult.Remote; remote != nil { 71 remoteState = remote.State() 72 } 73 74 // Check if remote state is being disabled 75 if c.conf.disableRemote { 76 if !remoteState.IsRemote() { 77 c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting.")) 78 return 1 79 } 80 if !localState.Empty() { 81 c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.", 82 c.conf.statePath)) 83 return 1 84 } 85 86 return c.disableRemoteState() 87 } 88 89 // Ensure there is no conflict, and then do the correct operation 90 var result int 91 haveCache := !remoteState.Empty() 92 haveLocal := !localState.Empty() 93 switch { 94 case haveCache && haveLocal: 95 c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!", 96 c.conf.statePath)) 97 result = 1 98 99 case !haveCache && !haveLocal: 100 // If we don't have either state file, initialize a blank state file 101 result = c.initBlankState() 102 103 case haveCache && !haveLocal: 104 // Update the remote state target potentially 105 result = c.updateRemoteConfig() 106 107 case !haveCache && haveLocal: 108 // Enable remote state management 109 result = c.enableRemoteState() 110 } 111 112 // If there was an error, return right away 113 if result != 0 { 114 return result 115 } 116 117 // If we're not pulling, then do nothing 118 if !c.conf.pullOnDisable { 119 return result 120 } 121 122 // Otherwise, refresh the state 123 stateResult, err := c.StateRaw(c.StateOpts()) 124 if err != nil { 125 c.Ui.Error(fmt.Sprintf( 126 "Error while performing the initial pull. The error message is shown\n"+ 127 "below. Note that remote state was properly configured, so you don't\n"+ 128 "need to reconfigure. You can now use `push` and `pull` directly.\n"+ 129 "\n%s", err)) 130 return 1 131 } 132 133 state := stateResult.State 134 if err := state.RefreshState(); err != nil { 135 c.Ui.Error(fmt.Sprintf( 136 "Error while performing the initial pull. The error message is shown\n"+ 137 "below. Note that remote state was properly configured, so you don't\n"+ 138 "need to reconfigure. You can now use `push` and `pull` directly.\n"+ 139 "\n%s", err)) 140 return 1 141 } 142 143 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 144 "[reset][bold][green]Remote state configured and pulled."))) 145 return 0 146 } 147 148 // disableRemoteState is used to disable remote state management, 149 // and move the state file into place. 150 func (c *RemoteConfigCommand) disableRemoteState() int { 151 if c.stateResult == nil { 152 c.Ui.Error(fmt.Sprintf( 153 "Internal error. State() must be called internally before remote\n" + 154 "state can be disabled. Please report this as a bug.")) 155 return 1 156 } 157 if !c.stateResult.State.State().IsRemote() { 158 c.Ui.Error(fmt.Sprintf( 159 "Remote state is not enabled. Can't disable remote state.")) 160 return 1 161 } 162 local := c.stateResult.Local 163 remote := c.stateResult.Remote 164 165 // Ensure we have the latest state before disabling 166 if c.conf.pullOnDisable { 167 log.Printf("[INFO] Refreshing local state from remote server") 168 if err := remote.RefreshState(); err != nil { 169 c.Ui.Error(fmt.Sprintf( 170 "Failed to refresh from remote state: %s", err)) 171 return 1 172 } 173 174 // Exit if we were unable to update 175 if change := remote.RefreshResult(); !change.SuccessfulPull() { 176 c.Ui.Error(fmt.Sprintf("%s", change)) 177 return 1 178 } else { 179 log.Printf("[INFO] %s", change) 180 } 181 } 182 183 // Clear the remote management, and copy into place 184 newState := remote.State() 185 newState.Remote = nil 186 if err := local.WriteState(newState); err != nil { 187 c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s", 188 c.conf.statePath, err)) 189 return 1 190 } 191 if err := local.PersistState(); err != nil { 192 c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s", 193 c.conf.statePath, err)) 194 return 1 195 } 196 197 // Remove the old state file 198 if err := os.Remove(c.stateResult.RemotePath); err != nil { 199 c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err)) 200 return 1 201 } 202 203 return 0 204 } 205 206 // validateRemoteConfig is used to verify that the remote configuration 207 // we have is valid 208 func (c *RemoteConfigCommand) validateRemoteConfig() error { 209 conf := c.remoteConf 210 _, err := remote.NewClient(conf.Type, conf.Config) 211 if err != nil { 212 c.Ui.Error(fmt.Sprintf( 213 "%s\n\n"+ 214 "If the error message above mentions requiring or modifying configuration\n"+ 215 "options, these are set using the `-backend-config` flag. Example:\n"+ 216 "-backend-config=\"name=foo\" to set the `name` configuration", 217 err)) 218 } 219 return err 220 } 221 222 // initBlank state is used to initialize a blank state that is 223 // remote enabled 224 func (c *RemoteConfigCommand) initBlankState() int { 225 // Validate the remote configuration 226 if err := c.validateRemoteConfig(); err != nil { 227 return 1 228 } 229 230 // Make a blank state, attach the remote configuration 231 blank := terraform.NewState() 232 blank.Remote = &c.remoteConf 233 234 // Persist the state 235 remote := &state.LocalState{Path: c.stateResult.RemotePath} 236 if err := remote.WriteState(blank); err != nil { 237 c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err)) 238 return 1 239 } 240 if err := remote.PersistState(); err != nil { 241 c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err)) 242 return 1 243 } 244 245 // Success! 246 c.Ui.Output("Initialized blank state with remote state enabled!") 247 return 0 248 } 249 250 // updateRemoteConfig is used to update the configuration of the 251 // remote state store 252 func (c *RemoteConfigCommand) updateRemoteConfig() int { 253 // Validate the remote configuration 254 if err := c.validateRemoteConfig(); err != nil { 255 return 1 256 } 257 258 // Read in the local state, which is just the cache of the remote state 259 remote := c.stateResult.Remote.Cache 260 261 // Update the configuration 262 state := remote.State() 263 state.Remote = &c.remoteConf 264 if err := remote.WriteState(state); err != nil { 265 c.Ui.Error(fmt.Sprintf("%s", err)) 266 return 1 267 } 268 if err := remote.PersistState(); err != nil { 269 c.Ui.Error(fmt.Sprintf("%s", err)) 270 return 1 271 } 272 273 // Success! 274 c.Ui.Output("Remote configuration updated") 275 return 0 276 } 277 278 // enableRemoteState is used to enable remote state management 279 // and to move a state file into place 280 func (c *RemoteConfigCommand) enableRemoteState() int { 281 // Validate the remote configuration 282 if err := c.validateRemoteConfig(); err != nil { 283 return 1 284 } 285 286 // Read the local state 287 local := c.stateResult.Local 288 if err := local.RefreshState(); err != nil { 289 c.Ui.Error(fmt.Sprintf("Failed to read local state: %s", err)) 290 return 1 291 } 292 293 // Backup the state file before we modify it 294 backupPath := c.conf.backupPath 295 if backupPath != "-" { 296 // Provide default backup path if none provided 297 if backupPath == "" { 298 backupPath = c.conf.statePath + DefaultBackupExtension 299 } 300 301 log.Printf("[INFO] Writing backup state to: %s", backupPath) 302 backup := &state.LocalState{Path: backupPath} 303 if err := backup.WriteState(local.State()); err != nil { 304 c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err)) 305 return 1 306 } 307 if err := backup.PersistState(); err != nil { 308 c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err)) 309 return 1 310 } 311 } 312 313 // Update the local configuration, move into place 314 state := local.State() 315 state.Remote = &c.remoteConf 316 remote := c.stateResult.Remote 317 if err := remote.WriteState(state); err != nil { 318 c.Ui.Error(fmt.Sprintf("%s", err)) 319 return 1 320 } 321 if err := remote.PersistState(); err != nil { 322 c.Ui.Error(fmt.Sprintf("%s", err)) 323 return 1 324 } 325 326 // Remove the original, local state file 327 log.Printf("[INFO] Removing state file: %s", c.conf.statePath) 328 if err := os.Remove(c.conf.statePath); err != nil { 329 c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v", 330 c.conf.statePath, err)) 331 return 1 332 } 333 334 // Success! 335 c.Ui.Output("Remote state management enabled") 336 return 0 337 } 338 339 func (c *RemoteConfigCommand) Help() string { 340 helpText := ` 341 Usage: terraform remote [options] 342 343 Configures Terraform to use a remote state server. This allows state 344 to be pulled down when necessary and then pushed to the server when 345 updated. In this mode, the state file does not need to be stored durably 346 since the remote server provides the durability. 347 348 Options: 349 350 -backend=Atlas Specifies the type of remote backend. Must be one 351 of Atlas, Consul, or HTTP. Defaults to Atlas. 352 353 -backend-config="k=v" Specifies configuration for the remote storage 354 backend. This can be specified multiple times. 355 356 -backup=path Path to backup the existing state file before 357 modifying. Defaults to the "-state" path with 358 ".backup" extension. Set to "-" to disable backup. 359 360 -disable Disables remote state management and migrates the state 361 to the -state path. 362 363 -pull=true If disabling, this controls if the remote state is 364 pulled before disabling. If enabling, this controls 365 if the remote state is pulled after enabling. This 366 defaults to true. 367 368 -state=path Path to read state. Defaults to "terraform.tfstate" 369 unless remote state is enabled. 370 371 -no-color If specified, output won't contain any color. 372 373 ` 374 return strings.TrimSpace(helpText) 375 } 376 377 func (c *RemoteConfigCommand) Synopsis() string { 378 return "Configures remote state management" 379 }