From 186af0bbb3dce69354ad45d8f416b47623f7225c Mon Sep 17 00:00:00 2001 From: Rob Zienert Date: Sat, 27 Dec 2014 22:33:33 -0600 Subject: [PATCH 01/38] Changing AWS ELB to not ForceNew when listeners change --- builtin/providers/aws/resource_aws_elb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_elb.go b/builtin/providers/aws/resource_aws_elb.go index 11d9bb7b73ac..32b6e37416b6 100644 --- a/builtin/providers/aws/resource_aws_elb.go +++ b/builtin/providers/aws/resource_aws_elb.go @@ -85,7 +85,7 @@ func resourceAwsElb() *schema.Resource { "listener": &schema.Schema{ Type: schema.TypeSet, Required: true, - ForceNew: true, + ForceNew: false, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "instance_port": &schema.Schema{ From 47b244d296c6f80f840caab748cd542d094798b2 Mon Sep 17 00:00:00 2001 From: nevins-b Date: Fri, 23 Jan 2015 09:46:20 -0500 Subject: [PATCH 02/38] adding support for egress rules in AWS Security Groups --- .../aws/resource_aws_security_group.go | 179 +++++++++++++----- .../aws/resource_aws_security_group_test.go | 17 ++ 2 files changed, 147 insertions(+), 49 deletions(-) diff --git a/builtin/providers/aws/resource_aws_security_group.go b/builtin/providers/aws/resource_aws_security_group.go index a5196c890d96..67439cf2e6e1 100644 --- a/builtin/providers/aws/resource_aws_security_group.go +++ b/builtin/providers/aws/resource_aws_security_group.go @@ -81,7 +81,52 @@ func resourceAwsSecurityGroup() *schema.Resource { }, }, }, - Set: resourceAwsSecurityGroupIngressHash, + Set: resourceAwsSecurityGroupRuleHash, + }, + + "egress": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "from_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + + "to_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "cidr_blocks": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "security_groups": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + + "self": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + Set: resourceAwsSecurityGroupRuleHash, }, "owner_id": &schema.Schema{ @@ -139,28 +184,14 @@ func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) er return resourceAwsSecurityGroupUpdate(d, meta) } -func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).ec2conn - - sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())() - if err != nil { - return err - } - if sgRaw == nil { - d.SetId("") - return nil - } - - sg := sgRaw.(*ec2.SecurityGroupInfo) - - // Gather our ingress rules - ingressMap := make(map[string]map[string]interface{}) - for _, perm := range sg.IPPerms { +func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPerm) []map[string]interface{} { + ruleMap := make(map[string]map[string]interface{}) + for _, perm := range permissions { k := fmt.Sprintf("%s-%d-%d", perm.Protocol, perm.FromPort, perm.ToPort) - m, ok := ingressMap[k] + m, ok := ruleMap[k] if !ok { m = make(map[string]interface{}) - ingressMap[k] = m + ruleMap[k] = m } m["from_port"] = perm.FromPort @@ -200,22 +231,15 @@ func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) erro m["security_groups"] = list } } - ingressRules := make([]map[string]interface{}, 0, len(ingressMap)) - for _, m := range ingressMap { - ingressRules = append(ingressRules, m) + rules := make([]map[string]interface{}, 0, len(ruleMap)) + for _, m := range ruleMap { + rules = append(rules, m) } - d.Set("description", sg.Description) - d.Set("name", sg.Name) - d.Set("vpc_id", sg.VpcId) - d.Set("owner_id", sg.OwnerId) - d.Set("ingress", ingressRules) - d.Set("tags", tagsToMap(sg.Tags)) - - return nil + return rules } -func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { ec2conn := meta.(*AWSClient).ec2conn sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())() @@ -226,10 +250,26 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er d.SetId("") return nil } - group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup - if d.HasChange("ingress") { - o, n := d.GetChange("ingress") + sg := sgRaw.(*ec2.SecurityGroupInfo) + + ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPerms) + egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermsEgress) + + d.Set("description", sg.Description) + d.Set("name", sg.Name) + d.Set("vpc_id", sg.VpcId) + d.Set("owner_id", sg.OwnerId) + d.Set("ingress", ingressRules) + d.Set("egress", egressRules) + d.Set("tags", tagsToMap(sg.Tags)) + + return nil +} + +func resourceAwsSecurityGroupUpdateRules(d *schema.ResourceData, ruleset string, meta interface{}, group ec2.SecurityGroup) error { + if d.HasChange(ruleset) { + o, n := d.GetChange(ruleset) if o == nil { o = new(schema.Set) } @@ -252,29 +292,70 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er // adding is easier here, and Terraform should be fast enough to // not have service issues. - if len(remove) > 0 { - // Revoke the old rules - _, err = ec2conn.RevokeSecurityGroup(group, remove) - if err != nil { - return fmt.Errorf("Error authorizing security group ingress rules: %s", err) + if len(remove) > 0 || len(add) > 0 { + + ec2conn := meta.(*AWSClient).ec2conn + + if len(remove) > 0 { + // Revoke the old rules + revoke := ec2conn.RevokeSecurityGroup + if ruleset == "egress" { + revoke = ec2conn.RevokeSecurityGroupEgress + } + _, err := revoke(group, remove) + if err != nil { + return fmt.Errorf("Error revoking security group %s rules: %s", ruleset, err) + } } - } - if len(add) > 0 { - // Authorize the new rules - _, err := ec2conn.AuthorizeSecurityGroup(group, add) - if err != nil { - return fmt.Errorf("Error authorizing security group ingress rules: %s", err) + if len(add) > 0 { + // Authorize the new rules + authorize := ec2conn.AuthorizeSecurityGroup + if ruleset == "egress" { + authorize = ec2conn.AuthorizeSecurityGroupEgress + } + _, err := authorize(group, add) + if err != nil { + return fmt.Errorf("Error authorizing security group %s rules: %s", ruleset, err) + } } } } + return nil +} + +func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).ec2conn + + sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())() + if err != nil { + return err + } + if sgRaw == nil { + d.SetId("") + return nil + } + group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup + + err = resourceAwsSecurityGroupUpdateRules(d, "ingress", ec2conn, group) + if err != nil { + return err + } + + if d.Get("vpc_id") != nil { + err = resourceAwsSecurityGroupUpdateRules(d, "egress", ec2conn, group) + if err != nil { + return err + } + } + if err := setTags(ec2conn, d); err != nil { return err - } else { - d.SetPartial("tags") } + d.SetPartial("tags") + return resourceAwsSecurityGroupRead(d, meta) } @@ -307,7 +388,7 @@ func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) er }) } -func resourceAwsSecurityGroupIngressHash(v interface{}) int { +func resourceAwsSecurityGroupRuleHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) diff --git a/builtin/providers/aws/resource_aws_security_group_test.go b/builtin/providers/aws/resource_aws_security_group_test.go index 1b8773b51b51..d31f9754b24e 100644 --- a/builtin/providers/aws/resource_aws_security_group_test.go +++ b/builtin/providers/aws/resource_aws_security_group_test.go @@ -123,6 +123,16 @@ func TestAccAWSSecurityGroup_vpc(t *testing.T) { "aws_security_group.web", "ingress.332851786.cidr_blocks.#", "1"), resource.TestCheckResourceAttr( "aws_security_group.web", "ingress.332851786.cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckResourceAttr( + "aws_security_group.web", "egress.332851786.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_security_group.web", "egress.332851786.from_port", "80"), + resource.TestCheckResourceAttr( + "aws_security_group.web", "egress.332851786.to_port", "8000"), + resource.TestCheckResourceAttr( + "aws_security_group.web", "egress.332851786.cidr_blocks.#", "1"), + resource.TestCheckResourceAttr( + "aws_security_group.web", "egress.332851786.cidr_blocks.0", "10.0.0.0/8"), testCheck, ), }, @@ -418,6 +428,13 @@ resource "aws_security_group" "web" { to_port = 8000 cidr_blocks = ["10.0.0.0/8"] } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } } ` From 04e86697fb1934ff311746db15dfe27b8fc22f08 Mon Sep 17 00:00:00 2001 From: Phil Frost Date: Thu, 29 Jan 2015 11:06:40 -0500 Subject: [PATCH 03/38] Fix error when refreshing on a deleted AWS subnet If a subnet exists in the state file and a refresh is performed, the read function for subnets would return an error. Now it updates the state to indicate that the subnet no longer exists, so Terraform can plan to recreate it. --- builtin/providers/aws/resource_aws_subnet.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builtin/providers/aws/resource_aws_subnet.go b/builtin/providers/aws/resource_aws_subnet.go index 7bb88f58f3ed..dce9151e6a47 100644 --- a/builtin/providers/aws/resource_aws_subnet.go +++ b/builtin/providers/aws/resource_aws_subnet.go @@ -94,6 +94,11 @@ func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error { resp, err := ec2conn.DescribeSubnets([]string{d.Id()}, ec2.NewFilter()) if err != nil { + if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidSubnetID.NotFound" { + // Update state to indicate the subnet no longer exists. + d.SetId("") + return nil + } return err } if resp == nil { From bfaf8ccee6ec00526a59c3744563941e6eb37ed7 Mon Sep 17 00:00:00 2001 From: Phil Frost Date: Fri, 30 Jan 2015 13:01:10 -0500 Subject: [PATCH 04/38] Support storage_type parameter for aws_db_instance This allows provisioning "gp2" (general purpose SSD) storage for DB instances. --- builtin/providers/aws/resource_aws_db_instance.go | 12 ++++++++++++ .../docs/providers/aws/r/db_instance.html.markdown | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index 845efad59e5d..ff88a1e44c65 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -55,6 +55,13 @@ func resourceAwsDbInstance() *schema.Resource { ForceNew: true, }, + "storage_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "identifier": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -190,6 +197,10 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error EngineVersion: d.Get("engine_version").(string), } + if attr, ok := d.GetOk("storage_type"); ok { + opts.StorageType = attr.(string) + } + if attr, ok := d.GetOk("backup_retention_period"); ok { opts.BackupRetentionPeriod = attr.(int) opts.SetBackupRetentionPeriod = true @@ -296,6 +307,7 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { d.Set("engine", v.Engine) d.Set("engine_version", v.EngineVersion) d.Set("allocated_storage", v.AllocatedStorage) + d.Set("storage_type", v.StorageType) d.Set("instance_class", v.DBInstanceClass) d.Set("availability_zone", v.AvailabilityZone) d.Set("backup_retention_period", v.BackupRetentionPeriod) diff --git a/website/source/docs/providers/aws/r/db_instance.html.markdown b/website/source/docs/providers/aws/r/db_instance.html.markdown index 635d8137f550..43616ba2d348 100644 --- a/website/source/docs/providers/aws/r/db_instance.html.markdown +++ b/website/source/docs/providers/aws/r/db_instance.html.markdown @@ -37,6 +37,9 @@ The following arguments are supported: * `engine_version` - (Required) The engine version to use. * `identifier` - (Required) The name of the RDS instance * `instance_class` - (Required) The instance type of the RDS instance. +* `storage_type` - (Optional) One of "standard" (magnetic), "gp2" (general + purpose SSD), or "io1" (provisioned IOPS SSD). The default is "io1" if + `iops` is specified, "standard" if not. * `final_snapshot_identifier` - (Optional) The name of your final DB snapshot when this DB instance is deleted. If omitted, no final snapshot will be made. @@ -48,7 +51,8 @@ The following arguments are supported: * `availability_zone` - (Optional) The AZ for the RDS instance. * `backup_retention_period` - (Optional) The days to retain backups for. * `backup_window` - (Optional) The backup window. -* `iops` - (Optional) The amount of provisioned IOPS +* `iops` - (Optional) The amount of provisioned IOPS. Setting this implies a + storage_type of "io1". * `maintenance_window` - (Optional) The window to perform maintenance in. * `multi_az` - (Optional) Specifies if the RDS instance is multi-AZ * `port` - (Optional) The port on which the DB accepts connections. From 8eb5418c4a8447c833bb6f730626361a243387d2 Mon Sep 17 00:00:00 2001 From: Phil Frost Date: Fri, 30 Jan 2015 11:00:14 -0500 Subject: [PATCH 05/38] Implement apply_method for RDS parameters This is necessary to support creating parameter groups with parameters that require a reboot, since the RDS API will return an error when attempting to set those parameters with ApplyMethod "immediate". --- .../aws/resource_aws_db_parameter_group.go | 13 +++++++++++++ builtin/providers/aws/structure.go | 4 +--- .../aws/r/db_parameter_group.html.markdown | 3 +++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_db_parameter_group.go b/builtin/providers/aws/resource_aws_db_parameter_group.go index 7253e7793dad..1ecc59be8d8c 100644 --- a/builtin/providers/aws/resource_aws_db_parameter_group.go +++ b/builtin/providers/aws/resource_aws_db_parameter_group.go @@ -48,6 +48,19 @@ func resourceAwsDbParameterGroup() *schema.Resource { Type: schema.TypeString, Required: true, }, + "apply_method": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "immediate", + // this parameter is not actually state, but a + // meta-parameter describing how the RDS API call + // to modify the parameter group should be made. + // Future reads of the resource from AWS don't tell + // us what we used for apply_method previously, so + // by squashing state to an empty string we avoid + // needing to do an update for every future run. + StateFunc: func(interface{}) string { return "" }, + }, }, }, Set: resourceAwsDbParameterHash, diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index 2e634931b187..3ab91611668f 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -99,9 +99,7 @@ func expandParameters(configured []interface{}) ([]rds.Parameter, error) { data := pRaw.(map[string]interface{}) p := rds.Parameter{ - // Only immediate is supported for now; should add in pending-reboot at some point - // but gets tricky as the DescribeParameterGroups AWS call doesn't return this data - ApplyMethod: "immediate", + ApplyMethod: data["apply_method"].(string), ParameterName: data["name"].(string), ParameterValue: data["value"].(string), } diff --git a/website/source/docs/providers/aws/r/db_parameter_group.html.markdown b/website/source/docs/providers/aws/r/db_parameter_group.html.markdown index 855665f66a85..defb698eb7a6 100644 --- a/website/source/docs/providers/aws/r/db_parameter_group.html.markdown +++ b/website/source/docs/providers/aws/r/db_parameter_group.html.markdown @@ -41,6 +41,9 @@ Parameter blocks support the following: * `name` - (Required) The name of the DB parameter. * `value` - (Required) The value of the DB parameter. +* `apply_method` - (Optional) "immediate" (default), or "pending-reboot". Some + engines can't apply some parameters without a reboot, and you will need to + specify "pending-reboot" here. ## Attributes Reference From aa009516b7c3f64288d1be255278b063faf39fd4 Mon Sep 17 00:00:00 2001 From: Dave Cunningham Date: Fri, 30 Jan 2015 14:53:09 -0500 Subject: [PATCH 06/38] Port to oauth2, fix #606 --- builtin/providers/google/config.go | 59 +++++++++++++++------------- builtin/providers/google/provider.go | 2 +- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/builtin/providers/google/config.go b/builtin/providers/google/config.go index edb7add161ba..d08ab9406225 100644 --- a/builtin/providers/google/config.go +++ b/builtin/providers/google/config.go @@ -7,9 +7,12 @@ import ( "net/http" "os" - "code.google.com/p/goauth2/oauth" - "code.google.com/p/goauth2/oauth/jwt" "code.google.com/p/google-api-go-client/compute/v1" + // oauth2 "github.com/rasa/oauth2-fork-b3f9a68" + "github.com/rasa/oauth2-fork-b3f9a68" + + // oauth2 "github.com/rasa/oauth2-fork-b3f9a68/google" + "github.com/rasa/oauth2-fork-b3f9a68/google" ) const clientScopes string = "https://www.googleapis.com/auth/compute" @@ -38,38 +41,40 @@ func (c *Config) loadAndValidate() error { c.Region = os.Getenv("GOOGLE_REGION") } - if err := loadJSON(&account, c.AccountFile); err != nil { - return fmt.Errorf( - "Error loading account file '%s': %s", - c.AccountFile, - err) + var f *oauth2.Options + var err error + + if c.AccountFile != "" { + if err := loadJSON(&account, c.AccountFile); err != nil { + return fmt.Errorf( + "Error loading account file '%s': %s", + c.AccountFile, + err) + } + + // Get the token for use in our requests + log.Printf("[INFO] Requesting Google token...") + log.Printf("[INFO] -- Email: %s", account.ClientEmail) + log.Printf("[INFO] -- Scopes: %s", clientScopes) + log.Printf("[INFO] -- Private Key Length: %d", len(account.PrivateKey)) + + f, err = oauth2.New( + oauth2.JWTClient(account.ClientEmail, []byte(account.PrivateKey)), + oauth2.Scope(clientScopes), + google.JWTEndpoint()) + + } else { + log.Printf("[INFO] Requesting Google token via GCE Service Role...") + f, err = oauth2.New(google.ComputeEngineAccount("")) + } - // Get the token for use in our requests - log.Printf("[INFO] Requesting Google token...") - log.Printf("[INFO] -- Email: %s", account.ClientEmail) - log.Printf("[INFO] -- Scopes: %s", clientScopes) - log.Printf("[INFO] -- Private Key Length: %d", len(account.PrivateKey)) - jwtTok := jwt.NewToken( - account.ClientEmail, - clientScopes, - []byte(account.PrivateKey)) - token, err := jwtTok.Assert(new(http.Client)) if err != nil { return fmt.Errorf("Error retrieving auth token: %s", err) } - // Instantiate the transport to communicate to Google - transport := &oauth.Transport{ - Config: &oauth.Config{ - ClientId: account.ClientId, - Scope: clientScopes, - }, - Token: token, - } - log.Printf("[INFO] Instantiating GCE client...") - c.clientCompute, err = compute.New(transport.Client()) + c.clientCompute, err = compute.New(&http.Client{Transport: f.NewTransport()}) if err != nil { return err } diff --git a/builtin/providers/google/provider.go b/builtin/providers/google/provider.go index 3a16dc0a03a9..4ad5a9a9f2fe 100644 --- a/builtin/providers/google/provider.go +++ b/builtin/providers/google/provider.go @@ -11,7 +11,7 @@ func Provider() terraform.ResourceProvider { Schema: map[string]*schema.Schema{ "account_file": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", nil), }, From 1d41800cd2173798339be882f19b6ad83a178c41 Mon Sep 17 00:00:00 2001 From: Dave Cunningham Date: Fri, 30 Jan 2015 15:12:13 -0500 Subject: [PATCH 07/38] Add docs --- website/source/docs/providers/google/index.html.markdown | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/website/source/docs/providers/google/index.html.markdown b/website/source/docs/providers/google/index.html.markdown index 3014125cfdca..cacf7599bcd5 100644 --- a/website/source/docs/providers/google/index.html.markdown +++ b/website/source/docs/providers/google/index.html.markdown @@ -34,9 +34,11 @@ resource "google_compute_instance" "default" { The following keys can be used to configure the provider. -* `account_file` - (Required) Path to the JSON file used to describe - your account credentials, downloaded from Google Cloud Console. More - details on retrieving this file are below. +* `account_file` - (Required) Path to the JSON file used to describe your + account credentials, downloaded from Google Cloud Console. More details on + retrieving this file are below. The _account file_ can be "" if you + are running terraform from a GCE instance with a properly-configured [Compute + Engine Service Account](https://cloud.google.com/compute/docs/authentication). * `project` - (Required) The name of the project to apply any resources to. From 9fc6b2ee5b5dd4455867dddb53c86aa99872c618 Mon Sep 17 00:00:00 2001 From: Emil Hessman Date: Thu, 29 Jan 2015 09:56:08 +0100 Subject: [PATCH 08/38] make.bat: Makefile-like test functionality for Windows Add make.bat which has the same test functionality as the Makefile. Makes it easier to run tests from the command prompt on Windows platforms. --- make.bat | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 make.bat diff --git a/make.bat b/make.bat new file mode 100644 index 000000000000..3e2dcb145392 --- /dev/null +++ b/make.bat @@ -0,0 +1,98 @@ +@echo off +setlocal + +set _EXITCODE=0 + +REM If no target is provided, default to test. +if [%1]==[] goto test + +set _TARGETS=generate,test,testacc,testrace,updatedeps,vet + +REM Run target. +for %%a in (%_TARGETS%) do (if x%1==x%%a goto %%a) +goto usage + +REM generate runs `go generate` to build the dynamically generated +REM source files. +:generate + go generate ./... + goto :eof + +REM test runs the unit tests and vets the code. +:test + call :testsetup + go test %_TEST% %TESTARGS% -timeout=30s -parallel=4 + call :setMaxExitCode %ERRORLEVEL% + echo. + goto vet + +REM testacc runs acceptance tests. +:testacc + call :testsetup + if x%_TEST% == x./... goto testacc_fail + if x%_TEST% == x.\... goto testacc_fail + set TF_ACC=1 + go test %_TEST% -v %TESTARGS% -timeout 45m + exit /b %ERRORLEVEL% +:testacc_fail + echo ERROR: Set TEST to a specific package. + exit /b 1 + +REM testrace runs the race checker. +:testrace + call :testsetup + go test -race %_TEST% %TESTARGS% + exit /b %ERRORLEVEL% + +REM testsetup calls `go generate` and defines the variables TF_ACC +REM and _TEST. TF_ACC is always cleared. _TEST defaults to the value +REM of the TEST environment variable, provided that TEST is defined, +REM otherwise _TEST it is set to "./...". +:testsetup + call :generate + set TF_ACC= + set _TEST=./... + if defined TEST set _TEST=%TEST% + goto :eof + +REM updatedeps installs all the dependencies that Terraform needs to +REM run and build. +:updatedeps + for /f "tokens=*" %%c in ('git symbolic-ref --short HEAD 2^>nul ^|^| git rev-parse HEAD') do set _REF=%%c + go get -u github.com/mitchellh/gox + go get -u golang.org/x/tools/cmd/stringer + go get -f -u -v ./... + call :setMaxExitCode %ERRORLEVEL% + git checkout %_REF% 1>nul 2>&1 + exit /b %_EXITCODE% + +REM vet runs the Go source code static analysis tool `vet` to find +REM any common errors. +:vet + set _VETARGS=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr + if defined VETARGS set _VETARGS=%VETARGS% + + go tool vet 2>nul + if %ERRORLEVEL% equ 3 go get golang.org/x/tools/cmd/vet + + echo go tool vet %_VETARGS% . + go tool vet %_VETARGS% . + set _vetExitCode=%ERRORLEVEL% + if %_vetExitCode% equ 0 exit /b %_EXITCODE% + call :setMaxExitCode %_vetExitCode% + echo. + echo Vet found suspicious constructs. Please check the reported constructs + echo and fix them if necessary before submitting the code for reviewal. + exit /b %_EXITCODE% + +:setMaxExitCode + if %1 gtr %_EXITCODE% set _EXITCODE=%1 + goto :eof + +:usage + echo usage: make [target] + echo. + echo target is in {%_TARGETS%}. + echo target defaults to test if none is provided. + exit /b 2 + goto :eof From 926effb800038988cc17227c91acf38af07f2a26 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 4 Feb 2015 10:00:03 -0600 Subject: [PATCH 09/38] providers/aws: read ASG termination policies Right now we yield a perpetual diff on ASGs because we're not reading termination policies back out in the provider. This depends on https://github.com/mitchellh/goamz/pull/218 and fixes it. --- builtin/providers/aws/resource_aws_autoscaling_group.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/providers/aws/resource_aws_autoscaling_group.go b/builtin/providers/aws/resource_aws_autoscaling_group.go index dfd6f604a7e6..69faf309cd19 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group.go @@ -197,6 +197,7 @@ func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) e d.Set("max_size", g.MaxSize) d.Set("name", g.Name) d.Set("vpc_zone_identifier", strings.Split(g.VPCZoneIdentifier, ",")) + d.Set("termination_policies", g.TerminationPolicies) return nil } From 6d9c4ea78ff62ff240f08e875db956dc476ca203 Mon Sep 17 00:00:00 2001 From: Emil Hessman Date: Wed, 28 Jan 2015 12:25:49 +0100 Subject: [PATCH 10/38] terraform: fix ContextPlan test failure on Windows The attributes in the diff are %#v-formatted. This means that all `\` characters in the Windows paths are escaped with a `\`. We need to escape the `\` characters in cwd, module, and root before doing any comparison work. Fixes the following test failure on Windows: --- FAIL: TestContextPlan_pathVar (0.00s) context_test.go:3833: bad: DIFF: CREATE: aws_instance.foo cwd: "" => "C:\\Users\\ceh\\src\\github.com\\hashicorp\\terraform\\terraform/barpath" module: "" => "C:\\Users\\ceh\\src\\github.com\\hashicorp\\terraform\\terraform\\test-fixtures\\plan-path-var/foopath" root: "" => "C:\\Users\\ceh\\src\\github.com\\hashicorp\\terraform\\terraform\\test-fixtures\\plan-path-var/barpath" type: "" => "aws_instance" STATE: expected: DIFF: CREATE: aws_instance.foo cwd: "" => "C:\Users\ceh\src\github.com\hashicorp\terraform\terraform/barpath" module: "" => "C:\Users\ceh\src\github.com\hashicorp\terraform\terraform\test-fixtures\plan-path-var/foopath" root: "" => "C:\Users\ceh\src\github.com\hashicorp\terraform\terraform\test-fixtures\plan-path-var/barpath" type: "" => "aws_instance" STATE: FAIL exit status 1 FAIL github.com/hashicorp/terraform/terraform 0.050s --- terraform/context_test.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/terraform/context_test.go b/terraform/context_test.go index b464fed90990..6bae0fdcafd1 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "reflect" + "runtime" "sort" "strings" "sync" @@ -3818,13 +3819,21 @@ func TestContextPlan_pathVar(t *testing.T) { actual := strings.TrimSpace(plan.String()) expected := strings.TrimSpace(testTerraformPlanPathVarStr) + module := m.Config().Dir + root := m.Config().Dir + if runtime.GOOS == "windows" { + // The attributes in the diff are %#v-formatted. This means + // that all `\` characters in the Windows paths are escaped + // with a `\`. We need to escape the `\` characters in cwd, + // module, and root before doing any comparison work. + cwd = strings.Replace(cwd, `\`, `\\`, -1) + module = strings.Replace(module, `\`, `\\`, -1) + root = strings.Replace(root, `\`, `\\`, -1) + } + // Warning: this ordering REALLY matters for this test. The // order is: cwd, module, root. - expected = fmt.Sprintf( - expected, - cwd, - m.Config().Dir, - m.Config().Dir) + expected = fmt.Sprintf(expected, cwd, module, root) if actual != expected { t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected) From e7bbbfb09834789121e2a96a8fd889b8030eed3b Mon Sep 17 00:00:00 2001 From: Emil Hessman Date: Tue, 3 Feb 2015 20:44:34 +0100 Subject: [PATCH 11/38] helper/url: add Windows 'safe' URL Parse wrapper Pull out the urlParse function, which was introduced in config/module, into a helper package. --- config/module/detect.go | 6 ++- config/module/detect_file.go | 15 ++++++ config/module/detect_file_test.go | 6 +-- config/module/get.go | 4 +- config/module/get_hg.go | 4 +- config/module/module_test.go | 3 +- config/module/url_helper.go | 63 ---------------------- helper/url/url.go | 14 +++++ helper/url/url_test.go | 88 +++++++++++++++++++++++++++++++ helper/url/url_unix.go | 11 ++++ helper/url/url_windows.go | 40 ++++++++++++++ 11 files changed, 183 insertions(+), 71 deletions(-) delete mode 100644 config/module/url_helper.go create mode 100644 helper/url/url.go create mode 100644 helper/url/url_test.go create mode 100644 helper/url/url_unix.go create mode 100644 helper/url/url_windows.go diff --git a/config/module/detect.go b/config/module/detect.go index 84e1a1d79357..51e07f725b4d 100644 --- a/config/module/detect.go +++ b/config/module/detect.go @@ -3,6 +3,8 @@ package module import ( "fmt" "path/filepath" + + "github.com/hashicorp/terraform/helper/url" ) // Detector defines the interface that an invalid URL or a URL with a blank @@ -37,7 +39,7 @@ func Detect(src string, pwd string) (string, error) { // Separate out the subdir if there is one, we don't pass that to detect getSrc, subDir := getDirSubdir(getSrc) - u, err := urlParse(getSrc) + u, err := url.Parse(getSrc) if err == nil && u.Scheme != "" { // Valid URL return src, nil @@ -66,7 +68,7 @@ func Detect(src string, pwd string) (string, error) { } } if subDir != "" { - u, err := urlParse(result) + u, err := url.Parse(result) if err != nil { return "", fmt.Errorf("Error parsing URL: %s", err) } diff --git a/config/module/detect_file.go b/config/module/detect_file.go index 4aa21ffa291b..2b8dbacbe6f2 100644 --- a/config/module/detect_file.go +++ b/config/module/detect_file.go @@ -3,6 +3,7 @@ package module import ( "fmt" "path/filepath" + "runtime" ) // FileDetector implements Detector to detect file paths. @@ -23,3 +24,17 @@ func (d *FileDetector) Detect(src, pwd string) (string, bool, error) { } return fmtFileURL(src), true, nil } + +func fmtFileURL(path string) string { + if runtime.GOOS == "windows" { + // Make sure we're using "/" on Windows. URLs are "/"-based. + path = filepath.ToSlash(path) + return fmt.Sprintf("file://%s", path) + } + + // Make sure that we don't start with "/" since we add that below. + if path[0] == '/' { + path = path[1:] + } + return fmt.Sprintf("file:///%s", path) +} diff --git a/config/module/detect_file_test.go b/config/module/detect_file_test.go index d20271bd63a7..4c75ce83d410 100644 --- a/config/module/detect_file_test.go +++ b/config/module/detect_file_test.go @@ -23,8 +23,8 @@ var unixFileTests = []fileTest{ var winFileTests = []fileTest{ {"/foo", "/pwd", "file:///pwd/foo", false}, - {`C:\`, `/pwd`, `file:///C:/`, false}, - {`C:\?bar=baz`, `/pwd`, `file:///C:/?bar=baz`, false}, + {`C:\`, `/pwd`, `file://C:/`, false}, + {`C:\?bar=baz`, `/pwd`, `file://C:/?bar=baz`, false}, } func TestFileDetector(t *testing.T) { @@ -61,7 +61,7 @@ var noPwdUnixFileTests = []fileTest{ var noPwdWinFileTests = []fileTest{ {in: "/foo", pwd: "", out: "", err: true}, - {in: `C:\`, pwd: ``, out: `file:///C:/`, err: false}, + {in: `C:\`, pwd: ``, out: `file://C:/`, err: false}, } func TestFileDetector_noPwd(t *testing.T) { diff --git a/config/module/get.go b/config/module/get.go index abc724b581ad..627d395a9d57 100644 --- a/config/module/get.go +++ b/config/module/get.go @@ -11,6 +11,8 @@ import ( "regexp" "strings" "syscall" + + urlhelper "github.com/hashicorp/terraform/helper/url" ) // Getter defines the interface that schemes must implement to download @@ -72,7 +74,7 @@ func Get(dst, src string) error { dst = tmpDir } - u, err := urlParse(src) + u, err := urlhelper.Parse(src) if err != nil { return err } diff --git a/config/module/get_hg.go b/config/module/get_hg.go index 666762080371..f74c14093249 100644 --- a/config/module/get_hg.go +++ b/config/module/get_hg.go @@ -6,6 +6,8 @@ import ( "os" "os/exec" "runtime" + + urlhelper "github.com/hashicorp/terraform/helper/url" ) // HgGetter is a Getter implementation that will download a module from @@ -17,7 +19,7 @@ func (g *HgGetter) Get(dst string, u *url.URL) error { return fmt.Errorf("hg must be available and on the PATH") } - newURL, err := urlParse(u.String()) + newURL, err := urlhelper.Parse(u.String()) if err != nil { return err } diff --git a/config/module/module_test.go b/config/module/module_test.go index 88a595cd39d1..f1517e4801d4 100644 --- a/config/module/module_test.go +++ b/config/module/module_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/hashicorp/terraform/config" + urlhelper "github.com/hashicorp/terraform/helper/url" ) const fixtureDir = "./test-fixtures" @@ -43,7 +44,7 @@ func testModule(n string) string { } func testModuleURL(n string) *url.URL { - u, err := urlParse(testModule(n)) + u, err := urlhelper.Parse(testModule(n)) if err != nil { panic(err) } diff --git a/config/module/url_helper.go b/config/module/url_helper.go deleted file mode 100644 index 79276192712c..000000000000 --- a/config/module/url_helper.go +++ /dev/null @@ -1,63 +0,0 @@ -package module - -import ( - "fmt" - "net/url" - "path/filepath" - "runtime" - "strings" -) - -func urlParse(rawURL string) (*url.URL, error) { - if runtime.GOOS == "windows" { - // Make sure we're using "/" on Windows. URLs are "/"-based. - rawURL = filepath.ToSlash(rawURL) - } - u, err := url.Parse(rawURL) - if err != nil { - return nil, err - } - - if runtime.GOOS != "windows" { - return u, err - } - - if len(rawURL) > 1 && rawURL[1] == ':' { - // Assume we're dealing with a drive letter file path on Windows. - // We need to adjust the URL Path for drive letter file paths - // because url.Parse("c:/users/user") yields URL Scheme = "c" - // and URL path = "/users/user". - u.Path = fmt.Sprintf("%s:%s", u.Scheme, u.Path) - u.Scheme = "" - } - - if len(u.Host) > 1 && u.Host[1] == ':' && strings.HasPrefix(rawURL, "file://") { - // Assume we're dealing with a drive letter file path on Windows - // where the drive letter has been parsed into the URL Host. - u.Path = fmt.Sprintf("%s%s", u.Host, u.Path) - u.Host = "" - } - - // Remove leading slash for absolute file paths on Windows. - // For example, url.Parse yields u.Path = "/C:/Users/user" for - // rawURL = "file:///C:/Users/user", which is an incorrect syntax. - if len(u.Path) > 2 && u.Path[0] == '/' && u.Path[2] == ':' { - u.Path = u.Path[1:] - } - - return u, err -} - -func fmtFileURL(path string) string { - if runtime.GOOS == "windows" { - // Make sure we're using "/" on Windows. URLs are "/"-based. - path = filepath.ToSlash(path) - } - - // Make sure that we don't start with "/" since we add that below. - if path[0] == '/' { - path = path[1:] - } - - return fmt.Sprintf("file:///%s", path) -} diff --git a/helper/url/url.go b/helper/url/url.go new file mode 100644 index 000000000000..02497c25433b --- /dev/null +++ b/helper/url/url.go @@ -0,0 +1,14 @@ +package url + +import ( + "net/url" +) + +// Parse parses rawURL into a URL structure. +// The rawURL may be relative or absolute. +// +// Parse is a wrapper for the Go stdlib net/url Parse function, but returns +// Windows "safe" URLs on Windows platforms. +func Parse(rawURL string) (*url.URL, error) { + return parse(rawURL) +} diff --git a/helper/url/url_test.go b/helper/url/url_test.go new file mode 100644 index 000000000000..ce3226b4bd83 --- /dev/null +++ b/helper/url/url_test.go @@ -0,0 +1,88 @@ +package url + +import ( + "runtime" + "testing" +) + +type parseTest struct { + rawURL string + scheme string + host string + path string + str string + err bool +} + +var parseTests = []parseTest{ + { + rawURL: "/foo/bar", + scheme: "", + host: "", + path: "/foo/bar", + str: "/foo/bar", + err: false, + }, + { + rawURL: "file:///dir/", + scheme: "file", + host: "", + path: "/dir/", + str: "file:///dir/", + err: false, + }, +} + +var winParseTests = []parseTest{ + { + rawURL: `C:\`, + scheme: ``, + host: ``, + path: `C:/`, + str: `C:/`, + err: false, + }, + { + rawURL: `file://C:\`, + scheme: `file`, + host: ``, + path: `C:/`, + str: `file://C:/`, + err: false, + }, + { + rawURL: `file:///C:\`, + scheme: `file`, + host: ``, + path: `C:/`, + str: `file://C:/`, + err: false, + }, +} + +func TestParse(t *testing.T) { + if runtime.GOOS == "windows" { + parseTests = append(parseTests, winParseTests...) + } + for i, pt := range parseTests { + url, err := Parse(pt.rawURL) + if err != nil && !pt.err { + t.Errorf("test %d: unexpected error: %s", i, err) + } + if err == nil && pt.err { + t.Errorf("test %d: expected an error", i) + } + if url.Scheme != pt.scheme { + t.Errorf("test %d: expected Scheme = %q, got %q", i, pt.scheme, url.Scheme) + } + if url.Host != pt.host { + t.Errorf("test %d: expected Host = %q, got %q", i, pt.host, url.Host) + } + if url.Path != pt.path { + t.Errorf("test %d: expected Path = %q, got %q", i, pt.path, url.Path) + } + if url.String() != pt.str { + t.Errorf("test %d: expected url.String() = %q, got %q", i, pt.str, url.String()) + } + } +} diff --git a/helper/url/url_unix.go b/helper/url/url_unix.go new file mode 100644 index 000000000000..ed1352a9176e --- /dev/null +++ b/helper/url/url_unix.go @@ -0,0 +1,11 @@ +// +build !windows + +package url + +import ( + "net/url" +) + +func parse(rawURL string) (*url.URL, error) { + return url.Parse(rawURL) +} diff --git a/helper/url/url_windows.go b/helper/url/url_windows.go new file mode 100644 index 000000000000..4655226f66b7 --- /dev/null +++ b/helper/url/url_windows.go @@ -0,0 +1,40 @@ +package url + +import ( + "fmt" + "net/url" + "path/filepath" + "strings" +) + +func parse(rawURL string) (*url.URL, error) { + // Make sure we're using "/" since URLs are "/"-based. + rawURL = filepath.ToSlash(rawURL) + + u, err := url.Parse(rawURL) + if err != nil { + return nil, err + } + + if len(rawURL) > 1 && rawURL[1] == ':' { + // Assume we're dealing with a drive letter file path where the drive + // letter has been parsed into the URL Scheme, and the rest of the path + // has been parsed into the URL Path without the leading ':' character. + u.Path = fmt.Sprintf("%s:%s", string(rawURL[0]), u.Path) + u.Scheme = "" + } + + if len(u.Host) > 1 && u.Host[1] == ':' && strings.HasPrefix(rawURL, "file://") { + // Assume we're dealing with a drive letter file path where the drive + // letter has been parsed into the URL Host. + u.Path = fmt.Sprintf("%s%s", u.Host, u.Path) + u.Host = "" + } + + // Remove leading slash for absolute file paths. + if len(u.Path) > 2 && u.Path[0] == '/' && u.Path[2] == ':' { + u.Path = u.Path[1:] + } + + return u, err +} From 2b3015f66a918628d131807391701ed02908462e Mon Sep 17 00:00:00 2001 From: Enrique Garbi Date: Thu, 5 Feb 2015 17:05:14 +0000 Subject: [PATCH 12/38] Added missing line for aws_launch_configuration resource on AWS provider docs --- website/source/docs/providers/aws/r/launch_config.html.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/website/source/docs/providers/aws/r/launch_config.html.markdown b/website/source/docs/providers/aws/r/launch_config.html.markdown index 58ff9197510d..677f3b088084 100644 --- a/website/source/docs/providers/aws/r/launch_config.html.markdown +++ b/website/source/docs/providers/aws/r/launch_config.html.markdown @@ -31,6 +31,7 @@ The following arguments are supported: with launched instances. * `key_name` - (Optional) The key name that should be used for the instance. * `security_groups` - (Optional) A list of associated security group IDS. +* `associate_public_ip_address` - (Optional) Associate a public ip address with an instance in a VPC. * `user_data` - (Optional) The user data to provide when launching the instance. ## Attributes Reference From 106a1c62f5a9a8d0209f935f6fd2a95de163198a Mon Sep 17 00:00:00 2001 From: Dave Cunningham Date: Wed, 11 Feb 2015 01:44:52 -0500 Subject: [PATCH 13/38] Revert to upstream oauth2 --- builtin/providers/google/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/providers/google/config.go b/builtin/providers/google/config.go index d08ab9406225..3d61ad6be140 100644 --- a/builtin/providers/google/config.go +++ b/builtin/providers/google/config.go @@ -9,10 +9,10 @@ import ( "code.google.com/p/google-api-go-client/compute/v1" // oauth2 "github.com/rasa/oauth2-fork-b3f9a68" - "github.com/rasa/oauth2-fork-b3f9a68" + "github.com/golang/oauth2" // oauth2 "github.com/rasa/oauth2-fork-b3f9a68/google" - "github.com/rasa/oauth2-fork-b3f9a68/google" + "github.com/golang/oauth2/google" ) const clientScopes string = "https://www.googleapis.com/auth/compute" From 3366f1924f7c749c4684a81abf061efd23203467 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 11 Feb 2015 13:33:59 +0000 Subject: [PATCH 14/38] Add docs for AWS IG tags --- .../docs/providers/aws/r/internet_gateway.html.markdown | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/source/docs/providers/aws/r/internet_gateway.html.markdown b/website/source/docs/providers/aws/r/internet_gateway.html.markdown index 2207febe00e9..ec79f922a4c9 100644 --- a/website/source/docs/providers/aws/r/internet_gateway.html.markdown +++ b/website/source/docs/providers/aws/r/internet_gateway.html.markdown @@ -15,6 +15,10 @@ Provides a resource to create a VPC Internet Gateway. ``` resource "aws_internet_gateway" "gw" { vpc_id = "${aws_vpc.main.id}" + + tags { + Name = "main" + } } ``` @@ -23,6 +27,7 @@ resource "aws_internet_gateway" "gw" { The following arguments are supported: * `vpc_id` - (Required) The VPC ID to create in. +* `tags` - (Optional) A mapping of tags to assign to the resource. ## Attributes Reference From 408f838db36d68ac59e2473d2235864cf1ac007c Mon Sep 17 00:00:00 2001 From: Clint Date: Wed, 11 Feb 2015 17:52:10 -0800 Subject: [PATCH 15/38] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d504047e9b..88f9eba967a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ BUG FIXES: Fixes issue where specific strings would convert to a negative index and be ommited when creating Route53 records. [GH-967] * provider/aws: ELB subnet change doesn't force new resource. [GH-804] + * provider/aws: Automatically suffix the Route53 zone name on record names. [GH-312] * provider/aws: Instance should ignore root EBS devices. [GH-877] * provider/aws: Fix `aws_db_instance` to not recreate each time. [GH-874] * provider/google: Fix bug preventing instances with metadata from being From 4d280f093130980dd0ac085844c3cbaa4aa472e2 Mon Sep 17 00:00:00 2001 From: Dave Cunningham Date: Wed, 11 Feb 2015 21:21:24 -0500 Subject: [PATCH 16/38] Use new oauth2 golang library --- builtin/providers/google/config.go | 44 ++++++++++++++++++------------ 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/builtin/providers/google/config.go b/builtin/providers/google/config.go index 3d61ad6be140..009f00092b39 100644 --- a/builtin/providers/google/config.go +++ b/builtin/providers/google/config.go @@ -8,14 +8,12 @@ import ( "os" "code.google.com/p/google-api-go-client/compute/v1" - // oauth2 "github.com/rasa/oauth2-fork-b3f9a68" - "github.com/golang/oauth2" - // oauth2 "github.com/rasa/oauth2-fork-b3f9a68/google" - "github.com/golang/oauth2/google" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "golang.org/x/oauth2/jwt" ) -const clientScopes string = "https://www.googleapis.com/auth/compute" // Config is the configuration structure used to instantiate the Google // provider. @@ -41,8 +39,7 @@ func (c *Config) loadAndValidate() error { c.Region = os.Getenv("GOOGLE_REGION") } - var f *oauth2.Options - var err error + var client *http.Client if c.AccountFile != "" { if err := loadJSON(&account, c.AccountFile); err != nil { @@ -52,29 +49,42 @@ func (c *Config) loadAndValidate() error { err) } + clientScopes := []string{"https://www.googleapis.com/auth/compute"} + // Get the token for use in our requests log.Printf("[INFO] Requesting Google token...") log.Printf("[INFO] -- Email: %s", account.ClientEmail) log.Printf("[INFO] -- Scopes: %s", clientScopes) log.Printf("[INFO] -- Private Key Length: %d", len(account.PrivateKey)) - f, err = oauth2.New( - oauth2.JWTClient(account.ClientEmail, []byte(account.PrivateKey)), - oauth2.Scope(clientScopes), - google.JWTEndpoint()) + conf := jwt.Config{ + Email: account.ClientEmail, + PrivateKey: []byte(account.PrivateKey), + Scopes: clientScopes, + TokenURL: "https://accounts.google.com/o/oauth2/token", + } + + // Initiate an http.Client. The following GET request will be + // authorized and authenticated on the behalf of + // your service account. + client = conf.Client(oauth2.NoContext) } else { log.Printf("[INFO] Requesting Google token via GCE Service Role...") - f, err = oauth2.New(google.ComputeEngineAccount("")) - - } + client = &http.Client{ + Transport: &oauth2.Transport{ + // Fetch from Google Compute Engine's metadata server to retrieve + // an access token for the provided account. + // If no account is specified, "default" is used. + Source: google.ComputeTokenSource(""), + }, + } - if err != nil { - return fmt.Errorf("Error retrieving auth token: %s", err) } log.Printf("[INFO] Instantiating GCE client...") - c.clientCompute, err = compute.New(&http.Client{Transport: f.NewTransport()}) + var err error + c.clientCompute, err = compute.New(client) if err != nil { return err } From 0b67ac3c49cf224c32e1d83c85a2111d7f404129 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Fri, 13 Feb 2015 12:52:56 -0600 Subject: [PATCH 17/38] Revert "fix build: upstream azure client change" This reverts commit 6da9f04c10ffc55c1893a1fad6ebae0a62486b8f. --- builtin/providers/azure/resource_virtual_machine.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/builtin/providers/azure/resource_virtual_machine.go b/builtin/providers/azure/resource_virtual_machine.go index 05f6e44ed190..88dd9f9fbdfc 100644 --- a/builtin/providers/azure/resource_virtual_machine.go +++ b/builtin/providers/azure/resource_virtual_machine.go @@ -5,10 +5,9 @@ import ( "fmt" "log" - "github.com/MSOpenTech/azure-sdk-for-go/clients/hostedServiceClient" - "github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" + "github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient" ) func resourceVirtualMachine() *schema.Resource { @@ -221,7 +220,7 @@ func resourceVirtualMachineDelete(d *schema.ResourceData, meta interface{}) erro } log.Printf("[DEBUG] Deleting Azure Hosted Service: %s", d.Id()) - if err := hostedServiceClient.DeleteHostedService(d.Id()); err != nil { + if err := vmClient.DeleteHostedService(d.Id()); err != nil { return fmt.Errorf("Error deleting Azure hosted service: %s", err) } From 971f3462378d8426db57dcf03ffc5502b78e248b Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Fri, 13 Feb 2015 12:53:00 -0600 Subject: [PATCH 18/38] Revert "Update CHANGELOG.md" This reverts commit d78309694a7c4579098233721f7e03259d082021. --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88f9eba967a9..ec19d0776bed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,5 @@ ## 0.3.7 (unreleased) -FEATURES: - - * **New provider: `azure`** - initially just supporting Linux virtual - machines [GH-899] - IMPROVEMENTS: * **New resources: `google_compute_forwarding_rule`, `google_compute_http_health_check`, From fad6f690710ceaa6265372669839134b8a9eaad7 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Fri, 13 Feb 2015 12:53:01 -0600 Subject: [PATCH 19/38] Revert "Add Azure provider" This reverts commit f561e2a6a87aa4640929334c4a91e011bbd57f90. --- builtin/bins/provider-azure/main.go | 12 - builtin/bins/provider-azure/main_test.go | 1 - builtin/providers/azure/config.go | 30 --- builtin/providers/azure/provider.go | 48 ---- builtin/providers/azure/provider_test.go | 35 --- .../azure/resource_virtual_machine.go | 241 ------------------ .../azure/resource_virtual_machine_test.go | 180 ------------- website/source/assets/stylesheets/_docs.scss | 1 - .../docs/providers/azure/index.html.markdown | 37 --- .../azure/r/virtual_machine.html.markdown | 71 ------ website/source/layouts/azure.erb | 26 -- website/source/layouts/docs.erb | 4 - 12 files changed, 686 deletions(-) delete mode 100644 builtin/bins/provider-azure/main.go delete mode 100644 builtin/bins/provider-azure/main_test.go delete mode 100644 builtin/providers/azure/config.go delete mode 100644 builtin/providers/azure/provider.go delete mode 100644 builtin/providers/azure/provider_test.go delete mode 100644 builtin/providers/azure/resource_virtual_machine.go delete mode 100644 builtin/providers/azure/resource_virtual_machine_test.go delete mode 100644 website/source/docs/providers/azure/index.html.markdown delete mode 100644 website/source/docs/providers/azure/r/virtual_machine.html.markdown delete mode 100644 website/source/layouts/azure.erb diff --git a/builtin/bins/provider-azure/main.go b/builtin/bins/provider-azure/main.go deleted file mode 100644 index 45af21656058..000000000000 --- a/builtin/bins/provider-azure/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "github.com/hashicorp/terraform/builtin/providers/azure" - "github.com/hashicorp/terraform/plugin" -) - -func main() { - plugin.Serve(&plugin.ServeOpts{ - ProviderFunc: azure.Provider, - }) -} diff --git a/builtin/bins/provider-azure/main_test.go b/builtin/bins/provider-azure/main_test.go deleted file mode 100644 index 06ab7d0f9a35..000000000000 --- a/builtin/bins/provider-azure/main_test.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/builtin/providers/azure/config.go b/builtin/providers/azure/config.go deleted file mode 100644 index 4f093d59127c..000000000000 --- a/builtin/providers/azure/config.go +++ /dev/null @@ -1,30 +0,0 @@ -package azure - -import ( - "fmt" - "log" - "os" - - azure "github.com/MSOpenTech/azure-sdk-for-go" -) - -type Config struct { - PublishSettingsFile string -} - -func (c *Config) loadAndValidate() error { - if _, err := os.Stat(c.PublishSettingsFile); os.IsNotExist(err) { - return fmt.Errorf( - "Error loading Azure Publish Settings file '%s': %s", - c.PublishSettingsFile, - err) - } - - log.Printf("[INFO] Importing Azure Publish Settings file...") - err := azure.ImportPublishSettingsFile(c.PublishSettingsFile) - if err != nil { - return err - } - - return nil -} diff --git a/builtin/providers/azure/provider.go b/builtin/providers/azure/provider.go deleted file mode 100644 index 199491e37c32..000000000000 --- a/builtin/providers/azure/provider.go +++ /dev/null @@ -1,48 +0,0 @@ -package azure - -import ( - "os" - - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" -) - -func Provider() terraform.ResourceProvider { - return &schema.Provider{ - Schema: map[string]*schema.Schema{ - "publish_settings_file": &schema.Schema{ - Type: schema.TypeString, - Required: true, - DefaultFunc: envDefaultFunc("AZURE_PUBLISH_SETTINGS_FILE"), - }, - }, - - ResourcesMap: map[string]*schema.Resource{ - "azure_virtual_machine": resourceVirtualMachine(), - }, - - ConfigureFunc: providerConfigure, - } -} - -func envDefaultFunc(k string) schema.SchemaDefaultFunc { - return func() (interface{}, error) { - if v := os.Getenv(k); v != "" { - return v, nil - } - - return nil, nil - } -} - -func providerConfigure(d *schema.ResourceData) (interface{}, error) { - config := Config{ - PublishSettingsFile: d.Get("publish_settings_file").(string), - } - - if err := config.loadAndValidate(); err != nil { - return nil, err - } - - return &config, nil -} diff --git a/builtin/providers/azure/provider_test.go b/builtin/providers/azure/provider_test.go deleted file mode 100644 index 4a40c5301139..000000000000 --- a/builtin/providers/azure/provider_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package azure - -import ( - "os" - "testing" - - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" -) - -var testAccProviders map[string]terraform.ResourceProvider -var testAccProvider *schema.Provider - -func init() { - testAccProvider = Provider().(*schema.Provider) - testAccProviders = map[string]terraform.ResourceProvider{ - "azure": testAccProvider, - } -} - -func TestProvider(t *testing.T) { - if err := Provider().(*schema.Provider).InternalValidate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvider_impl(t *testing.T) { - var _ terraform.ResourceProvider = Provider() -} - -func testAccPreCheck(t *testing.T) { - if v := os.Getenv("AZURE_PUBLISH_SETTINGS_FILE"); v == "" { - t.Fatal("AZURE_PUBLISH_SETTINGS_FILE must be set for acceptance tests") - } -} diff --git a/builtin/providers/azure/resource_virtual_machine.go b/builtin/providers/azure/resource_virtual_machine.go deleted file mode 100644 index 88dd9f9fbdfc..000000000000 --- a/builtin/providers/azure/resource_virtual_machine.go +++ /dev/null @@ -1,241 +0,0 @@ -package azure - -import ( - "bytes" - "fmt" - "log" - - "github.com/hashicorp/terraform/helper/hashcode" - "github.com/hashicorp/terraform/helper/schema" - "github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient" -) - -func resourceVirtualMachine() *schema.Resource { - return &schema.Resource{ - Create: resourceVirtualMachineCreate, - Read: resourceVirtualMachineRead, - Delete: resourceVirtualMachineDelete, - - Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "location": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "image": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "size": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "username": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "password": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "", - ForceNew: true, - }, - - "ssh_public_key_file": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "", - ForceNew: true, - }, - - "ssh_port": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, - Default: 22, - ForceNew: true, - }, - - "endpoint": &schema.Schema{ - Type: schema.TypeSet, - Optional: true, - Computed: true, - ForceNew: true, // This can be updatable once we support updates on the resource - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - - "protocol": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - - "port": &schema.Schema{ - Type: schema.TypeInt, - Required: true, - }, - - "local_port": &schema.Schema{ - Type: schema.TypeInt, - Required: true, - }, - }, - }, - Set: resourceVirtualMachineEndpointHash, - }, - - "url": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - - "ip_address": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - - "vip_address": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func resourceVirtualMachineCreate(d *schema.ResourceData, meta interface{}) error { - log.Printf("[DEBUG] Creating Azure Virtual Machine Configuration...") - vmConfig, err := vmClient.CreateAzureVMConfiguration( - d.Get("name").(string), - d.Get("size").(string), - d.Get("image").(string), - d.Get("location").(string)) - if err != nil { - return fmt.Errorf("Error creating Azure virtual machine configuration: %s", err) - } - - // Only Linux VMs are supported. If we want to support other VM types, we need to - // grab the image details and based on the OS add the corresponding configuration. - log.Printf("[DEBUG] Adding Azure Linux Provisioning Configuration...") - vmConfig, err = vmClient.AddAzureLinuxProvisioningConfig( - vmConfig, - d.Get("username").(string), - d.Get("password").(string), - d.Get("ssh_public_key_file").(string), - d.Get("ssh_port").(int)) - if err != nil { - return fmt.Errorf("Error adding Azure linux provisioning configuration: %s", err) - } - - if v := d.Get("endpoint").(*schema.Set); v.Len() > 0 { - log.Printf("[DEBUG] Adding Endpoints to the Azure Virtual Machine...") - endpoints := make([]vmClient.InputEndpoint, v.Len()) - for i, v := range v.List() { - m := v.(map[string]interface{}) - endpoint := vmClient.InputEndpoint{} - endpoint.Name = m["name"].(string) - endpoint.Protocol = m["protocol"].(string) - endpoint.Port = m["port"].(int) - endpoint.LocalPort = m["local_port"].(int) - endpoints[i] = endpoint - } - - configSets := vmConfig.ConfigurationSets.ConfigurationSet - if len(configSets) == 0 { - return fmt.Errorf("Azure virtual machine does not have configuration sets") - } - for i := 0; i < len(configSets); i++ { - if configSets[i].ConfigurationSetType != "NetworkConfiguration" { - continue - } - configSets[i].InputEndpoints.InputEndpoint = - append(configSets[i].InputEndpoints.InputEndpoint, endpoints...) - } - } - - log.Printf("[DEBUG] Creating Azure Virtual Machine...") - err = vmClient.CreateAzureVM( - vmConfig, - d.Get("name").(string), - d.Get("location").(string)) - if err != nil { - return fmt.Errorf("Error creating Azure virtual machine: %s", err) - } - - d.SetId(d.Get("name").(string)) - - return resourceVirtualMachineRead(d, meta) -} - -func resourceVirtualMachineRead(d *schema.ResourceData, meta interface{}) error { - log.Printf("[DEBUG] Getting Azure Virtual Machine Deployment: %s", d.Id()) - VMDeployment, err := vmClient.GetVMDeployment(d.Id(), d.Id()) - if err != nil { - return fmt.Errorf("Error getting Azure virtual machine deployment: %s", err) - } - - d.Set("url", VMDeployment.Url) - - roleInstances := VMDeployment.RoleInstanceList.RoleInstance - if len(roleInstances) == 0 { - return fmt.Errorf("Virtual Machine does not have IP addresses") - } - ipAddress := roleInstances[0].IpAddress - d.Set("ip_address", ipAddress) - - vips := VMDeployment.VirtualIPs.VirtualIP - if len(vips) == 0 { - return fmt.Errorf("Virtual Machine does not have VIP addresses") - } - vip := vips[0].Address - d.Set("vip_address", vip) - - d.SetConnInfo(map[string]string{ - "type": "ssh", - "host": vip, - "user": d.Get("username").(string), - }) - - return nil -} - -func resourceVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error { - log.Printf("[DEBUG] Deleting Azure Virtual Machine Deployment: %s", d.Id()) - if err := vmClient.DeleteVMDeployment(d.Id(), d.Id()); err != nil { - return fmt.Errorf("Error deleting Azure virtual machine deployment: %s", err) - } - - log.Printf("[DEBUG] Deleting Azure Hosted Service: %s", d.Id()) - if err := vmClient.DeleteHostedService(d.Id()); err != nil { - return fmt.Errorf("Error deleting Azure hosted service: %s", err) - } - - d.SetId("") - - return nil -} - -func resourceVirtualMachineEndpointHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) - buf.WriteString(fmt.Sprintf("%d-", m["port"].(int))) - buf.WriteString(fmt.Sprintf("%d-", m["local_port"].(int))) - - return hashcode.String(buf.String()) -} diff --git a/builtin/providers/azure/resource_virtual_machine_test.go b/builtin/providers/azure/resource_virtual_machine_test.go deleted file mode 100644 index c519383d244a..000000000000 --- a/builtin/providers/azure/resource_virtual_machine_test.go +++ /dev/null @@ -1,180 +0,0 @@ -package azure - -import ( - "fmt" - "math/rand" - "testing" - "time" - - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" - "github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient" -) - -func TestAccAzureVirtualMachine_Basic(t *testing.T) { - var VMDeployment vmClient.VMDeployment - - // The VM name can only be used once globally within azure, - // so we need to generate a random one - rand.Seed(time.Now().UnixNano()) - vmName := fmt.Sprintf("tf-test-vm-%d", rand.Int31()) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAzureVirtualMachineDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccCheckAzureVirtualMachineConfig_basic(vmName), - Check: resource.ComposeTestCheckFunc( - testAccCheckAzureVirtualMachineExists("azure_virtual_machine.foobar", &VMDeployment), - testAccCheckAzureVirtualMachineAttributes(&VMDeployment, vmName), - resource.TestCheckResourceAttr( - "azure_virtual_machine.foobar", "name", vmName), - resource.TestCheckResourceAttr( - "azure_virtual_machine.foobar", "location", "West US"), - resource.TestCheckResourceAttr( - "azure_virtual_machine.foobar", "image", "b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04-LTS-amd64-server-20140724-en-us-30GB"), - resource.TestCheckResourceAttr( - "azure_virtual_machine.foobar", "size", "Basic_A1"), - resource.TestCheckResourceAttr( - "azure_virtual_machine.foobar", "username", "foobar"), - ), - }, - }, - }) -} - -func TestAccAzureVirtualMachine_Endpoints(t *testing.T) { - var VMDeployment vmClient.VMDeployment - - // The VM name can only be used once globally within azure, - // so we need to generate a random one - rand.Seed(time.Now().UnixNano()) - vmName := fmt.Sprintf("tf-test-vm-%d", rand.Int31()) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAzureVirtualMachineDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccCheckAzureVirtualMachineConfig_endpoints(vmName), - Check: resource.ComposeTestCheckFunc( - testAccCheckAzureVirtualMachineExists("azure_virtual_machine.foobar", &VMDeployment), - testAccCheckAzureVirtualMachineAttributes(&VMDeployment, vmName), - testAccCheckAzureVirtualMachineEndpoint(&VMDeployment, "tcp", 80), - ), - }, - }, - }) -} - -func testAccCheckAzureVirtualMachineDestroy(s *terraform.State) error { - for _, rs := range s.RootModule().Resources { - if rs.Type != "azure_virtual_machine" { - continue - } - - _, err := vmClient.GetVMDeployment(rs.Primary.ID, rs.Primary.ID) - if err == nil { - return fmt.Errorf("Azure Virtual Machine (%s) still exists", rs.Primary.ID) - } - } - - return nil -} - -func testAccCheckAzureVirtualMachineExists(n string, VMDeployment *vmClient.VMDeployment) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No Azure Virtual Machine ID is set") - } - - retrieveVMDeployment, err := vmClient.GetVMDeployment(rs.Primary.ID, rs.Primary.ID) - if err != nil { - return err - } - - if retrieveVMDeployment.Name != rs.Primary.ID { - return fmt.Errorf("Azure Virtual Machine not found %s %s", VMDeployment.Name, rs.Primary.ID) - } - - *VMDeployment = *retrieveVMDeployment - - return nil - } -} - -func testAccCheckAzureVirtualMachineAttributes(VMDeployment *vmClient.VMDeployment, vmName string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if VMDeployment.Name != vmName { - return fmt.Errorf("Bad name: %s != %s", VMDeployment.Name, vmName) - } - - return nil - } -} - -func testAccCheckAzureVirtualMachineEndpoint(VMDeployment *vmClient.VMDeployment, protocol string, publicPort int) resource.TestCheckFunc { - return func(s *terraform.State) error { - roleInstances := VMDeployment.RoleInstanceList.RoleInstance - if len(roleInstances) == 0 { - return fmt.Errorf("Azure virtual machine does not have role instances") - } - - for i := 0; i < len(roleInstances); i++ { - instanceEndpoints := roleInstances[i].InstanceEndpoints.InstanceEndpoint - if len(instanceEndpoints) == 0 { - return fmt.Errorf("Azure virtual machine does not have endpoints") - } - endpointFound := 0 - for j := 0; i < len(instanceEndpoints); i++ { - if instanceEndpoints[j].Protocol == protocol && instanceEndpoints[j].PublicPort == publicPort { - endpointFound = 1 - break - } - } - if endpointFound == 0 { - return fmt.Errorf("Azure virtual machine does not have endpoint %s/%d", protocol, publicPort) - } - } - - return nil - } -} - -func testAccCheckAzureVirtualMachineConfig_basic(vmName string) string { - return fmt.Sprintf(` -resource "azure_virtual_machine" "foobar" { - name = "%s" - location = "West US" - image = "b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04-LTS-amd64-server-20140724-en-us-30GB" - size = "Basic_A1" - username = "foobar" -} -`, vmName) -} - -func testAccCheckAzureVirtualMachineConfig_endpoints(vmName string) string { - return fmt.Sprintf(` -resource "azure_virtual_machine" "foobar" { - name = "%s" - location = "West US" - image = "b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04-LTS-amd64-server-20140724-en-us-30GB" - size = "Basic_A1" - username = "foobar" - endpoint { - name = "http" - protocol = "tcp" - port = 80 - local_port = 80 - } -} -`, vmName) -} diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index cb1686a6e9f7..a0d2ce807123 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -16,7 +16,6 @@ body.layout-heroku, body.layout-mailgun, body.layout-digitalocean, body.layout-aws, -body.layout-azure, body.layout-docs, body.layout-inner, body.layout-downloads, diff --git a/website/source/docs/providers/azure/index.html.markdown b/website/source/docs/providers/azure/index.html.markdown deleted file mode 100644 index 4991ae6329e4..000000000000 --- a/website/source/docs/providers/azure/index.html.markdown +++ /dev/null @@ -1,37 +0,0 @@ ---- -layout: "azure" -page_title: "Provider: Microsoft Azure" -sidebar_current: "docs-azure-index" -description: |- - The Azure provider is used to interact with Microsoft Azure services. The provider needs to be configured with the proper credentials before it can be used. ---- - -# Azure Provider - -The Azure provider is used to interact with -[Microsoft Azure](http://azure.microsoft.com/). The provider needs -to be configured with the proper credentials before it can be used. - -Use the navigation to the left to read about the available resources. - -## Example Usage - -``` -# Configure the Azure provider -provider "azure" { - publish_settings_file = "account.publishsettings" -} - -# Create a new instance -resource "azure_virtual_machine" "default" { - ... -} -``` - -## Argument Reference - -The following keys can be used to configure the provider. - -* `publish_settings_file` - (Required) Path to the JSON file used to describe - your account settings, downloaded from Microsoft Azure. It must be provided, - but it can also be sourced from the AZURE_PUBLISH_SETTINGS_FILE environment variable. diff --git a/website/source/docs/providers/azure/r/virtual_machine.html.markdown b/website/source/docs/providers/azure/r/virtual_machine.html.markdown deleted file mode 100644 index 946f3b11dd3e..000000000000 --- a/website/source/docs/providers/azure/r/virtual_machine.html.markdown +++ /dev/null @@ -1,71 +0,0 @@ ---- -layout: "azure" -page_title: "Azure: azure_virtual_machine" -sidebar_current: "docs-azure-resource-virtual-machine" -description: |- - Manages a Virtual Machine resource within Azure. ---- - -# azure\_virtual\_machine - -Manages a Virtual Machine resource within Azure. - -## Example Usage - -``` -resource "azure_virtual_machine" "default" { - name = "test" - location = "West US" - image = "b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04-LTS-amd64-server-20140724-en-us-30GB" - size = "Basic_A1" - username = "${var.username}" - password = ""${var.password}" - ssh_public_key_file = "${var.azure_ssh_public_key_file}" - endpoint { - name = "http" - protocol = "tcp" - port = 80 - local_port = 80 - } -} -``` - -## Argument Reference - -The following arguments are supported: - -* `name` - (Required) A name for the virtual machine. It must use between 3 and - 24 lowercase letters and numbers and it must be unique within Azure. - -* `location` - (Required) The location that the virtual machine should be created in. - -* `image` - (Required) A image to be used to create the virtual machine. - -* `size` - (Required) Size that you want to use for the virtual machine. - -* `username` - (Required) Name of the account that you will use to administer - the virtual machine. You cannot use root for the user name. - -* `password` - (Optional) Password for the admin account. - -* `ssh_public_key_file` - (Optional) SSH key (PEM format). - -* `ssh_port` - (Optional) SSH port. - -* `endpoint` - (Optional) Can be specified multiple times for each - endpoint rule. Each endpoint block supports fields documented below. - -The `endpoint` block supports: - -* `name` - (Required) The name of the endpoint. -* `protocol` - (Required) The protocol. -* `port` - (Required) The public port. -* `local_port` - (Required) The private port. - -## Attributes Reference - -The following attributes are exported: - -* `url` - The URL for the virtual machine deployment. -* `ip_address` - The internal IP address of the virtual machine. -* `vip_address` - The public Virtual IP address of the virtual machine. diff --git a/website/source/layouts/azure.erb b/website/source/layouts/azure.erb deleted file mode 100644 index 918a12469d7d..000000000000 --- a/website/source/layouts/azure.erb +++ /dev/null @@ -1,26 +0,0 @@ -<% wrap_layout :inner do %> - <% content_for :sidebar do %> - - <% end %> - - <%= yield %> -<% end %> diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 8e07b61045ff..c71ac5a2ebcf 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -112,10 +112,6 @@ AWS - > - Azure - - > CloudFlare From b7f5f491e242c2e625c402c833ed867e070bd7ee Mon Sep 17 00:00:00 2001 From: Dave Cunningham Date: Fri, 13 Feb 2015 12:55:16 -0500 Subject: [PATCH 20/38] Make Google Instance disk attribute all ForceNew. Fix #608. --- builtin/providers/google/resource_compute_instance.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin/providers/google/resource_compute_instance.go b/builtin/providers/google/resource_compute_instance.go index 578b1a942ac4..020f3de9e7be 100644 --- a/builtin/providers/google/resource_compute_instance.go +++ b/builtin/providers/google/resource_compute_instance.go @@ -54,11 +54,13 @@ func resourceComputeInstance() *schema.Resource { "disk": &schema.Schema{ Type: schema.TypeString, Optional: true, + ForceNew: true, }, "image": &schema.Schema{ Type: schema.TypeString, Optional: true, + ForceNew: true, }, "type": &schema.Schema{ @@ -70,6 +72,7 @@ func resourceComputeInstance() *schema.Resource { "auto_delete": &schema.Schema{ Type: schema.TypeBool, Optional: true, + ForceNew: true, }, }, }, From 729939f122c48d9f476bf682fffc2fa2f5c8fd1c Mon Sep 17 00:00:00 2001 From: Jeremy Voorhis Date: Fri, 13 Feb 2015 21:27:54 -0800 Subject: [PATCH 21/38] Fill in missing outputs in modules.html.md. --- website/source/intro/getting-started/modules.html.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/website/source/intro/getting-started/modules.html.md b/website/source/intro/getting-started/modules.html.md index 138ab6098546..c11c73ef0d69 100644 --- a/website/source/intro/getting-started/modules.html.md +++ b/website/source/intro/getting-started/modules.html.md @@ -83,7 +83,9 @@ With the modules downloaded, we can now plan and apply it. If you run ``` $ terraform plan -TODO +... ++ module.consul + 4 resource(s) ``` As you can see, the module is treated like a black box. In the plan, Terraform @@ -97,7 +99,8 @@ will have some cost associated with it. ``` $ terraform apply -TODO +... +Apply complete! Resources: 3 added, 0 changed, 0 destroyed. ``` After a few minutes, you'll have a three server Consul cluster up and From c4869ef3b1080326b414948683d3423eac47e8f7 Mon Sep 17 00:00:00 2001 From: Jeremy Voorhis Date: Fri, 13 Feb 2015 21:46:40 -0800 Subject: [PATCH 22/38] Add missing preposition. --- website/source/intro/getting-started/dependencies.html.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/intro/getting-started/dependencies.html.md b/website/source/intro/getting-started/dependencies.html.md index 550546d29d26..fe3397afed83 100644 --- a/website/source/intro/getting-started/dependencies.html.md +++ b/website/source/intro/getting-started/dependencies.html.md @@ -112,7 +112,7 @@ to infer dependencies based on usage of attributes of other resources. Using this information, Terraform builds a graph of resources. -This tells Terraform not only what order to create resources, +This tells Terraform not only in what order to create resources, but also what resources can be created in parallel. In our example, since the IP address depended on the EC2 instance, they could not be created in parallel. From 5cdb50a148f8da0f9858a444b86faac23c8bd501 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 09:25:56 -0800 Subject: [PATCH 23/38] Update CHANGELOG --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec19d0776bed..5350db2aab46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,10 @@ IMPROVEMENTS: - * **New resources: `google_compute_forwarding_rule`, `google_compute_http_health_check`, - and `google_compute_target_pool`** - Together these provide network-level + * **New resources: `google_compute_forwarding_rule`, `google_compute_http_health_check`, + and `google_compute_target_pool`** - Together these provide network-level load balancing. [GH-588] - * **New resource: `aws_main_route_table_association`** - Manage the main routing table + * **New resource: `aws_main_route_table_association`** - Manage the main routing table of a VPC. [GH-918] * core: Formalized the syntax of interpolations and documented it very heavily. @@ -17,7 +17,7 @@ IMPROVEMENTS: * provider/aws: The `aws_db_instance` resource no longer requires both `final_snapshot_identifier` and `skip_final_snapshot`; the presence or absence of the former now implies the latter. [GH-874] - * provider/aws: Avoid unecessary update of `aws_subnet` when + * provider/aws: Avoid unecessary update of `aws_subnet` when `map_public_ip_on_launch` is not specified in config. [GH-898] * provider/google: Remove "client secrets file", as it's no longer necessary for API authentication [GH-884]. @@ -35,13 +35,14 @@ BUG FIXES: * command/apply: Won't try to initialize modules in some cases when no arguments are given. [GH-780] * command/apply: Fix regression where user variables weren't asked [GH-736] - * helper/hashcode: Update `hash.String()` to always return a positive index. + * helper/hashcode: Update `hash.String()` to always return a positive index. Fixes issue where specific strings would convert to a negative index and be ommited when creating Route53 records. [GH-967] * provider/aws: ELB subnet change doesn't force new resource. [GH-804] * provider/aws: Automatically suffix the Route53 zone name on record names. [GH-312] * provider/aws: Instance should ignore root EBS devices. [GH-877] * provider/aws: Fix `aws_db_instance` to not recreate each time. [GH-874] + * provider/aws: ASG termination policies are synced with remote state. [GH-923] * provider/google: Fix bug preventing instances with metadata from being created [GH-884]. From 1fe3a2ee13fd13fff6ac77331d316664bea6bfbd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 09:28:59 -0800 Subject: [PATCH 24/38] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5350db2aab46..383e4efc1ecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ BUG FIXES: * provider/aws: Instance should ignore root EBS devices. [GH-877] * provider/aws: Fix `aws_db_instance` to not recreate each time. [GH-874] * provider/aws: ASG termination policies are synced with remote state. [GH-923] + * provider/aws: Add `apply_method` to `aws_db_parameter_group` [GH-897] * provider/google: Fix bug preventing instances with metadata from being created [GH-884]. From 2b2a6376d8bd17b4b91ecb16ee3fb8739acd7ca6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 09:30:27 -0800 Subject: [PATCH 25/38] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 383e4efc1ecd..e3ad37a229a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ BUG FIXES: * provider/aws: Fix `aws_db_instance` to not recreate each time. [GH-874] * provider/aws: ASG termination policies are synced with remote state. [GH-923] * provider/aws: Add `apply_method` to `aws_db_parameter_group` [GH-897] + * provider/aws: Add `storage_type` to `aws_db_instance` [GH-896] * provider/google: Fix bug preventing instances with metadata from being created [GH-884]. From 4811e72a430a4e62692a00c201dc9f4050bd5d51 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 09:31:14 -0800 Subject: [PATCH 26/38] update CHANGELOG --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3ad37a229a7..42ef80bc4111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ IMPROVEMENTS: absence of the former now implies the latter. [GH-874] * provider/aws: Avoid unecessary update of `aws_subnet` when `map_public_ip_on_launch` is not specified in config. [GH-898] + * provider/aws: Add `apply_method` to `aws_db_parameter_group` [GH-897] + * provider/aws: Add `storage_type` to `aws_db_instance` [GH-896] * provider/google: Remove "client secrets file", as it's no longer necessary for API authentication [GH-884]. * provider/google: Expose `self_link` on `google_compute_instance` [GH-906] @@ -43,8 +45,7 @@ BUG FIXES: * provider/aws: Instance should ignore root EBS devices. [GH-877] * provider/aws: Fix `aws_db_instance` to not recreate each time. [GH-874] * provider/aws: ASG termination policies are synced with remote state. [GH-923] - * provider/aws: Add `apply_method` to `aws_db_parameter_group` [GH-897] - * provider/aws: Add `storage_type` to `aws_db_instance` [GH-896] + * provider/aws: No read error when subnet is manually deleted. [GH-889] * provider/google: Fix bug preventing instances with metadata from being created [GH-884]. From 1752c93e0c9597a4cfeca9d74d18079eae25b888 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 09:44:44 -0800 Subject: [PATCH 27/38] update CHANGELOG --- CHANGELOG.md | 1 + builtin/providers/aws/resource_aws_elb.go | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42ef80bc4111..90e17dc06a0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ IMPROVEMENTS: `map_public_ip_on_launch` is not specified in config. [GH-898] * provider/aws: Add `apply_method` to `aws_db_parameter_group` [GH-897] * provider/aws: Add `storage_type` to `aws_db_instance` [GH-896] + * provider/aws: ELB can update listeners without requiring new. [GH-721] * provider/google: Remove "client secrets file", as it's no longer necessary for API authentication [GH-884]. * provider/google: Expose `self_link` on `google_compute_instance` [GH-906] diff --git a/builtin/providers/aws/resource_aws_elb.go b/builtin/providers/aws/resource_aws_elb.go index f6e3b57a8b1e..77cd929eca75 100644 --- a/builtin/providers/aws/resource_aws_elb.go +++ b/builtin/providers/aws/resource_aws_elb.go @@ -79,11 +79,9 @@ func resourceAwsElb() *schema.Resource { }, }, - // TODO: could be not ForceNew "listener": &schema.Schema{ Type: schema.TypeSet, Required: true, - ForceNew: false, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "instance_port": &schema.Schema{ @@ -115,7 +113,6 @@ func resourceAwsElb() *schema.Resource { Set: resourceAwsElbListenerHash, }, - // TODO: could be not ForceNew "health_check": &schema.Schema{ Type: schema.TypeSet, Optional: true, From b7238ca6f2225b10a577f9ae605b245104469d5b Mon Sep 17 00:00:00 2001 From: nevins-b Date: Tue, 17 Feb 2015 13:23:10 -0500 Subject: [PATCH 28/38] adding documentation --- .../aws/r/security_group.html.markdown | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/website/source/docs/providers/aws/r/security_group.html.markdown b/website/source/docs/providers/aws/r/security_group.html.markdown index e408026e5b6d..c8f76a121d7c 100644 --- a/website/source/docs/providers/aws/r/security_group.html.markdown +++ b/website/source/docs/providers/aws/r/security_group.html.markdown @@ -25,6 +25,13 @@ resource "aws_security_group" "allow_all" { protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } + + egress { + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } } ``` @@ -56,6 +63,9 @@ The following arguments are supported: * `description` - (Required) The security group description. * `ingress` - (Optional) Can be specified multiple times for each ingress rule. Each ingress block supports fields documented below. +* `egress` - (Optional) Can be specified multiple times for each + egress rule. Each egress block supports fields documented below. + VPC only. * `vpc_id` - (Optional) The VPC ID. * `owner_id` - (Optional) The AWS Owner ID. @@ -70,6 +80,17 @@ The `ingress` block supports: * `to_port` - (Required) The end range port. * `tags` - (Optional) A mapping of tags to assign to the resource. +The `egress` block supports: + +* `cidr_blocks` - (Optional) List of CIDR blocks. Cannot be used with `security_groups`. +* `from_port` - (Required) The start port. +* `protocol` - (Required) The protocol. +* `security_groups` - (Optional) List of security group IDs. Cannot be used with `cidr_blocks`. +* `self` - (Optional) If true, the security group itself will be added as + a source to this egress rule. +* `to_port` - (Required) The end range port. +* `tags` - (Optional) A mapping of tags to assign to the resource. + ## Attributes Reference The following attributes are exported: @@ -80,3 +101,4 @@ The following attributes are exported: * `name` - The name of the security group * `description` - The description of the security group * `ingress` - The ingress rules. See above for more. +* `egress` - The egress rules. See above for more. From b778a65a83c37ba1e7a90d055a5b9ee0c7f6b5a2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 11:10:45 -0800 Subject: [PATCH 29/38] helper/schema: diff of zero value in state with lack of value should not diff --- helper/schema/schema.go | 3 +++ helper/schema/schema_test.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/helper/schema/schema.go b/helper/schema/schema.go index 81af80de500c..d8b4e8166992 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -810,6 +810,9 @@ func (m schemaMap) diffString( originalN = n n = schema.StateFunc(n) } + if n == nil { + n = schema.Type.Zero() + } if err := mapstructure.WeakDecode(o, &os); err != nil { return fmt.Errorf("%s: %s", k, err) } diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index 80cff8bd6d5b..f918e19377f9 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -1943,6 +1943,29 @@ func TestSchemaMap_Diff(t *testing.T) { Diff: nil, Err: false, }, + + // #48 + { + Schema: map[string]*Schema{ + "port": &Schema{ + Type: TypeBool, + Optional: true, + ForceNew: true, + }, + }, + + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "port": "false", + }, + }, + + Config: map[string]interface{}{}, + + Diff: nil, + + Err: false, + }, } for i, tc := range cases { From cbcfb26ec66749a8b6100cd8fc0db9027e7207f2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 11:12:45 -0800 Subject: [PATCH 30/38] helper/schema: add test for sets --- helper/schema/schema_test.go | 45 +++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index f918e19377f9..91a7d4d4ba9d 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -1944,7 +1944,7 @@ func TestSchemaMap_Diff(t *testing.T) { Err: false, }, - // #48 + // #48 - Zero value in state shouldn't result in diff { Schema: map[string]*Schema{ "port": &Schema{ @@ -1966,6 +1966,49 @@ func TestSchemaMap_Diff(t *testing.T) { Err: false, }, + + // #42 Set - Same as #47 but for sets + { + Schema: map[string]*Schema{ + "route": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "index": &Schema{ + Type: TypeInt, + Required: true, + }, + + "gateway": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Schema{Type: TypeInt}, + Set: func(a interface{}) int { + return a.(int) + }, + }, + }, + }, + Set: func(v interface{}) int { + m := v.(map[string]interface{}) + return m["index"].(int) + }, + }, + }, + + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "route.#": "0", + }, + }, + + Config: map[string]interface{}{}, + + Diff: nil, + + Err: false, + }, } for i, tc := range cases { From c22ba7d3a8383435d4c70ca02ab0a976fd7a558b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 11:14:04 -0800 Subject: [PATCH 31/38] helper/schema: fix test index --- helper/schema/schema_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index 91a7d4d4ba9d..bb32374f389b 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -1967,7 +1967,7 @@ func TestSchemaMap_Diff(t *testing.T) { Err: false, }, - // #42 Set - Same as #47 but for sets + // #49 Set - Same as #47 but for sets { Schema: map[string]*Schema{ "route": &Schema{ From 2212d6895d8bb0cd0aa36a6c54f24457b6fec155 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 11:38:56 -0800 Subject: [PATCH 32/38] helper/schema: diff with set going to 0 elements removes it from state --- helper/schema/field_reader_diff.go | 18 ++++++++- helper/schema/field_reader_diff_test.go | 42 ++++++++++++++++++++ helper/schema/getsource_string.go | 36 +++++++++++++++++ helper/schema/resource_data.go | 14 ------- helper/schema/resource_data_get_source.go | 17 ++++++++ helper/schema/resource_data_test.go | 47 ++++++++++++++++++++--- 6 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 helper/schema/getsource_string.go create mode 100644 helper/schema/resource_data_get_source.go diff --git a/helper/schema/field_reader_diff.go b/helper/schema/field_reader_diff.go index ec875421bb7e..bfac2a0b050a 100644 --- a/helper/schema/field_reader_diff.go +++ b/helper/schema/field_reader_diff.go @@ -144,11 +144,12 @@ func (r *DiffFieldReader) readPrimitive( func (r *DiffFieldReader) readSet( address []string, schema *Schema) (FieldReadResult, error) { + prefix := strings.Join(address, ".") + "." + // Create the set that will be our result set := &Set{F: schema.Set} // Go through the map and find all the set items - prefix := strings.Join(address, ".") + "." for k, _ := range r.Diff.Attributes { if !strings.HasPrefix(k, prefix) { continue @@ -174,8 +175,21 @@ func (r *DiffFieldReader) readSet( set.Add(raw.Value) } + // Determine if the set "exists". It exists if there are items or if + // the diff explicitly wanted it empty. + exists := set.Len() > 0 + if !exists { + // We could check if the diff value is "0" here but I think the + // existence of "#" on its own is enough to show it existed. This + // protects us in the future from the zero value changing from + // "0" to "" breaking us (if that were to happen). + if _, ok := r.Diff.Attributes[prefix+"#"]; ok { + exists = true + } + } + return FieldReadResult{ Value: set, - Exists: set.Len() > 0, + Exists: exists, }, nil } diff --git a/helper/schema/field_reader_diff_test.go b/helper/schema/field_reader_diff_test.go index fbb10fcafbb6..205b254f4062 100644 --- a/helper/schema/field_reader_diff_test.go +++ b/helper/schema/field_reader_diff_test.go @@ -90,6 +90,28 @@ func TestDiffFieldReader_extra(t *testing.T) { return m["index"].(int) }, }, + + "setEmpty": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "index": &Schema{ + Type: TypeInt, + Required: true, + }, + + "value": &Schema{ + Type: TypeString, + Required: true, + }, + }, + }, + Set: func(a interface{}) int { + m := a.(map[string]interface{}) + return m["index"].(int) + }, + }, } r := &DiffFieldReader{ @@ -114,6 +136,11 @@ func TestDiffFieldReader_extra(t *testing.T) { Old: "50", New: "80", }, + + "setEmpty.#": &terraform.ResourceAttrDiff{ + Old: "2", + New: "0", + }, }, }, @@ -131,6 +158,12 @@ func TestDiffFieldReader_extra(t *testing.T) { "setChange.#": "1", "setChange.10.index": "10", "setChange.10.value": "50", + + "setEmpty.#": "2", + "setEmpty.10.index": "10", + "setEmpty.10.value": "50", + "setEmpty.20.index": "20", + "setEmpty.20.value": "50", }), }, } @@ -191,6 +224,15 @@ func TestDiffFieldReader_extra(t *testing.T) { }, false, }, + + "setEmpty": { + []string{"setEmpty"}, + FieldReadResult{ + Value: []interface{}{}, + Exists: true, + }, + false, + }, } for name, tc := range cases { diff --git a/helper/schema/getsource_string.go b/helper/schema/getsource_string.go new file mode 100644 index 000000000000..039bb561a084 --- /dev/null +++ b/helper/schema/getsource_string.go @@ -0,0 +1,36 @@ +// generated by stringer -type=getSource resource_data_get_source.go; DO NOT EDIT + +package schema + +import "fmt" + +const ( + _getSource_name_0 = "getSourceStategetSourceConfig" + _getSource_name_1 = "getSourceDiff" + _getSource_name_2 = "getSourceSet" + _getSource_name_3 = "getSourceLevelMaskgetSourceExact" +) + +var ( + _getSource_index_0 = [...]uint8{0, 14, 29} + _getSource_index_1 = [...]uint8{0, 13} + _getSource_index_2 = [...]uint8{0, 12} + _getSource_index_3 = [...]uint8{0, 18, 32} +) + +func (i getSource) String() string { + switch { + case 1 <= i && i <= 2: + i -= 1 + return _getSource_name_0[_getSource_index_0[i]:_getSource_index_0[i+1]] + case i == 4: + return _getSource_name_1 + case i == 8: + return _getSource_name_2 + case 15 <= i && i <= 16: + i -= 15 + return _getSource_name_3[_getSource_index_3[i]:_getSource_index_3[i+1]] + default: + return fmt.Sprintf("getSource(%d)", i) + } +} diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index 924b9a8777c1..b4feff220193 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -32,20 +32,6 @@ type ResourceData struct { once sync.Once } -// getSource represents the level we want to get for a value (internally). -// Any source less than or equal to the level will be loaded (whichever -// has a value first). -type getSource byte - -const ( - getSourceState getSource = 1 << iota - getSourceConfig - getSourceDiff - getSourceSet - getSourceExact // Only get from the _exact_ level - getSourceLevelMask getSource = getSourceState | getSourceConfig | getSourceDiff | getSourceSet -) - // getResult is the internal structure that is generated when a Get // is called that contains some extra data that might be used. type getResult struct { diff --git a/helper/schema/resource_data_get_source.go b/helper/schema/resource_data_get_source.go new file mode 100644 index 000000000000..7dd655de3d24 --- /dev/null +++ b/helper/schema/resource_data_get_source.go @@ -0,0 +1,17 @@ +package schema + +//go:generate stringer -type=getSource resource_data_get_source.go + +// getSource represents the level we want to get for a value (internally). +// Any source less than or equal to the level will be loaded (whichever +// has a value first). +type getSource byte + +const ( + getSourceState getSource = 1 << iota + getSourceConfig + getSourceDiff + getSourceSet + getSourceExact // Only get from the _exact_ level + getSourceLevelMask getSource = getSourceState | getSourceConfig | getSourceDiff | getSourceSet +) diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go index f7b2118104ad..ff847298fca5 100644 --- a/helper/schema/resource_data_test.go +++ b/helper/schema/resource_data_test.go @@ -648,11 +648,10 @@ func TestResourceDataGet(t *testing.T) { State: &terraform.InstanceState{ Attributes: map[string]string{ - "ratio": "0.5", + "ratio": "0.5", }, }, - Diff: nil, Key: "ratio", @@ -672,11 +671,10 @@ func TestResourceDataGet(t *testing.T) { State: &terraform.InstanceState{ Attributes: map[string]string{ - "ratio": "-0.5", + "ratio": "-0.5", }, }, - Diff: &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ "ratio": &terraform.ResourceAttrDiff{ @@ -686,7 +684,6 @@ func TestResourceDataGet(t *testing.T) { }, }, - Key: "ratio", Value: 33.0, @@ -1533,7 +1530,6 @@ func TestResourceDataSet(t *testing.T) { GetKey: "ratios", GetValue: []interface{}{1.0, 2.2, 5.5}, }, - } for i, tc := range cases { @@ -2500,6 +2496,45 @@ func TestResourceDataState(t *testing.T) { }, }, }, + + // #24 + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeSet, + Optional: true, + Computed: true, + Elem: &Schema{Type: TypeInt}, + Set: func(a interface{}) int { + return a.(int) + }, + }, + }, + + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "ports.#": "3", + "ports.100": "100", + "ports.80": "80", + "ports.81": "81", + }, + }, + + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "ports.#": &terraform.ResourceAttrDiff{ + Old: "3", + New: "0", + }, + }, + }, + + Result: &terraform.InstanceState{ + Attributes: map[string]string{ + "ports.#": "0", + }, + }, + }, } for i, tc := range cases { From c2f3f0594dd5f358df0dff51e0e0b2f2e1689705 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 11:47:53 -0800 Subject: [PATCH 33/38] terraform: sort dependencies of resource state [GH-928] --- CHANGELOG.md | 1 + terraform/state.go | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90e17dc06a0f..c953965f0b86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ BUG FIXES: * core: Fix crash that could occur when there are exactly zero providers installed on a system. [GH-786] * core: JSON TF configurations can configure provisioners. [GH-807] + * core: Sort `depends_on` in state to prevent unnecessary file changes. [GH-928] * command/apply: Won't try to initialize modules in some cases when no arguments are given. [GH-780] * command/apply: Fix regression where user variables weren't asked [GH-736] diff --git a/terraform/state.go b/terraform/state.go index 7535d2a47c16..dea8fe96105c 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -156,6 +156,11 @@ func (s *State) prune() { // sort sorts the modules func (s *State) sort() { sort.Sort(moduleStateSort(s.Modules)) + + // Allow modules to be sorted + for _, m := range s.Modules { + m.sort() + } } func (s *State) GoString() string { @@ -347,6 +352,12 @@ func (m *ModuleState) prune() { } } +func (m *ModuleState) sort() { + for _, v := range m.Resources { + v.sort() + } +} + func (m *ModuleState) GoString() string { return fmt.Sprintf("*%#v", *m) } @@ -515,6 +526,10 @@ func (r *ResourceState) prune() { r.Tainted = r.Tainted[:n] } +func (r *ResourceState) sort() { + sort.Strings(r.Dependencies) +} + func (s *ResourceState) GoString() string { return fmt.Sprintf("*%#v", *s) } From 72a35cb44a8e54dc618ab87e2d8472ffc8ac01a9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 11:52:07 -0800 Subject: [PATCH 34/38] update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c953965f0b86..362f595041bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,10 @@ BUG FIXES: installed on a system. [GH-786] * core: JSON TF configurations can configure provisioners. [GH-807] * core: Sort `depends_on` in state to prevent unnecessary file changes. [GH-928] + * core: State containing the zero value won't cause a diff with the + lack of a value. [GH-952] + * core: If a set type becomes empty, the state will be properly updated + to remove it. [GH-952] * command/apply: Won't try to initialize modules in some cases when no arguments are given. [GH-780] * command/apply: Fix regression where user variables weren't asked [GH-736] From ad6be99f5b717bd4cece59b9e668a03707470697 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 13:15:30 -0800 Subject: [PATCH 35/38] helper/schema: failing test --- helper/schema/schema_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index bb32374f389b..a0fc18c2b7f5 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -2009,6 +2009,29 @@ func TestSchemaMap_Diff(t *testing.T) { Err: false, }, + + // #50 + { + Schema: map[string]*Schema{ + "active": &Schema{ + Type: TypeBool, + Computed: true, + ForceNew: true, + }, + }, + + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "active": "true", + }, + }, + + Config: map[string]interface{}{}, + + Diff: nil, + + Err: false, + }, } for i, tc := range cases { From bcdec738d422de940cdee79fe94d3d407e7341e0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 13:16:59 -0800 Subject: [PATCH 36/38] helper/schema: default the new value to zero only for the decode --- helper/schema/schema.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/helper/schema/schema.go b/helper/schema/schema.go index d8b4e8166992..d781da28285e 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -810,13 +810,14 @@ func (m schemaMap) diffString( originalN = n n = schema.StateFunc(n) } - if n == nil { - n = schema.Type.Zero() + nraw := n + if nraw == nil { + nraw = schema.Type.Zero() } if err := mapstructure.WeakDecode(o, &os); err != nil { return fmt.Errorf("%s: %s", k, err) } - if err := mapstructure.WeakDecode(n, &ns); err != nil { + if err := mapstructure.WeakDecode(nraw, &ns); err != nil { return fmt.Errorf("%s: %s", k, err) } From fd274d7328ea517491d346be5425a48325af4347 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 13:17:23 -0800 Subject: [PATCH 37/38] helper/schema: update test desc --- helper/schema/schema_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index a0fc18c2b7f5..8f6119490de8 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -2010,7 +2010,7 @@ func TestSchemaMap_Diff(t *testing.T) { Err: false, }, - // #50 + // #50 - A set computed element shouldn't cause a diff { Schema: map[string]*Schema{ "active": &Schema{ From cc3c03d3b364860a59c9d0ae4558bdb15a5bd7ce Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Feb 2015 14:00:29 -0800 Subject: [PATCH 38/38] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 362f595041bc..51b552eae48d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ IMPROVEMENTS: * provider/aws: Add `apply_method` to `aws_db_parameter_group` [GH-897] * provider/aws: Add `storage_type` to `aws_db_instance` [GH-896] * provider/aws: ELB can update listeners without requiring new. [GH-721] + * provider/aws: Security group support egress rules. [GH-856] * provider/google: Remove "client secrets file", as it's no longer necessary for API authentication [GH-884]. * provider/google: Expose `self_link` on `google_compute_instance` [GH-906]