github.com/jgadling/terraform@v0.3.8-0.20150227214559-abd68c2c87bc/command/remote.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 // RemoteCommand is a Command implementation that is used to 25 // enable and disable remote state management 26 type RemoteCommand struct { 27 Meta 28 conf remoteCommandConfig 29 remoteConf terraform.RemoteState 30 } 31 32 func (c *RemoteCommand) 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 return 1 45 } 46 47 // Show help if given no inputs 48 if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && len(config) == 0 { 49 cmdFlags.Usage() 50 return 1 51 } 52 53 // Set the local state path 54 c.statePath = c.conf.statePath 55 56 // Populate the various configurations 57 c.remoteConf.Config = config 58 59 // Get the state information. We specifically request the cache only 60 // for the remote state here because it is possible the remote state 61 // is invalid and we don't want to error. 62 stateOpts := c.StateOpts() 63 stateOpts.RemoteCacheOnly = true 64 if _, err := c.StateRaw(stateOpts); err != nil { 65 c.Ui.Error(fmt.Sprintf("Error loading local state: %s", err)) 66 return 1 67 } 68 69 // Get the local and remote [cached] state 70 localState := c.stateResult.Local.State() 71 var remoteState *terraform.State 72 if remote := c.stateResult.Remote; remote != nil { 73 remoteState = remote.State() 74 } 75 76 // Check if remote state is being disabled 77 if c.conf.disableRemote { 78 if !remoteState.IsRemote() { 79 c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting.")) 80 return 1 81 } 82 if !localState.Empty() { 83 c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.", 84 c.conf.statePath)) 85 return 1 86 } 87 88 return c.disableRemoteState() 89 } 90 91 // Ensure there is no conflict 92 haveCache := !remoteState.Empty() 93 haveLocal := !localState.Empty() 94 switch { 95 case haveCache && haveLocal: 96 c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!", 97 c.conf.statePath)) 98 return 1 99 100 case !haveCache && !haveLocal: 101 // If we don't have either state file, initialize a blank state file 102 return c.initBlankState() 103 104 case haveCache && !haveLocal: 105 // Update the remote state target potentially 106 return c.updateRemoteConfig() 107 108 case !haveCache && haveLocal: 109 // Enable remote state management 110 return c.enableRemoteState() 111 } 112 113 panic("unhandled case") 114 } 115 116 // disableRemoteState is used to disable remote state management, 117 // and move the state file into place. 118 func (c *RemoteCommand) disableRemoteState() int { 119 if c.stateResult == nil { 120 c.Ui.Error(fmt.Sprintf( 121 "Internal error. State() must be called internally before remote\n" + 122 "state can be disabled. Please report this as a bug.")) 123 return 1 124 } 125 if !c.stateResult.State.State().IsRemote() { 126 c.Ui.Error(fmt.Sprintf( 127 "Remote state is not enabled. Can't disable remote state.")) 128 return 1 129 } 130 local := c.stateResult.Local 131 remote := c.stateResult.Remote 132 133 // Ensure we have the latest state before disabling 134 if c.conf.pullOnDisable { 135 log.Printf("[INFO] Refreshing local state from remote server") 136 if err := remote.RefreshState(); err != nil { 137 c.Ui.Error(fmt.Sprintf( 138 "Failed to refresh from remote state: %s", err)) 139 return 1 140 } 141 142 // Exit if we were unable to update 143 if change := remote.RefreshResult(); !change.SuccessfulPull() { 144 c.Ui.Error(fmt.Sprintf("%s", change)) 145 return 1 146 } else { 147 log.Printf("[INFO] %s", change) 148 } 149 } 150 151 // Clear the remote management, and copy into place 152 newState := remote.State() 153 newState.Remote = nil 154 if err := local.WriteState(newState); err != nil { 155 c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s", 156 c.conf.statePath, err)) 157 return 1 158 } 159 if err := local.PersistState(); err != nil { 160 c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s", 161 c.conf.statePath, err)) 162 return 1 163 } 164 165 // Remove the old state file 166 if err := os.Remove(c.stateResult.RemotePath); err != nil { 167 c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err)) 168 return 1 169 } 170 171 return 0 172 } 173 174 // validateRemoteConfig is used to verify that the remote configuration 175 // we have is valid 176 func (c *RemoteCommand) validateRemoteConfig() error { 177 conf := c.remoteConf 178 _, err := remote.NewClient(conf.Type, conf.Config) 179 if err != nil { 180 c.Ui.Error(fmt.Sprintf("%s", err)) 181 } 182 return err 183 } 184 185 // initBlank state is used to initialize a blank state that is 186 // remote enabled 187 func (c *RemoteCommand) initBlankState() int { 188 // Validate the remote configuration 189 if err := c.validateRemoteConfig(); err != nil { 190 return 1 191 } 192 193 // Make a blank state, attach the remote configuration 194 blank := terraform.NewState() 195 blank.Remote = &c.remoteConf 196 197 // Persist the state 198 remote := &state.LocalState{Path: c.stateResult.RemotePath} 199 if err := remote.WriteState(blank); err != nil { 200 c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err)) 201 return 1 202 } 203 if err := remote.PersistState(); err != nil { 204 c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err)) 205 return 1 206 } 207 208 // Success! 209 c.Ui.Output("Initialized blank state with remote state enabled!") 210 return 0 211 } 212 213 // updateRemoteConfig is used to update the configuration of the 214 // remote state store 215 func (c *RemoteCommand) updateRemoteConfig() int { 216 // Validate the remote configuration 217 if err := c.validateRemoteConfig(); err != nil { 218 return 1 219 } 220 221 // Read in the local state, which is just the cache of the remote state 222 remote := c.stateResult.Remote.Cache 223 224 // Update the configuration 225 state := remote.State() 226 state.Remote = &c.remoteConf 227 if err := remote.WriteState(state); err != nil { 228 c.Ui.Error(fmt.Sprintf("%s", err)) 229 return 1 230 } 231 if err := remote.PersistState(); err != nil { 232 c.Ui.Error(fmt.Sprintf("%s", err)) 233 return 1 234 } 235 236 // Success! 237 c.Ui.Output("Remote configuration updated") 238 return 0 239 } 240 241 // enableRemoteState is used to enable remote state management 242 // and to move a state file into place 243 func (c *RemoteCommand) enableRemoteState() int { 244 // Validate the remote configuration 245 if err := c.validateRemoteConfig(); err != nil { 246 return 1 247 } 248 249 // Read the local state 250 local := c.stateResult.Local 251 if err := local.RefreshState(); err != nil { 252 c.Ui.Error(fmt.Sprintf("Failed to read local state: %s", err)) 253 return 1 254 } 255 256 // Backup the state file before we modify it 257 backupPath := c.conf.backupPath 258 if backupPath != "-" { 259 // Provide default backup path if none provided 260 if backupPath == "" { 261 backupPath = c.conf.statePath + DefaultBackupExtention 262 } 263 264 log.Printf("[INFO] Writing backup state to: %s", backupPath) 265 backup := &state.LocalState{Path: backupPath} 266 if err := backup.WriteState(local.State()); err != nil { 267 c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err)) 268 return 1 269 } 270 if err := backup.PersistState(); err != nil { 271 c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err)) 272 return 1 273 } 274 } 275 276 // Update the local configuration, move into place 277 state := local.State() 278 state.Remote = &c.remoteConf 279 remote := c.stateResult.Remote 280 if err := remote.WriteState(state); err != nil { 281 c.Ui.Error(fmt.Sprintf("%s", err)) 282 return 1 283 } 284 if err := remote.PersistState(); err != nil { 285 c.Ui.Error(fmt.Sprintf("%s", err)) 286 return 1 287 } 288 289 // Remove the original, local state file 290 log.Printf("[INFO] Removing state file: %s", c.conf.statePath) 291 if err := os.Remove(c.conf.statePath); err != nil { 292 c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v", 293 c.conf.statePath, err)) 294 return 1 295 } 296 297 // Success! 298 c.Ui.Output("Remote state management enabled") 299 return 0 300 } 301 302 func (c *RemoteCommand) Help() string { 303 helpText := ` 304 Usage: terraform remote [options] 305 306 Configures Terraform to use a remote state server. This allows state 307 to be pulled down when necessary and then pushed to the server when 308 updated. In this mode, the state file does not need to be stored durably 309 since the remote server provides the durability. 310 311 Options: 312 313 -backend=Atlas Specifies the type of remote backend. Must be one 314 of Atlas, Consul, or HTTP. Defaults to Atlas. 315 316 -backend-config="k=v" Specifies configuration for the remote storage 317 backend. This can be specified multiple times. 318 319 -backup=path Path to backup the existing state file before 320 modifying. Defaults to the "-state" path with 321 ".backup" extension. Set to "-" to disable backup. 322 323 -disable Disables remote state management and migrates the state 324 to the -state path. 325 326 -pull=true Controls if the remote state is pulled before disabling. 327 This defaults to true to ensure the latest state is cached 328 before disabling. 329 330 -state=path Path to read state. Defaults to "terraform.tfstate" 331 unless remote state is enabled. 332 333 ` 334 return strings.TrimSpace(helpText) 335 } 336 337 func (c *RemoteCommand) Synopsis() string { 338 return "Configures remote state management" 339 }