github.com/arvindram03/terraform@v0.3.7-0.20150212015210-408f838db36d/command/remote.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "flag" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "os" 10 "strings" 11 12 "github.com/hashicorp/terraform/remote" 13 "github.com/hashicorp/terraform/terraform" 14 ) 15 16 // remoteCommandConfig is used to encapsulate our configuration 17 type remoteCommandConfig struct { 18 disableRemote bool 19 pullOnDisable bool 20 21 statePath string 22 backupPath string 23 } 24 25 // RemoteCommand is a Command implementation that is used to 26 // enable and disable remote state management 27 type RemoteCommand struct { 28 Meta 29 conf remoteCommandConfig 30 remoteConf terraform.RemoteState 31 } 32 33 func (c *RemoteCommand) Run(args []string) int { 34 args = c.Meta.process(args, false) 35 var address, accessToken, name, path string 36 cmdFlags := flag.NewFlagSet("remote", flag.ContinueOnError) 37 cmdFlags.BoolVar(&c.conf.disableRemote, "disable", false, "") 38 cmdFlags.BoolVar(&c.conf.pullOnDisable, "pull", true, "") 39 cmdFlags.StringVar(&c.conf.statePath, "state", DefaultStateFilename, "path") 40 cmdFlags.StringVar(&c.conf.backupPath, "backup", "", "path") 41 cmdFlags.StringVar(&c.remoteConf.Type, "backend", "atlas", "") 42 cmdFlags.StringVar(&address, "address", "", "") 43 cmdFlags.StringVar(&accessToken, "access-token", "", "") 44 cmdFlags.StringVar(&name, "name", "", "") 45 cmdFlags.StringVar(&path, "path", "", "") 46 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 47 if err := cmdFlags.Parse(args); err != nil { 48 return 1 49 } 50 51 // Show help if given no inputs 52 if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && 53 name == "" && accessToken == "" { 54 cmdFlags.Usage() 55 return 1 56 } 57 58 // Populate the various configurations 59 c.remoteConf.Config = map[string]string{ 60 "address": address, 61 "access_token": accessToken, 62 "name": name, 63 "path": path, 64 } 65 66 // Check if have an existing local state file 67 haveLocal, err := remote.HaveLocalState() 68 if err != nil { 69 c.Ui.Error(fmt.Sprintf("Failed to check for local state: %v", err)) 70 return 1 71 } 72 73 // Check if we have the non-managed state file 74 haveNonManaged, err := remote.ExistsFile(c.conf.statePath) 75 if err != nil { 76 c.Ui.Error(fmt.Sprintf("Failed to check for state file: %v", err)) 77 return 1 78 } 79 80 // Check if remote state is being disabled 81 if c.conf.disableRemote { 82 if !haveLocal { 83 c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting.")) 84 return 1 85 } 86 if haveNonManaged { 87 c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.", 88 c.conf.statePath)) 89 return 1 90 } 91 return c.disableRemoteState() 92 } 93 94 // Ensure there is no conflict 95 switch { 96 case haveLocal && haveNonManaged: 97 c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!", 98 c.conf.statePath)) 99 return 1 100 101 case !haveLocal && !haveNonManaged: 102 // If we don't have either state file, initialize a blank state file 103 return c.initBlankState() 104 105 case haveLocal && !haveNonManaged: 106 // Update the remote state target potentially 107 return c.updateRemoteConfig() 108 109 case !haveLocal && haveNonManaged: 110 // Enable remote state management 111 return c.enableRemoteState() 112 113 default: 114 panic("unhandled case") 115 } 116 return 0 117 } 118 119 // disableRemoteState is used to disable remote state management, 120 // and move the state file into place. 121 func (c *RemoteCommand) disableRemoteState() int { 122 // Get the local state 123 local, _, err := remote.ReadLocalState() 124 if err != nil { 125 c.Ui.Error(fmt.Sprintf("Failed to read local state: %v", err)) 126 return 1 127 } 128 129 // Ensure we have the latest state before disabling 130 if c.conf.pullOnDisable { 131 log.Printf("[INFO] Refreshing local state from remote server") 132 change, err := remote.RefreshState(local.Remote) 133 if err != nil { 134 c.Ui.Error(fmt.Sprintf( 135 "Failed to refresh from remote state: %v", err)) 136 return 1 137 } 138 139 // Exit if we were unable to update 140 if !change.SuccessfulPull() { 141 c.Ui.Error(fmt.Sprintf("%s", change)) 142 return 1 143 } else { 144 log.Printf("[INFO] %s", change) 145 } 146 147 // Reload the local state after the refresh 148 local, _, err = remote.ReadLocalState() 149 if err != nil { 150 c.Ui.Error(fmt.Sprintf("Failed to read local state: %v", err)) 151 return 1 152 } 153 } 154 155 // Clear the remote management, and copy into place 156 local.Remote = nil 157 fh, err := os.Create(c.conf.statePath) 158 if err != nil { 159 c.Ui.Error(fmt.Sprintf("Failed to create state file '%s': %v", 160 c.conf.statePath, err)) 161 return 1 162 } 163 defer fh.Close() 164 if err := terraform.WriteState(local, fh); err != nil { 165 c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %v", 166 c.conf.statePath, err)) 167 return 1 168 } 169 170 // Remove the old state file 171 path, err := remote.HiddenStatePath() 172 if err != nil { 173 c.Ui.Error(fmt.Sprintf("Failed to get local state path: %v", err)) 174 return 1 175 } 176 if err := os.Remove(path); err != nil { 177 c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err)) 178 return 1 179 } 180 return 0 181 } 182 183 // validateRemoteConfig is used to verify that the remote configuration 184 // we have is valid 185 func (c *RemoteCommand) validateRemoteConfig() error { 186 err := remote.ValidConfig(&c.remoteConf) 187 if err != nil { 188 c.Ui.Error(fmt.Sprintf("%s", err)) 189 } 190 return err 191 } 192 193 // initBlank state is used to initialize a blank state that is 194 // remote enabled 195 func (c *RemoteCommand) initBlankState() int { 196 // Validate the remote configuration 197 if err := c.validateRemoteConfig(); err != nil { 198 return 1 199 } 200 201 // Make the hidden directory 202 if err := remote.EnsureDirectory(); err != nil { 203 c.Ui.Error(fmt.Sprintf("%s", err)) 204 return 1 205 } 206 207 // Make a blank state, attach the remote configuration 208 blank := terraform.NewState() 209 blank.Remote = &c.remoteConf 210 211 // Persist the state 212 if err := remote.PersistState(blank); err != nil { 213 c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err)) 214 return 1 215 } 216 217 // Success! 218 c.Ui.Output("Initialized blank state with remote state enabled!") 219 return 0 220 } 221 222 // updateRemoteConfig is used to update the configuration of the 223 // remote state store 224 func (c *RemoteCommand) updateRemoteConfig() int { 225 // Validate the remote configuration 226 if err := c.validateRemoteConfig(); err != nil { 227 return 1 228 } 229 230 // Read in the local state 231 local, _, err := remote.ReadLocalState() 232 if err != nil { 233 c.Ui.Error(fmt.Sprintf("Failed to read local state: %v", err)) 234 return 1 235 } 236 237 // Update the configuration 238 local.Remote = &c.remoteConf 239 if err := remote.PersistState(local); err != nil { 240 c.Ui.Error(fmt.Sprintf("%s", err)) 241 return 1 242 } 243 244 // Success! 245 c.Ui.Output("Remote configuration updated") 246 return 0 247 } 248 249 // enableRemoteState is used to enable remote state management 250 // and to move a state file into place 251 func (c *RemoteCommand) enableRemoteState() int { 252 // Validate the remote configuration 253 if err := c.validateRemoteConfig(); err != nil { 254 return 1 255 } 256 257 // Make the hidden directory 258 if err := remote.EnsureDirectory(); err != nil { 259 c.Ui.Error(fmt.Sprintf("%s", err)) 260 return 1 261 } 262 263 // Read the provided state file 264 raw, err := ioutil.ReadFile(c.conf.statePath) 265 if err != nil { 266 c.Ui.Error(fmt.Sprintf("Failed to read '%s': %v", c.conf.statePath, err)) 267 return 1 268 } 269 state, err := terraform.ReadState(bytes.NewReader(raw)) 270 if err != nil { 271 c.Ui.Error(fmt.Sprintf("Failed to decode '%s': %v", c.conf.statePath, err)) 272 return 1 273 } 274 275 // Backup the state file before we modify it 276 backupPath := c.conf.backupPath 277 if backupPath != "-" { 278 // Provide default backup path if none provided 279 if backupPath == "" { 280 backupPath = c.conf.statePath + DefaultBackupExtention 281 } 282 283 log.Printf("[INFO] Writing backup state to: %s", backupPath) 284 f, err := os.Create(backupPath) 285 if err == nil { 286 err = terraform.WriteState(state, f) 287 f.Close() 288 } 289 if err != nil { 290 c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err)) 291 return 1 292 } 293 } 294 295 // Update the local configuration, move into place 296 state.Remote = &c.remoteConf 297 if err := remote.PersistState(state); err != nil { 298 c.Ui.Error(fmt.Sprintf("%s", err)) 299 return 1 300 } 301 302 // Remove the state file 303 log.Printf("[INFO] Removing state file: %s", c.conf.statePath) 304 if err := os.Remove(c.conf.statePath); err != nil { 305 c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v", 306 c.conf.statePath, err)) 307 return 1 308 } 309 310 // Success! 311 c.Ui.Output("Remote state management enabled") 312 return 0 313 } 314 315 func (c *RemoteCommand) Help() string { 316 helpText := ` 317 Usage: terraform remote [options] 318 319 Configures Terraform to use a remote state server. This allows state 320 to be pulled down when necessary and then pushed to the server when 321 updated. In this mode, the state file does not need to be stored durably 322 since the remote server provides the durability. 323 324 Options: 325 326 -address=url URL of the remote storage server. 327 Required for HTTP backend, optional for Atlas and Consul. 328 329 -access-token=token Authentication token for state storage server. 330 Required for Atlas backend, optional for Consul. 331 332 -backend=Atlas Specifies the type of remote backend. Must be one 333 of Atlas, Consul, or HTTP. Defaults to Atlas. 334 335 -backup=path Path to backup the existing state file before 336 modifying. Defaults to the "-state" path with 337 ".backup" extension. Set to "-" to disable backup. 338 339 -disable Disables remote state management and migrates the state 340 to the -state path. 341 342 -name=name Name of the state file in the state storage server. 343 Required for Atlas backend. 344 345 -path=path Path of the remote state in Consul. Required for the 346 Consul backend. 347 348 -pull=true Controls if the remote state is pulled before disabling. 349 This defaults to true to ensure the latest state is cached 350 before disabling. 351 352 -state=path Path to read state. Defaults to "terraform.tfstate" 353 unless remote state is enabled. 354 355 ` 356 return strings.TrimSpace(helpText) 357 } 358 359 func (c *RemoteCommand) Synopsis() string { 360 return "Configures remote state management" 361 }