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