Merge branch 'master' into f-aws-route53zone-tags
* master: provider/aws: Fix dependency violation when deleting Internet Gateways command/remote-config: failing tests update CHANGELOG command/remote-config: do a pull with `terraform remote config` command/remote-{pull,push}: colorize and show success output command/remote-config: lowercase the type so that Atlas works, for example command/remote-config: show flag parse errors command/remote-config: remove weird error case that shows no error message command: when setting up state, only write back if local is newer
This commit is contained in:
commit
3ce5b6cd70
|
@ -40,6 +40,8 @@ IMPROVEMENTS:
|
||||||
like refresh.
|
like refresh.
|
||||||
* core: Autoload `terraform.tfvars.json` as well as `terraform.tfvars` [GH-1030]
|
* core: Autoload `terraform.tfvars.json` as well as `terraform.tfvars` [GH-1030]
|
||||||
* core: `.tf` files that start with a period are now ignored. [GH-1227]
|
* core: `.tf` files that start with a period are now ignored. [GH-1227]
|
||||||
|
* command/remote-config: After enabling remote state, a `pull` is
|
||||||
|
automatically done initially.
|
||||||
* providers/google: Add `size` option to disk blocks for instances. [GH-1284]
|
* providers/google: Add `size` option to disk blocks for instances. [GH-1284]
|
||||||
|
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
|
|
|
@ -199,39 +199,14 @@ func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{})
|
||||||
d.Id(),
|
d.Id(),
|
||||||
vpcID.(string))
|
vpcID.(string))
|
||||||
|
|
||||||
wait := true
|
|
||||||
err := ec2conn.DetachInternetGateway(&ec2.DetachInternetGatewayRequest{
|
|
||||||
InternetGatewayID: aws.String(d.Id()),
|
|
||||||
VPCID: aws.String(vpcID.(string)),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
ec2err, ok := err.(aws.APIError)
|
|
||||||
if ok {
|
|
||||||
if ec2err.Code == "InvalidInternetGatewayID.NotFound" {
|
|
||||||
err = nil
|
|
||||||
wait = false
|
|
||||||
} else if ec2err.Code == "Gateway.NotAttached" {
|
|
||||||
err = nil
|
|
||||||
wait = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !wait {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for it to be fully detached before continuing
|
// Wait for it to be fully detached before continuing
|
||||||
log.Printf("[DEBUG] Waiting for internet gateway (%s) to detach", d.Id())
|
log.Printf("[DEBUG] Waiting for internet gateway (%s) to detach", d.Id())
|
||||||
stateConf := &resource.StateChangeConf{
|
stateConf := &resource.StateChangeConf{
|
||||||
Pending: []string{"attached", "detaching", "available"},
|
Pending: []string{"detaching"},
|
||||||
Target: "detached",
|
Target: "detached",
|
||||||
Refresh: IGAttachStateRefreshFunc(ec2conn, d.Id(), "detached"),
|
Refresh: detachIGStateRefreshFunc(ec2conn, d.Id(), vpcID.(string)),
|
||||||
Timeout: 1 * time.Minute,
|
Timeout: 2 * time.Minute,
|
||||||
|
Delay: 10 * time.Second,
|
||||||
}
|
}
|
||||||
if _, err := stateConf.WaitForState(); err != nil {
|
if _, err := stateConf.WaitForState(); err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
|
@ -242,6 +217,32 @@ func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
||||||
|
// an EC2 instance.
|
||||||
|
func detachIGStateRefreshFunc(conn *ec2.EC2, instanceID, vpcID string) resource.StateRefreshFunc {
|
||||||
|
return func() (interface{}, string, error) {
|
||||||
|
err := conn.DetachInternetGateway(&ec2.DetachInternetGatewayRequest{
|
||||||
|
InternetGatewayID: aws.String(instanceID),
|
||||||
|
VPCID: aws.String(vpcID),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec2err, ok := err.(aws.APIError)
|
||||||
|
if ok {
|
||||||
|
if ec2err.Code == "InvalidInternetGatewayID.NotFound" {
|
||||||
|
return nil, "Not Found", err
|
||||||
|
} else if ec2err.Code == "Gateway.NotAttached" {
|
||||||
|
return "detached", "detached", nil
|
||||||
|
} else if ec2err.Code == "DependencyViolation" {
|
||||||
|
return nil, "detaching", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DetachInternetGateway only returns an error, so if it's nil, assume we're
|
||||||
|
// detached
|
||||||
|
return "detached", "detached", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// IGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
// IGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
||||||
// an internet gateway.
|
// an internet gateway.
|
||||||
func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
||||||
|
@ -300,10 +301,6 @@ func IGAttachStateRefreshFunc(ec2conn *ec2.EC2, id string, expected string) reso
|
||||||
|
|
||||||
ig := &resp.InternetGateways[0]
|
ig := &resp.InternetGateways[0]
|
||||||
|
|
||||||
if time.Now().Sub(start) > 10*time.Second {
|
|
||||||
return ig, expected, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ig.Attachments) == 0 {
|
if len(ig.Attachments) == 0 {
|
||||||
// No attachments, we're detached
|
// No attachments, we're detached
|
||||||
return ig, "detached", nil
|
return ig, "detached", nil
|
||||||
|
|
|
@ -41,14 +41,12 @@ func (c *RemoteConfigCommand) Run(args []string) int {
|
||||||
cmdFlags.Var((*FlagKV)(&config), "backend-config", "config")
|
cmdFlags.Var((*FlagKV)(&config), "backend-config", "config")
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("\nError parsing CLI flags: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show help if given no inputs
|
// Lowercase the type
|
||||||
if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && len(config) == 0 {
|
c.remoteConf.Type = strings.ToLower(c.remoteConf.Type)
|
||||||
cmdFlags.Usage()
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the local state path
|
// Set the local state path
|
||||||
c.statePath = c.conf.statePath
|
c.statePath = c.conf.statePath
|
||||||
|
@ -88,29 +86,63 @@ func (c *RemoteConfigCommand) Run(args []string) int {
|
||||||
return c.disableRemoteState()
|
return c.disableRemoteState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure there is no conflict
|
// Ensure there is no conflict, and then do the correct operation
|
||||||
|
var result int
|
||||||
haveCache := !remoteState.Empty()
|
haveCache := !remoteState.Empty()
|
||||||
haveLocal := !localState.Empty()
|
haveLocal := !localState.Empty()
|
||||||
switch {
|
switch {
|
||||||
case haveCache && haveLocal:
|
case haveCache && haveLocal:
|
||||||
c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
|
c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
|
||||||
c.conf.statePath))
|
c.conf.statePath))
|
||||||
return 1
|
result = 1
|
||||||
|
|
||||||
case !haveCache && !haveLocal:
|
case !haveCache && !haveLocal:
|
||||||
// If we don't have either state file, initialize a blank state file
|
// If we don't have either state file, initialize a blank state file
|
||||||
return c.initBlankState()
|
result = c.initBlankState()
|
||||||
|
|
||||||
case haveCache && !haveLocal:
|
case haveCache && !haveLocal:
|
||||||
// Update the remote state target potentially
|
// Update the remote state target potentially
|
||||||
return c.updateRemoteConfig()
|
result = c.updateRemoteConfig()
|
||||||
|
|
||||||
case !haveCache && haveLocal:
|
case !haveCache && haveLocal:
|
||||||
// Enable remote state management
|
// Enable remote state management
|
||||||
return c.enableRemoteState()
|
result = c.enableRemoteState()
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unhandled case")
|
// If there was an error, return right away
|
||||||
|
if result != 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not pulling, then do nothing
|
||||||
|
if !c.conf.pullOnDisable {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, refresh the state
|
||||||
|
stateResult, err := c.StateRaw(c.StateOpts())
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(
|
||||||
|
"Error while performing the initial pull. The error message is shown\n"+
|
||||||
|
"below. Note that remote state was properly configured, so you don't\n"+
|
||||||
|
"need to reconfigure. You can now use `push` and `pull` directly.\n"+
|
||||||
|
"\n%s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
state := stateResult.State
|
||||||
|
if err := state.RefreshState(); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(
|
||||||
|
"Error while performing the initial pull. The error message is shown\n"+
|
||||||
|
"below. Note that remote state was properly configured, so you don't\n"+
|
||||||
|
"need to reconfigure. You can now use `push` and `pull` directly.\n"+
|
||||||
|
"\n%s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
|
||||||
|
"[reset][bold][green]Remote state configured and pulled.")))
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// disableRemoteState is used to disable remote state management,
|
// disableRemoteState is used to disable remote state management,
|
||||||
|
@ -328,9 +360,10 @@ Options:
|
||||||
-disable Disables remote state management and migrates the state
|
-disable Disables remote state management and migrates the state
|
||||||
to the -state path.
|
to the -state path.
|
||||||
|
|
||||||
-pull=true Controls if the remote state is pulled before disabling.
|
-pull=true If disabling, this controls if the remote state is
|
||||||
This defaults to true to ensure the latest state is cached
|
pulled before disabling. If enabling, this controls
|
||||||
before disabling.
|
if the remote state is pulled after enabling. This
|
||||||
|
defaults to true.
|
||||||
|
|
||||||
-state=path Path to read state. Defaults to "terraform.tfstate"
|
-state=path Path to read state. Defaults to "terraform.tfstate"
|
||||||
unless remote state is enabled.
|
unless remote state is enabled.
|
||||||
|
|
|
@ -245,6 +245,7 @@ func TestRemoteConfig_initBlank(t *testing.T) {
|
||||||
"-backend=http",
|
"-backend=http",
|
||||||
"-backend-config", "address=http://example.com",
|
"-backend-config", "address=http://example.com",
|
||||||
"-backend-config", "access_token=test",
|
"-backend-config", "access_token=test",
|
||||||
|
"-pull=false",
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
|
@ -321,6 +322,7 @@ func TestRemoteConfig_updateRemote(t *testing.T) {
|
||||||
"-backend=http",
|
"-backend=http",
|
||||||
"-backend-config", "address=http://example.com",
|
"-backend-config", "address=http://example.com",
|
||||||
"-backend-config", "access_token=test",
|
"-backend-config", "access_token=test",
|
||||||
|
"-pull=false",
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
|
@ -376,6 +378,7 @@ func TestRemoteConfig_enableRemote(t *testing.T) {
|
||||||
"-backend=http",
|
"-backend=http",
|
||||||
"-backend-config", "address=http://example.com",
|
"-backend-config", "address=http://example.com",
|
||||||
"-backend-config", "access_token=test",
|
"-backend-config", "access_token=test",
|
||||||
|
"-pull=false",
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
|
@ -61,7 +61,8 @@ func (c *RemotePullCommand) Run(args []string) int {
|
||||||
c.Ui.Error(fmt.Sprintf("%s", change))
|
c.Ui.Error(fmt.Sprintf("%s", change))
|
||||||
return 1
|
return 1
|
||||||
} else {
|
} else {
|
||||||
c.Ui.Output(fmt.Sprintf("%s", change))
|
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
|
||||||
|
"[reset][bold][green]%s", change)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -80,15 +80,6 @@ func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.Remote
|
||||||
var b64md5 string
|
var b64md5 string
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
if s != nil {
|
|
||||||
enc := json.NewEncoder(buf)
|
|
||||||
if err := enc.Encode(s); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
md5 := md5.Sum(buf.Bytes())
|
|
||||||
b64md5 = base64.StdEncoding.EncodeToString(md5[:16])
|
|
||||||
}
|
|
||||||
|
|
||||||
cb := func(resp http.ResponseWriter, req *http.Request) {
|
cb := func(resp http.ResponseWriter, req *http.Request) {
|
||||||
if req.Method == "PUT" {
|
if req.Method == "PUT" {
|
||||||
resp.WriteHeader(c)
|
resp.WriteHeader(c)
|
||||||
|
@ -98,13 +89,28 @@ func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.Remote
|
||||||
resp.WriteHeader(404)
|
resp.WriteHeader(404)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Header().Set("Content-MD5", b64md5)
|
resp.Header().Set("Content-MD5", b64md5)
|
||||||
resp.Write(buf.Bytes())
|
resp.Write(buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(cb))
|
srv := httptest.NewServer(http.HandlerFunc(cb))
|
||||||
remote := &terraform.RemoteState{
|
remote := &terraform.RemoteState{
|
||||||
Type: "http",
|
Type: "http",
|
||||||
Config: map[string]string{"address": srv.URL},
|
Config: map[string]string{"address": srv.URL},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s != nil {
|
||||||
|
// Set the remote data
|
||||||
|
s.Remote = remote
|
||||||
|
|
||||||
|
enc := json.NewEncoder(buf)
|
||||||
|
if err := enc.Encode(s); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
md5 := md5.Sum(buf.Bytes())
|
||||||
|
b64md5 = base64.StdEncoding.EncodeToString(md5[:16])
|
||||||
|
}
|
||||||
|
|
||||||
return remote, srv
|
return remote, srv
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,8 @@ func (c *RemotePushCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.Ui.Output(c.Colorize().Color(
|
||||||
|
"[reset][bold][green]State successfully pushed!"))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -231,10 +231,20 @@ func remoteState(
|
||||||
"Error reloading remote state: {{err}}", err)
|
"Error reloading remote state: {{err}}", err)
|
||||||
}
|
}
|
||||||
switch cache.RefreshResult() {
|
switch cache.RefreshResult() {
|
||||||
|
// All the results below can be safely ignored since it means the
|
||||||
|
// pull was successful in some way. Noop = nothing happened.
|
||||||
|
// Init = both are empty. UpdateLocal = local state was older and
|
||||||
|
// updated.
|
||||||
|
//
|
||||||
|
// We don't have to do anything, the pull was successful.
|
||||||
case state.CacheRefreshNoop:
|
case state.CacheRefreshNoop:
|
||||||
case state.CacheRefreshInit:
|
case state.CacheRefreshInit:
|
||||||
case state.CacheRefreshLocalNewer:
|
|
||||||
case state.CacheRefreshUpdateLocal:
|
case state.CacheRefreshUpdateLocal:
|
||||||
|
|
||||||
|
// Our local state has a higher serial number than remote, so we
|
||||||
|
// want to explicitly sync the remote side with our local so that
|
||||||
|
// the remote gets the latest serial number.
|
||||||
|
case state.CacheRefreshLocalNewer:
|
||||||
// Write our local state out to the durable storage to start.
|
// Write our local state out to the durable storage to start.
|
||||||
if err := cache.WriteState(local); err != nil {
|
if err := cache.WriteState(local); err != nil {
|
||||||
return nil, errwrap.Wrapf(
|
return nil, errwrap.Wrapf(
|
||||||
|
|
|
@ -344,6 +344,10 @@ func (r *RemoteState) Equals(other *RemoteState) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RemoteState) GoString() string {
|
||||||
|
return fmt.Sprintf("*%#v", *r)
|
||||||
|
}
|
||||||
|
|
||||||
// ModuleState is used to track all the state relevant to a single
|
// ModuleState is used to track all the state relevant to a single
|
||||||
// module. Previous to Terraform 0.3, all state belonged to the "root"
|
// module. Previous to Terraform 0.3, all state belonged to the "root"
|
||||||
// module.
|
// module.
|
||||||
|
|
|
@ -73,8 +73,9 @@ The command-line flags are all optional. The list of available flags are:
|
||||||
* `-path=path` - Path of the remote state in Consul. Required for the
|
* `-path=path` - Path of the remote state in Consul. Required for the
|
||||||
Consul backend.
|
Consul backend.
|
||||||
|
|
||||||
* `-pull=true` - Controls if the remote state is pulled before disabling.
|
* `-pull=true` - Controls if the remote state is pulled before disabling
|
||||||
This defaults to true to ensure the latest state is cached before disabling.
|
or after enabling. This defaults to true to ensure the latest state
|
||||||
|
is available under both conditions.
|
||||||
|
|
||||||
* `-state=path` - Path to read state. Defaults to "terraform.tfstate"
|
* `-state=path` - Path to read state. Defaults to "terraform.tfstate"
|
||||||
unless remote state is enabled.
|
unless remote state is enabled.
|
||||||
|
|
Loading…
Reference in New Issue