From d83ecf9e72d2a0a7ab3299834fef6f2862467639 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Fri, 7 Apr 2017 15:15:18 -0500 Subject: [PATCH 01/23] remove some manual names to allow the automatic random names, avoid some possible conflicts --- builtin/providers/aws/resource_aws_efs_file_system_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/builtin/providers/aws/resource_aws_efs_file_system_test.go b/builtin/providers/aws/resource_aws_efs_file_system_test.go index b242fbf16..93119bb79 100644 --- a/builtin/providers/aws/resource_aws_efs_file_system_test.go +++ b/builtin/providers/aws/resource_aws_efs_file_system_test.go @@ -317,7 +317,6 @@ resource "aws_efs_file_system" "foo" { func testAccAWSEFSFileSystemConfigPagedTags(rInt int) string { return fmt.Sprintf(` resource "aws_efs_file_system" "foo" { - creation_token = "radeksimko" tags { Name = "foo-efs-%d" Another = "tag" @@ -338,7 +337,6 @@ func testAccAWSEFSFileSystemConfigPagedTags(rInt int) string { func testAccAWSEFSFileSystemConfigWithTags(rInt int) string { return fmt.Sprintf(` resource "aws_efs_file_system" "foo-with-tags" { - creation_token = "yada_yada" tags { Name = "foo-efs-%d" Another = "tag" From f521b1531117649ca7e92818f341de200c82d7c2 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Fri, 7 Apr 2017 16:03:26 -0500 Subject: [PATCH 02/23] provider/aws: Fix DB Option group test by skipping backup --- builtin/providers/aws/resource_aws_db_option_group_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/providers/aws/resource_aws_db_option_group_test.go b/builtin/providers/aws/resource_aws_db_option_group_test.go index 3ee5f8197..8e9b22e1b 100644 --- a/builtin/providers/aws/resource_aws_db_option_group_test.go +++ b/builtin/providers/aws/resource_aws_db_option_group_test.go @@ -317,6 +317,7 @@ resource "aws_db_instance" "bar" { maintenance_window = "Fri:09:00-Fri:09:30" backup_retention_period = 0 + skip_final_snapshot = true option_group_name = "${aws_db_option_group.bar.name}" } From 3640bdd6e1ad4717f4069e29e7e179b352b7fc16 Mon Sep 17 00:00:00 2001 From: Ryan Roberts Date: Thu, 7 Apr 2016 17:50:57 +0100 Subject: [PATCH 03/23] AWS IAM OpenID Connect provider http://docs.aws.amazon.com/cli/latest/reference/iam/create-open-id-connect-provider.html Tests currently use a personal google account identity --- ...esource_aws_iam_openid_connect_provider.go | 125 ++++++++++++++++++ ...ce_aws_iam_openid_connect_provider_test.go | 90 +++++++++++++ .../iam_openid_connect_provider.html.markdown | 37 ++++++ 3 files changed, 252 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_iam_openid_connect_provider.go create mode 100644 builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go create mode 100644 website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown diff --git a/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go b/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go new file mode 100644 index 000000000..cd23e86a4 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go @@ -0,0 +1,125 @@ +package aws + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/iam" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsIamOpenIDConnectProvider() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsIamOpenIDConnectProviderCreate, + Read: resourceAwsIamOpenIDConnectProviderRead, + Update: resourceAwsIamOpenIDConnectProviderUpdate, + Delete: resourceAwsIamOpenIDConnectProviderDelete, + + Schema: map[string]*schema.Schema{ + "arn": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "url": &schema.Schema{ + Type: schema.TypeString, + Computed: false, + Required: true, + ForceNew: true, + }, + "client-id-list": &schema.Schema{ + Elem: &schema.Schema{Type: schema.TypeString}, + Type: schema.TypeList, + Required: true, + ForceNew: true, + }, + "thumbprint-list": &schema.Schema{ + Elem: &schema.Schema{Type: schema.TypeString}, + Type: schema.TypeList, + Required: true, + }, + }, + } +} + +func stringListToStringSlice(stringList []interface{}) []string { + ret := []string{} + for _, v := range stringList { + ret = append(ret, v.(string)) + } + return ret +} + +func resourceAwsIamOpenIDConnectProviderCreate(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + + input := &iam.CreateOpenIDConnectProviderInput{ + Url: aws.String(d.Get("url").(string)), + ClientIDList: aws.StringSlice(stringListToStringSlice(d.Get("client-id-list").([]interface{}))), + ThumbprintList: aws.StringSlice(stringListToStringSlice(d.Get("thumbprint-list").([]interface{}))), + } + + out, err := iamconn.CreateOpenIDConnectProvider(input) + if err != nil { + return err + } + + d.SetId(*out.OpenIDConnectProviderArn) + + return resourceAwsIamOpenIDConnectProviderRead(d, meta) +} + +func resourceAwsIamOpenIDConnectProviderRead(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + + input := &iam.GetOpenIDConnectProviderInput{ + OpenIDConnectProviderArn: aws.String(d.Id()), + } + out, err := iamconn.GetOpenIDConnectProvider(input) + if err != nil { + return err + } + + d.Set("arn", d.Id()) + d.Set("url", *out.Url) + d.Set("client-id-list", out.ClientIDList) + d.Set("thumbprint-list", out.ThumbprintList) + + return nil +} + +func resourceAwsIamOpenIDConnectProviderUpdate(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + + if d.HasChange("thumbprint-list") { + input := &iam.UpdateOpenIDConnectProviderThumbprintInput{ + OpenIDConnectProviderArn: aws.String(d.Id()), + } + + _, err := iamconn.UpdateOpenIDConnectProviderThumbprint(input) + if err != nil { + return err + } + } + + return resourceAwsIamOpenIDConnectProviderRead(d, meta) +} + +func resourceAwsIamOpenIDConnectProviderDelete(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + + input := &iam.DeleteOpenIDConnectProviderInput{ + OpenIDConnectProviderArn: aws.String(d.Id()), + } + _, err := iamconn.DeleteOpenIDConnectProvider(input) + + if err != nil { + if err, ok := err.(awserr.Error); ok && err.Code() == "NotFound" { + return nil + } + return fmt.Errorf("Error deleting platform application %s", err) + } + + return nil +} diff --git a/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go b/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go new file mode 100644 index 000000000..c4eda023b --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go @@ -0,0 +1,90 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSIAMOpenIDConnectProvider_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIAMOpenIDConnectProviderDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccIAMOpenIDConnectProviderConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMOpenIDConnectProvider("aws_iam_openid_connect_provider.goog"), + ), + }, + }, + }) +} + +func testAccCheckIAMOpenIDConnectProviderDestroy(s *terraform.State) error { + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iam_openid_connect_provider" { + continue + } + + input := &iam.GetOpenIDConnectProviderInput{ + OpenIDConnectProviderArn: aws.String(rs.Primary.ID), + } + out, err := iamconn.GetOpenIDConnectProvider(input) + if err != nil { + if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { + // none found, that's good + return nil + } + return fmt.Errorf("Error reading IAM OpenID Connect Provider, out: %s, err: %s", out, err) + } + + if out != nil { + return fmt.Errorf("Found IAM OpenID Connect Provider, expected none: %s", out) + } + } + + return nil +} + +func testAccCheckIAMOpenIDConnectProvider(id string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[id] + if !ok { + return fmt.Errorf("Not Found: %s", id) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + _, err := iamconn.GetOpenIDConnectProvider(&iam.GetOpenIDConnectProviderInput{ + OpenIDConnectProviderArn: aws.String(rs.Primary.ID), + }) + + if err != nil { + return err + } + + return nil + } +} + +const testAccIAMOpenIDConnectProviderConfig = ` +resource "aws_iam_openid_connect_provider" "goog" { + url="https://accounts.google.com" + client-id-list = [ + "266362248691-re108qaeld573ia0l6clj2i5ac7r7291.apps.googleusercontent.com" + ] + thumbprint-list = [] +} +` diff --git a/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown b/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown new file mode 100644 index 000000000..76a5a8bf8 --- /dev/null +++ b/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "aws" +page_title: "AWS: aws_iam_openid_connect_provider" +sidebar_current: "docs-aws-resource-iam-openid-connect-provider" +description: |- + Provides an IAM OpenID Connect provider. +--- + +# aws\_iam\_openid\_connect\_provider + +Provides an IAM OpenID Connect provider. + +## Example Usage + +``` +resource "aws_iam_openid_connect_provider" "default" { + url = "https://accounts.google.com" + client-id-list = [ + "266362248691-342342xasdasdasda-apps.googleusercontent.com" + ] + thumbprint-list = [] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `url` - (Required) The URL of the identity provider. Corresponds to the _iss_ claim. +* `client-id-list` - (Required) A list of client IDs (also known as audiences). When a mobile or web app registers with an OpenID Connect provider, they establish a value that identifies the application. (This is the value that's sent as the client_id parameter on OAuth requests.) +* `thumbprint-list` - (Required) A list of server certificate thumbprints for the OpenID Connect (OIDC) identity provider's server certificate(s). + +## Attributes Reference + +The following attributes are exported: + +* `arn` - The ARN assigned by AWS for this provider. From 668a09aa00dfcdc057d3edf01c4342ff5ffca24b Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 6 Apr 2017 10:26:32 +0100 Subject: [PATCH 04/23] provider/aws: Add diff suppression & validation for OpenID URL --- builtin/providers/aws/diff_suppress_funcs.go | 17 ++++++++++ builtin/providers/aws/provider.go | 1 + ...esource_aws_iam_openid_connect_provider.go | 10 +++--- builtin/providers/aws/validators.go | 17 ++++++++++ builtin/providers/aws/validators_test.go | 32 +++++++++++++++++++ 5 files changed, 73 insertions(+), 4 deletions(-) diff --git a/builtin/providers/aws/diff_suppress_funcs.go b/builtin/providers/aws/diff_suppress_funcs.go index 5c96c6696..e8c58b813 100644 --- a/builtin/providers/aws/diff_suppress_funcs.go +++ b/builtin/providers/aws/diff_suppress_funcs.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "log" + "net/url" "strings" "github.com/hashicorp/terraform/helper/schema" @@ -58,3 +59,19 @@ func suppressEquivalentJsonDiffs(k, old, new string, d *schema.ResourceData) boo return jsonBytesEqual(ob.Bytes(), nb.Bytes()) } + +func suppressOpenIdURL(k, old, new string, d *schema.ResourceData) bool { + oldUrl, err := url.Parse(old) + if err != nil { + return false + } + + newUrl, err := url.Parse(new) + if err != nil { + return false + } + + oldUrl.Scheme = "https" + + return oldUrl.String() == newUrl.String() +} diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index f97908772..67e813a9e 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -309,6 +309,7 @@ func Provider() terraform.ResourceProvider { "aws_iam_group_membership": resourceAwsIamGroupMembership(), "aws_iam_group_policy_attachment": resourceAwsIamGroupPolicyAttachment(), "aws_iam_instance_profile": resourceAwsIamInstanceProfile(), + "aws_iam_openid_connect_provider": resourceAwsIamOpenIDConnectProvider(), "aws_iam_policy": resourceAwsIamPolicy(), "aws_iam_policy_attachment": resourceAwsIamPolicyAttachment(), "aws_iam_role_policy_attachment": resourceAwsIamRolePolicyAttachment(), diff --git a/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go b/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go index cd23e86a4..fa4530fa8 100644 --- a/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go +++ b/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go @@ -23,10 +23,12 @@ func resourceAwsIamOpenIDConnectProvider() *schema.Resource { Computed: true, }, "url": &schema.Schema{ - Type: schema.TypeString, - Computed: false, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Computed: false, + Required: true, + ForceNew: true, + ValidateFunc: validateOpenIdURL, + DiffSuppressFunc: suppressOpenIdURL, }, "client-id-list": &schema.Schema{ Elem: &schema.Schema{Type: schema.TypeString}, diff --git a/builtin/providers/aws/validators.go b/builtin/providers/aws/validators.go index b2f934646..425a747d5 100644 --- a/builtin/providers/aws/validators.go +++ b/builtin/providers/aws/validators.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "net" + "net/url" "regexp" "strings" "time" @@ -1170,3 +1171,19 @@ func validateAwsAlbTargetGroupNamePrefix(v interface{}, k string) (ws []string, } return } + +func validateOpenIdURL(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + u, err := url.Parse(value) + if err != nil { + errors = append(errors, fmt.Errorf("%q has to be a valid URL", k)) + return + } + if u.Scheme != "https" { + errors = append(errors, fmt.Errorf("%q has to use HTTPS scheme (i.e. begin with https://)", k)) + } + if len(u.Query()) > 0 { + errors = append(errors, fmt.Errorf("%q cannot contain query parameters per the OIDC standard", k)) + } + return +} diff --git a/builtin/providers/aws/validators_test.go b/builtin/providers/aws/validators_test.go index 7fe451a87..0af2b49fa 100644 --- a/builtin/providers/aws/validators_test.go +++ b/builtin/providers/aws/validators_test.go @@ -1913,3 +1913,35 @@ func TestValidateDbOptionGroupNamePrefix(t *testing.T) { } } } + +func TestValidateOpenIdURL(t *testing.T) { + cases := []struct { + Value string + ErrCount int + }{ + { + Value: "http://wrong.scheme.com", + ErrCount: 1, + }, + { + Value: "ftp://wrong.scheme.co.uk", + ErrCount: 1, + }, + { + Value: "%@invalidUrl", + ErrCount: 1, + }, + { + Value: "https://example.com/?query=param", + ErrCount: 1, + }, + } + + for _, tc := range cases { + _, errors := validateOpenIdURL(tc.Value, "url") + + if len(errors) != tc.ErrCount { + t.Fatalf("Expected %d of OpenID URL validation errors, got %d", tc.ErrCount, len(errors)) + } + } +} From caa82d70ca484eb06a5c86266497979b34301097 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 6 Apr 2017 10:36:52 +0100 Subject: [PATCH 05/23] provider/aws: Align field names with conventions (lowercase+underscore) --- .../resource_aws_iam_openid_connect_provider.go | 16 ++++++++-------- ...ource_aws_iam_openid_connect_provider_test.go | 4 ++-- .../r/iam_openid_connect_provider.html.markdown | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go b/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go index fa4530fa8..bb1505465 100644 --- a/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go +++ b/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go @@ -30,13 +30,13 @@ func resourceAwsIamOpenIDConnectProvider() *schema.Resource { ValidateFunc: validateOpenIdURL, DiffSuppressFunc: suppressOpenIdURL, }, - "client-id-list": &schema.Schema{ + "client_id_list": &schema.Schema{ Elem: &schema.Schema{Type: schema.TypeString}, Type: schema.TypeList, Required: true, ForceNew: true, }, - "thumbprint-list": &schema.Schema{ + "thumbprint_list": &schema.Schema{ Elem: &schema.Schema{Type: schema.TypeString}, Type: schema.TypeList, Required: true, @@ -58,8 +58,8 @@ func resourceAwsIamOpenIDConnectProviderCreate(d *schema.ResourceData, meta inte input := &iam.CreateOpenIDConnectProviderInput{ Url: aws.String(d.Get("url").(string)), - ClientIDList: aws.StringSlice(stringListToStringSlice(d.Get("client-id-list").([]interface{}))), - ThumbprintList: aws.StringSlice(stringListToStringSlice(d.Get("thumbprint-list").([]interface{}))), + ClientIDList: aws.StringSlice(stringListToStringSlice(d.Get("client_id_list").([]interface{}))), + ThumbprintList: aws.StringSlice(stringListToStringSlice(d.Get("thumbprint_list").([]interface{}))), } out, err := iamconn.CreateOpenIDConnectProvider(input) @@ -84,9 +84,9 @@ func resourceAwsIamOpenIDConnectProviderRead(d *schema.ResourceData, meta interf } d.Set("arn", d.Id()) - d.Set("url", *out.Url) - d.Set("client-id-list", out.ClientIDList) - d.Set("thumbprint-list", out.ThumbprintList) + d.Set("url", out.Url) + d.Set("client_id_list", out.ClientIDList) + d.Set("thumbprint_list", out.ThumbprintList) return nil } @@ -94,7 +94,7 @@ func resourceAwsIamOpenIDConnectProviderRead(d *schema.ResourceData, meta interf func resourceAwsIamOpenIDConnectProviderUpdate(d *schema.ResourceData, meta interface{}) error { iamconn := meta.(*AWSClient).iamconn - if d.HasChange("thumbprint-list") { + if d.HasChange("thumbprint_list") { input := &iam.UpdateOpenIDConnectProviderThumbprintInput{ OpenIDConnectProviderArn: aws.String(d.Id()), } diff --git a/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go b/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go index c4eda023b..11f034d87 100644 --- a/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go +++ b/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go @@ -82,9 +82,9 @@ func testAccCheckIAMOpenIDConnectProvider(id string) resource.TestCheckFunc { const testAccIAMOpenIDConnectProviderConfig = ` resource "aws_iam_openid_connect_provider" "goog" { url="https://accounts.google.com" - client-id-list = [ + client_id_list = [ "266362248691-re108qaeld573ia0l6clj2i5ac7r7291.apps.googleusercontent.com" ] - thumbprint-list = [] + thumbprint_list = [] } ` diff --git a/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown b/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown index 76a5a8bf8..194a080c6 100644 --- a/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown +++ b/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown @@ -15,10 +15,10 @@ Provides an IAM OpenID Connect provider. ``` resource "aws_iam_openid_connect_provider" "default" { url = "https://accounts.google.com" - client-id-list = [ + client_id_list = [ "266362248691-342342xasdasdasda-apps.googleusercontent.com" ] - thumbprint-list = [] + thumbprint_list = [] } ``` @@ -27,8 +27,8 @@ resource "aws_iam_openid_connect_provider" "default" { The following arguments are supported: * `url` - (Required) The URL of the identity provider. Corresponds to the _iss_ claim. -* `client-id-list` - (Required) A list of client IDs (also known as audiences). When a mobile or web app registers with an OpenID Connect provider, they establish a value that identifies the application. (This is the value that's sent as the client_id parameter on OAuth requests.) -* `thumbprint-list` - (Required) A list of server certificate thumbprints for the OpenID Connect (OIDC) identity provider's server certificate(s). +* `client_id_list` - (Required) A list of client IDs (also known as audiences). When a mobile or web app registers with an OpenID Connect provider, they establish a value that identifies the application. (This is the value that's sent as the client_id parameter on OAuth requests.) +* `thumbprint_list` - (Required) A list of server certificate thumbprints for the OpenID Connect (OIDC) identity provider's server certificate(s). ## Attributes Reference From ab4b06a95dad6908cc2216cf0546ad213f7f7ed3 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 6 Apr 2017 10:58:03 +0100 Subject: [PATCH 06/23] aws: Fix OID connect provider updates + simplify + add tests --- ...esource_aws_iam_openid_connect_provider.go | 13 ++---- ...ce_aws_iam_openid_connect_provider_test.go | 44 +++++++++++++++++-- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go b/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go index bb1505465..0e25bca14 100644 --- a/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go +++ b/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go @@ -45,21 +45,13 @@ func resourceAwsIamOpenIDConnectProvider() *schema.Resource { } } -func stringListToStringSlice(stringList []interface{}) []string { - ret := []string{} - for _, v := range stringList { - ret = append(ret, v.(string)) - } - return ret -} - func resourceAwsIamOpenIDConnectProviderCreate(d *schema.ResourceData, meta interface{}) error { iamconn := meta.(*AWSClient).iamconn input := &iam.CreateOpenIDConnectProviderInput{ Url: aws.String(d.Get("url").(string)), - ClientIDList: aws.StringSlice(stringListToStringSlice(d.Get("client_id_list").([]interface{}))), - ThumbprintList: aws.StringSlice(stringListToStringSlice(d.Get("thumbprint_list").([]interface{}))), + ClientIDList: expandStringList(d.Get("client_id_list").([]interface{})), + ThumbprintList: expandStringList(d.Get("thumbprint_list").([]interface{})), } out, err := iamconn.CreateOpenIDConnectProvider(input) @@ -97,6 +89,7 @@ func resourceAwsIamOpenIDConnectProviderUpdate(d *schema.ResourceData, meta inte if d.HasChange("thumbprint_list") { input := &iam.UpdateOpenIDConnectProviderThumbprintInput{ OpenIDConnectProviderArn: aws.String(d.Id()), + ThumbprintList: expandStringList(d.Get("thumbprint_list").([]interface{})), } _, err := iamconn.UpdateOpenIDConnectProviderThumbprint(input) diff --git a/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go b/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go index 11f034d87..6fb72aa90 100644 --- a/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go +++ b/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go @@ -7,20 +7,42 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) func TestAccAWSIAMOpenIDConnectProvider_basic(t *testing.T) { + rString := acctest.RandString(5) + url := "accounts.google.com/" + rString + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckIAMOpenIDConnectProviderDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccIAMOpenIDConnectProviderConfig, + Config: testAccIAMOpenIDConnectProviderConfig(rString), Check: resource.ComposeTestCheckFunc( testAccCheckIAMOpenIDConnectProvider("aws_iam_openid_connect_provider.goog"), + resource.TestCheckResourceAttr("aws_iam_openid_connect_provider.goog", "url", url), + resource.TestCheckResourceAttr("aws_iam_openid_connect_provider.goog", "client_id_list.#", "1"), + resource.TestCheckResourceAttr("aws_iam_openid_connect_provider.goog", "client_id_list.0", + "266362248691-re108qaeld573ia0l6clj2i5ac7r7291.apps.googleusercontent.com"), + resource.TestCheckResourceAttr("aws_iam_openid_connect_provider.goog", "thumbprint_list.#", "0"), + ), + }, + resource.TestStep{ + Config: testAccIAMOpenIDConnectProviderConfig_modified(rString), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMOpenIDConnectProvider("aws_iam_openid_connect_provider.goog"), + resource.TestCheckResourceAttr("aws_iam_openid_connect_provider.goog", "url", url), + resource.TestCheckResourceAttr("aws_iam_openid_connect_provider.goog", "client_id_list.#", "1"), + resource.TestCheckResourceAttr("aws_iam_openid_connect_provider.goog", "client_id_list.0", + "266362248691-re108qaeld573ia0l6clj2i5ac7r7291.apps.googleusercontent.com"), + resource.TestCheckResourceAttr("aws_iam_openid_connect_provider.goog", "thumbprint_list.#", "2"), + resource.TestCheckResourceAttr("aws_iam_openid_connect_provider.goog", "thumbprint_list.0", "cf23df2207d99a74fbe169e3eba035e633b65d94"), + resource.TestCheckResourceAttr("aws_iam_openid_connect_provider.goog", "thumbprint_list.1", "c784713d6f9cb67b55dd84f4e4af7832d42b8f55"), ), }, }, @@ -79,12 +101,26 @@ func testAccCheckIAMOpenIDConnectProvider(id string) resource.TestCheckFunc { } } -const testAccIAMOpenIDConnectProviderConfig = ` +func testAccIAMOpenIDConnectProviderConfig(rString string) string { + return fmt.Sprintf(` resource "aws_iam_openid_connect_provider" "goog" { - url="https://accounts.google.com" + url="https://accounts.google.com/%s" client_id_list = [ "266362248691-re108qaeld573ia0l6clj2i5ac7r7291.apps.googleusercontent.com" ] thumbprint_list = [] } -` +`, rString) +} + +func testAccIAMOpenIDConnectProviderConfig_modified(rString string) string { + return fmt.Sprintf(` +resource "aws_iam_openid_connect_provider" "goog" { + url="https://accounts.google.com/%s" + client_id_list = [ + "266362248691-re108qaeld573ia0l6clj2i5ac7r7291.apps.googleusercontent.com" + ] + thumbprint_list = ["cf23df2207d99a74fbe169e3eba035e633b65d94", "c784713d6f9cb67b55dd84f4e4af7832d42b8f55"] +} +`, rString) +} From affdc76d4cda00435f0ad9cf0ceff923f899023b Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 6 Apr 2017 12:23:07 +0100 Subject: [PATCH 07/23] aws: Allow import of OID connect provider + allow disappearance --- ...esource_aws_iam_openid_connect_provider.go | 27 +++++++- ...ce_aws_iam_openid_connect_provider_test.go | 61 +++++++++++++++++++ .../iam_openid_connect_provider.html.markdown | 8 +++ website/source/layouts/aws.erb | 4 ++ 4 files changed, 97 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go b/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go index 0e25bca14..1791da4ec 100644 --- a/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go +++ b/builtin/providers/aws/resource_aws_iam_openid_connect_provider.go @@ -16,6 +16,10 @@ func resourceAwsIamOpenIDConnectProvider() *schema.Resource { Read: resourceAwsIamOpenIDConnectProviderRead, Update: resourceAwsIamOpenIDConnectProviderUpdate, Delete: resourceAwsIamOpenIDConnectProviderDelete, + Exists: resourceAwsIamOpenIDConnectProviderExists, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "arn": &schema.Schema{ @@ -77,8 +81,8 @@ func resourceAwsIamOpenIDConnectProviderRead(d *schema.ResourceData, meta interf d.Set("arn", d.Id()) d.Set("url", out.Url) - d.Set("client_id_list", out.ClientIDList) - d.Set("thumbprint_list", out.ThumbprintList) + d.Set("client_id_list", flattenStringList(out.ClientIDList)) + d.Set("thumbprint_list", flattenStringList(out.ThumbprintList)) return nil } @@ -110,7 +114,7 @@ func resourceAwsIamOpenIDConnectProviderDelete(d *schema.ResourceData, meta inte _, err := iamconn.DeleteOpenIDConnectProvider(input) if err != nil { - if err, ok := err.(awserr.Error); ok && err.Code() == "NotFound" { + if err, ok := err.(awserr.Error); ok && err.Code() == "NoSuchEntity" { return nil } return fmt.Errorf("Error deleting platform application %s", err) @@ -118,3 +122,20 @@ func resourceAwsIamOpenIDConnectProviderDelete(d *schema.ResourceData, meta inte return nil } + +func resourceAwsIamOpenIDConnectProviderExists(d *schema.ResourceData, meta interface{}) (bool, error) { + iamconn := meta.(*AWSClient).iamconn + + input := &iam.GetOpenIDConnectProviderInput{ + OpenIDConnectProviderArn: aws.String(d.Id()), + } + _, err := iamconn.GetOpenIDConnectProvider(input) + if err != nil { + if err, ok := err.(awserr.Error); ok && err.Code() == "NoSuchEntity" { + return false, nil + } + return true, err + } + + return true, nil +} diff --git a/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go b/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go index 6fb72aa90..6cf10d8b8 100644 --- a/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go +++ b/builtin/providers/aws/resource_aws_iam_openid_connect_provider_test.go @@ -49,6 +49,48 @@ func TestAccAWSIAMOpenIDConnectProvider_basic(t *testing.T) { }) } +func TestAccAWSIAMOpenIDConnectProvider_importBasic(t *testing.T) { + resourceName := "aws_iam_openid_connect_provider.goog" + rString := acctest.RandString(5) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIAMOpenIDConnectProviderDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccIAMOpenIDConnectProviderConfig_modified(rString), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSIAMOpenIDConnectProvider_disappears(t *testing.T) { + rString := acctest.RandString(5) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIAMOpenIDConnectProviderDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccIAMOpenIDConnectProviderConfig(rString), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMOpenIDConnectProvider("aws_iam_openid_connect_provider.goog"), + testAccCheckIAMOpenIDConnectProviderDisappears("aws_iam_openid_connect_provider.goog"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccCheckIAMOpenIDConnectProviderDestroy(s *terraform.State) error { iamconn := testAccProvider.Meta().(*AWSClient).iamconn @@ -77,6 +119,25 @@ func testAccCheckIAMOpenIDConnectProviderDestroy(s *terraform.State) error { return nil } +func testAccCheckIAMOpenIDConnectProviderDisappears(id string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[id] + if !ok { + return fmt.Errorf("Not Found: %s", id) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + _, err := iamconn.DeleteOpenIDConnectProvider(&iam.DeleteOpenIDConnectProviderInput{ + OpenIDConnectProviderArn: aws.String(rs.Primary.ID), + }) + return err + } +} + func testAccCheckIAMOpenIDConnectProvider(id string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[id] diff --git a/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown b/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown index 194a080c6..2e312e5b9 100644 --- a/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown +++ b/website/source/docs/providers/aws/r/iam_openid_connect_provider.html.markdown @@ -35,3 +35,11 @@ The following arguments are supported: The following attributes are exported: * `arn` - The ARN assigned by AWS for this provider. + +## Import + +IAM OpenID Connect Providers can be imported using the `arn`, e.g. + +``` +$ terraform import aws_iam_openid_connect_provider.default arn:aws:iam::123456789012:oidc-provider/accounts.google.com +``` diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 0373c3716..716a53411 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -745,6 +745,10 @@ aws_iam_instance_profile + > + aws_iam_openid_connect_provider + + > aws_iam_policy From ca96c856fe370463e64374027a4252c434108e9a Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 16:43:24 -0700 Subject: [PATCH 08/23] website: Activate HCL syntax highlighting for Vault provider docs --- website/source/docs/providers/vault/d/generic_secret.html.md | 2 +- website/source/docs/providers/vault/index.html.markdown | 2 +- website/source/docs/providers/vault/r/generic_secret.html.md | 2 +- website/source/docs/providers/vault/r/policy.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/website/source/docs/providers/vault/d/generic_secret.html.md b/website/source/docs/providers/vault/d/generic_secret.html.md index 59ed9b2fe..c6ef7fecf 100644 --- a/website/source/docs/providers/vault/d/generic_secret.html.md +++ b/website/source/docs/providers/vault/d/generic_secret.html.md @@ -25,7 +25,7 @@ for more details. ## Example Usage -``` +```hcl data "vault_generic_secret" "rundeck_auth" { path = "secret/rundeck_auth" } diff --git a/website/source/docs/providers/vault/index.html.markdown b/website/source/docs/providers/vault/index.html.markdown index ba67c94b0..d58195d72 100644 --- a/website/source/docs/providers/vault/index.html.markdown +++ b/website/source/docs/providers/vault/index.html.markdown @@ -126,7 +126,7 @@ The `client_auth` configuration block accepts the following arguments: ## Example Usage -``` +```hcl provider "vault" { # It is strongly recommended to configure this provider through the # environment variables described above, so that each user can have diff --git a/website/source/docs/providers/vault/r/generic_secret.html.md b/website/source/docs/providers/vault/r/generic_secret.html.md index 39c1030ba..e24b52291 100644 --- a/website/source/docs/providers/vault/r/generic_secret.html.md +++ b/website/source/docs/providers/vault/r/generic_secret.html.md @@ -25,7 +25,7 @@ for more details. ## Example Usage -``` +```hcl resource "vault_generic_secret" "example" { path = "secret/foo" diff --git a/website/source/docs/providers/vault/r/policy.md b/website/source/docs/providers/vault/r/policy.md index 89e60c531..2b3a59256 100644 --- a/website/source/docs/providers/vault/r/policy.md +++ b/website/source/docs/providers/vault/r/policy.md @@ -11,7 +11,7 @@ description: |- ## Example Usage -``` +```hcl resource "vault_policy" "example" { name = "dev-team" From 4a24b58fd80044df8a80b175f7ef59629c3af9c1 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 16:44:52 -0700 Subject: [PATCH 09/23] website: Activate HCL syntax highlighting for Rundeck provider docs --- website/source/docs/providers/rundeck/index.html.markdown | 2 +- website/source/docs/providers/rundeck/r/job.html.md | 2 +- website/source/docs/providers/rundeck/r/private_key.html.md | 2 +- website/source/docs/providers/rundeck/r/project.html.md | 2 +- website/source/docs/providers/rundeck/r/public_key.html.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/website/source/docs/providers/rundeck/index.html.markdown b/website/source/docs/providers/rundeck/index.html.markdown index 595ccaf5c..062045de5 100644 --- a/website/source/docs/providers/rundeck/index.html.markdown +++ b/website/source/docs/providers/rundeck/index.html.markdown @@ -30,7 +30,7 @@ Use the navigation to the left to read about the available resources. ## Example Usage -``` +```hcl provider "rundeck" { url = "http://rundeck.example.com/" auth_token = "abcd1234" diff --git a/website/source/docs/providers/rundeck/r/job.html.md b/website/source/docs/providers/rundeck/r/job.html.md index 725d1bf2b..5345c3b24 100644 --- a/website/source/docs/providers/rundeck/r/job.html.md +++ b/website/source/docs/providers/rundeck/r/job.html.md @@ -16,7 +16,7 @@ Each job belongs to a project. A project can be created with the `rundeck_projec ## Example Usage -``` +```hcl resource "rundeck_job" "bounceweb" { name = "Bounce Web Servers" project_name = "anvils" diff --git a/website/source/docs/providers/rundeck/r/private_key.html.md b/website/source/docs/providers/rundeck/r/private_key.html.md index 3d74aaf1d..a95829ef8 100644 --- a/website/source/docs/providers/rundeck/r/private_key.html.md +++ b/website/source/docs/providers/rundeck/r/private_key.html.md @@ -14,7 +14,7 @@ it runs commands. ## Example Usage -``` +```hcl resource "rundeck_private_key" "anvils" { path = "anvils/id_rsa" key_material = "${file("/id_rsa")}" diff --git a/website/source/docs/providers/rundeck/r/project.html.md b/website/source/docs/providers/rundeck/r/project.html.md index e8a7d3eba..31a3827a3 100644 --- a/website/source/docs/providers/rundeck/r/project.html.md +++ b/website/source/docs/providers/rundeck/r/project.html.md @@ -14,7 +14,7 @@ can be run on. ## Example Usage -``` +```hcl resource "rundeck_project" "anvils" { name = "anvils" description = "Application for managing Anvils" diff --git a/website/source/docs/providers/rundeck/r/public_key.html.md b/website/source/docs/providers/rundeck/r/public_key.html.md index 13ddca606..a3e617829 100644 --- a/website/source/docs/providers/rundeck/r/public_key.html.md +++ b/website/source/docs/providers/rundeck/r/public_key.html.md @@ -17,7 +17,7 @@ may be used in the configuration of other resources such as ``aws_key_pair``. ## Example Usage -``` +```hcl resource "rundeck_public_key" "anvils" { path = "anvils/id_rsa.pub" key_material = "ssh-rsa yada-yada-yada" From 5fc7414076a9e35f7a287f3f8cc1f2e3f7f3d632 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 16:46:56 -0700 Subject: [PATCH 10/23] website: Activate HCL syntax highlighting for Template provider docs --- .../providers/template/d/cloudinit_config.html.markdown | 2 +- website/source/docs/providers/template/d/file.html.md | 6 +++--- website/source/docs/providers/template/index.html.markdown | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/website/source/docs/providers/template/d/cloudinit_config.html.markdown b/website/source/docs/providers/template/d/cloudinit_config.html.markdown index 030092ea5..46f3ca3c8 100644 --- a/website/source/docs/providers/template/d/cloudinit_config.html.markdown +++ b/website/source/docs/providers/template/d/cloudinit_config.html.markdown @@ -12,7 +12,7 @@ Renders a multi-part cloud-init config from source files. ## Example Usage -``` +```hcl # Render a part using a `template_file` data "template_file" "script" { template = "${file("${path.module}/init.tpl")}" diff --git a/website/source/docs/providers/template/d/file.html.md b/website/source/docs/providers/template/d/file.html.md index 4929041f1..e00107acc 100644 --- a/website/source/docs/providers/template/d/file.html.md +++ b/website/source/docs/providers/template/d/file.html.md @@ -16,7 +16,7 @@ Option 1: From a file: Reference the template path: -``` +```hcl data "template_file" "init" { template = "${file("${path.module}/init.tpl")}" @@ -28,7 +28,7 @@ data "template_file" "init" { Inside the file, reference the variable as such: -``` +```bash #!/bin/bash echo "CONSUL_ADDRESS = ${consul_address}" > /tmp/iplist @@ -36,7 +36,7 @@ echo "CONSUL_ADDRESS = ${consul_address}" > /tmp/iplist Option 2: Inline: -``` +```hcl data "template_file" "init" { template = "$${consul_address}:1234" diff --git a/website/source/docs/providers/template/index.html.markdown b/website/source/docs/providers/template/index.html.markdown index 19357988e..22d715c2f 100644 --- a/website/source/docs/providers/template/index.html.markdown +++ b/website/source/docs/providers/template/index.html.markdown @@ -15,7 +15,7 @@ Use the navigation to the left to read about the available data sources. ## Example Usage -``` +```hcl # Template for initial configuration bash script data "template_file" "init" { template = "${file("init.tpl")}" @@ -35,7 +35,7 @@ resource "aws_instance" "web" { Or using an inline template: -``` +```hcl # Template for initial configuration bash script data "template_file" "init" { template = "$${consul_address}:1234" From af05871e653a21cf276e57e3e0f1bd30b31e11f5 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 16:48:08 -0700 Subject: [PATCH 11/23] website: Activate HCL syntax highlighting for Random provider docs --- website/source/docs/providers/random/index.html.markdown | 2 +- website/source/docs/providers/random/r/id.html.md | 2 +- website/source/docs/providers/random/r/pet.html.md | 2 +- website/source/docs/providers/random/r/shuffle.html.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/website/source/docs/providers/random/index.html.markdown b/website/source/docs/providers/random/index.html.markdown index 32bf038d8..234b00106 100644 --- a/website/source/docs/providers/random/index.html.markdown +++ b/website/source/docs/providers/random/index.html.markdown @@ -43,7 +43,7 @@ the same until new random values are desired. For example: -``` +```hcl resource "random_id" "server" { keepers = { # Generate a new id each time we switch to a new AMI id diff --git a/website/source/docs/providers/random/r/id.html.md b/website/source/docs/providers/random/r/id.html.md index a7a94a407..b06e04013 100644 --- a/website/source/docs/providers/random/r/id.html.md +++ b/website/source/docs/providers/random/r/id.html.md @@ -26,7 +26,7 @@ exist concurrently. The following example shows how to generate a unique name for an AWS EC2 instance that changes each time a new AMI id is selected. -``` +```hcl resource "random_id" "server" { keepers = { # Generate a new id each time we switch to a new AMI id diff --git a/website/source/docs/providers/random/r/pet.html.md b/website/source/docs/providers/random/r/pet.html.md index 86b457a7a..56068237b 100644 --- a/website/source/docs/providers/random/r/pet.html.md +++ b/website/source/docs/providers/random/r/pet.html.md @@ -21,7 +21,7 @@ exist concurrently. The following example shows how to generate a unique pet name for an AWS EC2 instance that changes each time a new AMI id is selected. -``` +```hcl resource "random_pet" "server" { keepers = { # Generate a new pet name each time we switch to a new AMI id diff --git a/website/source/docs/providers/random/r/shuffle.html.md b/website/source/docs/providers/random/r/shuffle.html.md index bfae65bca..9204df791 100644 --- a/website/source/docs/providers/random/r/shuffle.html.md +++ b/website/source/docs/providers/random/r/shuffle.html.md @@ -13,7 +13,7 @@ of strings given as an argument. ## Example Usage -``` +```hcl resource "random_shuffle" "az" { input = ["us-west-1a", "us-west-1c", "us-west-1d", "us-west-1e"] result_count = 2 From 853b411840eaa84abf108b7ea70fd834ac9345eb Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 16:54:00 -0700 Subject: [PATCH 12/23] website: syntax highlighting and hclfmt layout for TLS provider --- .../docs/providers/tls/index.html.markdown | 46 +++++++++---------- .../docs/providers/tls/r/cert_request.html.md | 14 +++--- .../tls/r/locally_signed_cert.html.md | 23 +++++----- .../docs/providers/tls/r/private_key.html.md | 6 +-- .../providers/tls/r/self_signed_cert.html.md | 26 +++++------ 5 files changed, 57 insertions(+), 58 deletions(-) diff --git a/website/source/docs/providers/tls/index.html.markdown b/website/source/docs/providers/tls/index.html.markdown index 42017111a..57f962e64 100644 --- a/website/source/docs/providers/tls/index.html.markdown +++ b/website/source/docs/providers/tls/index.html.markdown @@ -26,7 +26,7 @@ Use the navigation to the left to read about the available resources. ## Example Usage -``` +```hcl ## This example create a self-signed certificate for a development ## environment. ## THIS IS NOT RECOMMENDED FOR PRODUCTION SERVICES. @@ -34,39 +34,39 @@ Use the navigation to the left to read about the available resources. ## security considerations and other practical tradeoffs. resource "tls_private_key" "example" { - algorithm = "ECDSA" + algorithm = "ECDSA" } resource "tls_self_signed_cert" "example" { - key_algorithm = "${tls_private_key.example.algorithm}" - private_key_pem = "${tls_private_key.example.private_key_pem}" + key_algorithm = "${tls_private_key.example.algorithm}" + private_key_pem = "${tls_private_key.example.private_key_pem}" - # Certificate expires after 12 hours. - validity_period_hours = 12 + # Certificate expires after 12 hours. + validity_period_hours = 12 - # Generate a new certificate if Terraform is run within three - # hours of the certificate's expiration time. - early_renewal_hours = 3 + # Generate a new certificate if Terraform is run within three + # hours of the certificate's expiration time. + early_renewal_hours = 3 - # Reasonable set of uses for a server SSL certificate. - allowed_uses = [ - "key_encipherment", - "digital_signature", - "server_auth", - ] + # Reasonable set of uses for a server SSL certificate. + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] - dns_names = ["example.com", "example.net"] + dns_names = ["example.com", "example.net"] - subject { - common_name = "example.com" - organization = "ACME Examples, Inc" - } + subject { + common_name = "example.com" + organization = "ACME Examples, Inc" + } } # For example, this can be used to populate an AWS IAM server certificate. resource "aws_iam_server_certificate" "example" { - name = "example_self_signed_cert" - certificate_body = "${tls_self_signed_cert.example.cert_pem}" - private_key = "${tls_private_key.example.private_key_pem}" + name = "example_self_signed_cert" + certificate_body = "${tls_self_signed_cert.example.cert_pem}" + private_key = "${tls_private_key.example.private_key_pem}" } ``` diff --git a/website/source/docs/providers/tls/r/cert_request.html.md b/website/source/docs/providers/tls/r/cert_request.html.md index 8869aeac6..3a71583fd 100644 --- a/website/source/docs/providers/tls/r/cert_request.html.md +++ b/website/source/docs/providers/tls/r/cert_request.html.md @@ -26,15 +26,15 @@ resource form. ## Example Usage -``` +```hcl resource "tls_cert_request" "example" { - key_algorithm = "ECDSA" - private_key_pem = "${file(\"private_key.pem\")}" + key_algorithm = "ECDSA" + private_key_pem = "${file("private_key.pem")}" - subject { - common_name = "example.com" - organization = "ACME Examples, Inc" - } + subject { + common_name = "example.com" + organization = "ACME Examples, Inc" + } } ``` diff --git a/website/source/docs/providers/tls/r/locally_signed_cert.html.md b/website/source/docs/providers/tls/r/locally_signed_cert.html.md index ebd13fd54..a022c2925 100644 --- a/website/source/docs/providers/tls/r/locally_signed_cert.html.md +++ b/website/source/docs/providers/tls/r/locally_signed_cert.html.md @@ -17,21 +17,20 @@ or when deployed internally to an organization. ## Example Usage -``` +```hcl resource "tls_locally_signed_cert" "example" { - cert_request_pem = "${file(\"cert_request.pem\")}" + cert_request_pem = "${file("cert_request.pem")}" + ca_key_algorithm = "ECDSA" + ca_private_key_pem = "${file("ca_private_key.pem")}" + ca_cert_pem = "${file("ca_cert.pem")}" - ca_key_algorithm = "ECDSA" - ca_private_key_pem = "${file(\"ca_private_key.pem\")}" - ca_cert_pem = "${file(\"ca_cert.pem\")}" + validity_period_hours = 12 - validity_period_hours = 12 - - allowed_uses = [ - "key_encipherment", - "digital_signature", - "server_auth", - ] + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] } ``` diff --git a/website/source/docs/providers/tls/r/private_key.html.md b/website/source/docs/providers/tls/r/private_key.html.md index c0d6d1beb..06d4fc4bc 100644 --- a/website/source/docs/providers/tls/r/private_key.html.md +++ b/website/source/docs/providers/tls/r/private_key.html.md @@ -23,10 +23,10 @@ state and does not create any external managed resources. ## Example Usage -``` +```hcl resource "tls_private_key" "example" { - algorithm = "ECDSA" - ecdsa_curve = "P384" + algorithm = "ECDSA" + ecdsa_curve = "P384" } ``` diff --git a/website/source/docs/providers/tls/r/self_signed_cert.html.md b/website/source/docs/providers/tls/r/self_signed_cert.html.md index f2a047f4f..60cf5bcf1 100644 --- a/website/source/docs/providers/tls/r/self_signed_cert.html.md +++ b/website/source/docs/providers/tls/r/self_signed_cert.html.md @@ -27,23 +27,23 @@ Load Balancer*, *Elastic Beanstalk*, *CloudFront* or *OpsWorks*. ## Example Usage -``` +```hcl resource "tls_self_signed_cert" "example" { - key_algorithm = "ECDSA" - private_key_pem = "${file(\"private_key.pem\")}" + key_algorithm = "ECDSA" + private_key_pem = "${file(\"private_key.pem\")}" - subject { - common_name = "example.com" - organization = "ACME Examples, Inc" - } + subject { + common_name = "example.com" + organization = "ACME Examples, Inc" + } - validity_period_hours = 12 + validity_period_hours = 12 - allowed_uses = [ - "key_encipherment", - "digital_signature", - "server_auth", - ] + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] } ``` From 6b8c98d3936940fe0889a6d1c6ae9e1998aea3eb Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 16:55:42 -0700 Subject: [PATCH 13/23] website: syntax highlighting for "external" provider docs --- website/source/docs/providers/external/data_source.html.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/providers/external/data_source.html.md b/website/source/docs/providers/external/data_source.html.md index 9241d0215..53b2271df 100644 --- a/website/source/docs/providers/external/data_source.html.md +++ b/website/source/docs/providers/external/data_source.html.md @@ -27,7 +27,7 @@ configurations that are applied within Terraform Enterprise. ## Example Usage -``` +```hcl data "external" "example" { program = ["python", "${path.module}/example-data-source.py"] @@ -94,7 +94,7 @@ language. The following example shows some input/output boilerplate code for a data source implemented in bash: -``` +```bash #!/bin/bash # Exit if any of the intermediate steps fail From 2933123a624f71d8c543bbf91528e0158782616c Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 16:56:35 -0700 Subject: [PATCH 14/23] website: additional syntax highlighting for "terraform" provider docs --- website/source/docs/providers/terraform/index.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/providers/terraform/index.html.markdown b/website/source/docs/providers/terraform/index.html.markdown index 3fca18257..f0b7784a0 100644 --- a/website/source/docs/providers/terraform/index.html.markdown +++ b/website/source/docs/providers/terraform/index.html.markdown @@ -15,7 +15,7 @@ Use the navigation to the left to read about the available data sources. ## Example Usage -``` +```hcl # Shared infrastructure state stored in Atlas data "terraform_remote_state" "vpc" { backend = "atlas" From 22110ee73f04b6e7faaf401a71a446d1960f7c42 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 16:58:01 -0700 Subject: [PATCH 15/23] website: Syntax highlighting for "mysql" provider docs --- website/source/docs/providers/mysql/index.html.markdown | 4 ++-- website/source/docs/providers/mysql/r/database.html.markdown | 2 +- website/source/docs/providers/mysql/r/grant.html.markdown | 2 +- website/source/docs/providers/mysql/r/user.html.markdown | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/website/source/docs/providers/mysql/index.html.markdown b/website/source/docs/providers/mysql/index.html.markdown index f7d8eec4f..eae80872c 100644 --- a/website/source/docs/providers/mysql/index.html.markdown +++ b/website/source/docs/providers/mysql/index.html.markdown @@ -18,7 +18,7 @@ Use the navigation to the left to read about the available resources. The following is a minimal example: -``` +```hcl # Configure the MySQL provider provider "mysql" { endpoint = "my-database.example.com:3306" @@ -36,7 +36,7 @@ This provider can be used in conjunction with other resources that create MySQL servers. For example, ``aws_db_instance`` is able to create MySQL servers in Amazon's RDS service. -``` +```hcl # Create a database server resource "aws_db_instance" "default" { engine = "mysql" diff --git a/website/source/docs/providers/mysql/r/database.html.markdown b/website/source/docs/providers/mysql/r/database.html.markdown index 0f38fe4b4..04ffc60b5 100644 --- a/website/source/docs/providers/mysql/r/database.html.markdown +++ b/website/source/docs/providers/mysql/r/database.html.markdown @@ -19,7 +19,7 @@ on your database resources as an extra safety measure. ## Example Usage -``` +```hcl resource "mysql_database" "app" { name = "my_awesome_app" } diff --git a/website/source/docs/providers/mysql/r/grant.html.markdown b/website/source/docs/providers/mysql/r/grant.html.markdown index aacc20a39..464445836 100644 --- a/website/source/docs/providers/mysql/r/grant.html.markdown +++ b/website/source/docs/providers/mysql/r/grant.html.markdown @@ -13,7 +13,7 @@ a user on a MySQL server. ## Example Usage -``` +```hcl resource "mysql_user" "jdoe" { user = "jdoe" host = "example.com" diff --git a/website/source/docs/providers/mysql/r/user.html.markdown b/website/source/docs/providers/mysql/r/user.html.markdown index ebf101174..6f3f8b50d 100644 --- a/website/source/docs/providers/mysql/r/user.html.markdown +++ b/website/source/docs/providers/mysql/r/user.html.markdown @@ -16,7 +16,7 @@ server. ## Example Usage -``` +```hcl resource "mysql_user" "jdoe" { user = "jdoe" host = "example.com" From 1d500f57a68c68691ab23f81a6424afa671e9429 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 17:04:55 -0700 Subject: [PATCH 16/23] provider/dns: update hard-coded DNS result in acctests The acctests depend on getting a particular result back from resolving www.hashicorp.com, which seems to have changed. In the long run we should probably *not* be depending on the DNS configuration of the main Hashicorp website, but this is just a quick fix to get the build back to green so we can continue work on other things. --- builtin/providers/dns/data_dns_cname_record_set_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/dns/data_dns_cname_record_set_test.go b/builtin/providers/dns/data_dns_cname_record_set_test.go index 84270ff04..3ae2fdbc5 100644 --- a/builtin/providers/dns/data_dns_cname_record_set_test.go +++ b/builtin/providers/dns/data_dns_cname_record_set_test.go @@ -17,7 +17,7 @@ func TestAccDnsCnameRecordSet_Basic(t *testing.T) { host = "www.hashicorp.com" } `, - "s.shared.global.fastly.net.", + "dualstack.s.shared.global.fastly.net.", }, } From 46994483e1f83b64366d63abb9607e92d1e1c3dd Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 14:14:06 -0700 Subject: [PATCH 17/23] Upgrade mapstructure vendoring to latest version Apparently we've let this get several years out of date. --- .../mitchellh/mapstructure/decode_hooks.go | 5 +- .../mitchellh/mapstructure/mapstructure.go | 148 +++++++++++++----- vendor/vendor.json | 5 +- 3 files changed, 120 insertions(+), 38 deletions(-) diff --git a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go index aa91f76ce..115ae67c1 100644 --- a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go +++ b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go @@ -72,7 +72,10 @@ func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { } // Modify the from kind to be correct with the new data - f = reflect.ValueOf(data).Type() + f = nil + if val := reflect.ValueOf(data); val.IsValid() { + f = val.Type() + } } return data, nil diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go index 40be5116d..6dee0ef0a 100644 --- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go +++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go @@ -1,5 +1,5 @@ // The mapstructure package exposes functionality to convert an -// abitrary map[string]interface{} into a native Go structure. +// arbitrary map[string]interface{} into a native Go structure. // // The Go structure can be arbitrarily complex, containing slices, // other structs, etc. and the decoder will properly decode nested @@ -8,6 +8,7 @@ package mapstructure import ( + "encoding/json" "errors" "fmt" "reflect" @@ -67,6 +68,10 @@ type DecoderConfig struct { // FALSE, false, False. Anything else is an error) // - empty array = empty map and vice versa // - negative numbers to overflowed uint values (base 10) + // - slice of maps to a merged map + // - single values are converted to slices if required. Each + // element is weakly decoded. For example: "4" can become []int{4} + // if the target type is an int slice. // WeaklyTypedInput bool @@ -200,7 +205,7 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error d.config.DecodeHook, dataVal.Type(), val.Type(), data) if err != nil { - return err + return fmt.Errorf("error decoding '%s': %s", name, err) } } @@ -227,6 +232,8 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error err = d.decodePtr(name, data, val) case reflect.Slice: err = d.decodeSlice(name, data, val) + case reflect.Func: + err = d.decodeFunc(name, data, val) default: // If we reached this point then we weren't able to decode it return fmt.Errorf("%s: unsupported type: %s", name, dataKind) @@ -245,6 +252,10 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error // value to "data" of that type. func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) + if !dataVal.IsValid() { + dataVal = reflect.Zero(val.Type()) + } + dataValType := dataVal.Type() if !dataValType.AssignableTo(val.Type()) { return fmt.Errorf( @@ -301,6 +312,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) dataKind := getKind(dataVal) + dataType := dataVal.Type() switch { case dataKind == reflect.Int: @@ -322,6 +334,14 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er } else { return fmt.Errorf("cannot parse '%s' as int: %s", name, err) } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Int64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetInt(i) default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", @@ -408,6 +428,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) dataKind := getKind(dataVal) + dataType := dataVal.Type() switch { case dataKind == reflect.Int: @@ -429,6 +450,14 @@ func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) } else { return fmt.Errorf("cannot parse '%s' as float: %s", name, err) } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Float64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetFloat(i) default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", @@ -456,15 +485,30 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er // Check input type dataVal := reflect.Indirect(reflect.ValueOf(data)) if dataVal.Kind() != reflect.Map { - // Accept empty array/slice instead of an empty map in weakly typed mode - if d.config.WeaklyTypedInput && - (dataVal.Kind() == reflect.Slice || dataVal.Kind() == reflect.Array) && - dataVal.Len() == 0 { - val.Set(valMap) - return nil - } else { - return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + // In weak mode, we accept a slice of maps as an input... + if d.config.WeaklyTypedInput { + switch dataVal.Kind() { + case reflect.Array, reflect.Slice: + // Special case for BC reasons (covered by tests) + if dataVal.Len() == 0 { + val.Set(valMap) + return nil + } + + for i := 0; i < dataVal.Len(); i++ { + err := d.decode( + fmt.Sprintf("%s[%d]", name, i), + dataVal.Index(i).Interface(), val) + if err != nil { + return err + } + } + + return nil + } } + + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) } // Accumulate errors @@ -507,7 +551,12 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er // into that. Then set the value of the pointer to this type. valType := val.Type() valElemType := valType.Elem() - realVal := reflect.New(valElemType) + + realVal := val + if realVal.IsNil() || d.config.ZeroFields { + realVal = reflect.New(valElemType) + } + if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil { return err } @@ -516,6 +565,19 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er return nil } +func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error { + // Create an element of the concrete (non pointer) type and decode + // into that. Then set the value of the pointer to this type. + dataVal := reflect.Indirect(reflect.ValueOf(data)) + if val.Type() != dataVal.Type() { + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + val.Set(dataVal) + return nil +} + func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() @@ -523,26 +585,44 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) valElemType := valType.Elem() sliceType := reflect.SliceOf(valElemType) - // Check input type - if dataValKind != reflect.Array && dataValKind != reflect.Slice { - // Accept empty map instead of array/slice in weakly typed mode - if d.config.WeaklyTypedInput && dataVal.Kind() == reflect.Map && dataVal.Len() == 0 { - val.Set(reflect.MakeSlice(sliceType, 0, 0)) - return nil - } else { + valSlice := val + if valSlice.IsNil() || d.config.ZeroFields { + // Check input type + if dataValKind != reflect.Array && dataValKind != reflect.Slice { + if d.config.WeaklyTypedInput { + switch { + // Empty maps turn into empty slices + case dataValKind == reflect.Map: + if dataVal.Len() == 0 { + val.Set(reflect.MakeSlice(sliceType, 0, 0)) + return nil + } + + // All other types we try to convert to the slice type + // and "lift" it into it. i.e. a string becomes a string slice. + default: + // Just re-try this function with data as a slice. + return d.decodeSlice(name, []interface{}{data}, val) + } + } + return fmt.Errorf( "'%s': source data must be an array or slice, got %s", name, dataValKind) - } - } - // Make a new slice to hold our result, same size as the original data. - valSlice := reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) + } + + // Make a new slice to hold our result, same size as the original data. + valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) + } // Accumulate any errors errors := make([]string, 0) for i := 0; i < dataVal.Len(); i++ { currentData := dataVal.Index(i).Interface() + for valSlice.Len() <= i { + valSlice = reflect.Append(valSlice, reflect.Zero(valElemType)) + } currentField := valSlice.Index(i) fieldName := fmt.Sprintf("%s[%d]", name, i) @@ -607,17 +687,10 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) structs = structs[1:] structType := structVal.Type() + for i := 0; i < structType.NumField(); i++ { fieldType := structType.Field(i) - - if fieldType.Anonymous { - fieldKind := fieldType.Type.Kind() - if fieldKind != reflect.Struct { - errors = appendErrors(errors, - fmt.Errorf("%s: unsupported type: %s", fieldType.Name, fieldKind)) - continue - } - } + fieldKind := fieldType.Type.Kind() // If "squash" is specified in the tag, we squash the field down. squash := false @@ -630,7 +703,12 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) } if squash { - structs = append(structs, val.FieldByName(fieldType.Name)) + if fieldKind != reflect.Struct { + errors = appendErrors(errors, + fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind)) + } else { + structs = append(structs, val.FieldByName(fieldType.Name)) + } continue } @@ -653,7 +731,7 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) if !rawMapVal.IsValid() { // Do a slower search by iterating over each key and // doing case-insensitive search. - for dataValKey, _ := range dataValKeys { + for dataValKey := range dataValKeys { mK, ok := dataValKey.Interface().(string) if !ok { // Not a string key @@ -701,7 +779,7 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) if d.config.ErrorUnused && len(dataValKeysUnused) > 0 { keys := make([]string, 0, len(dataValKeysUnused)) - for rawKey, _ := range dataValKeysUnused { + for rawKey := range dataValKeysUnused { keys = append(keys, rawKey.(string)) } sort.Strings(keys) @@ -716,7 +794,7 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) // Add the unused keys to the list of unused keys if we're tracking metadata if d.config.Metadata != nil { - for rawKey, _ := range dataValKeysUnused { + for rawKey := range dataValKeysUnused { key := rawKey.(string) if name != "" { key = fmt.Sprintf("%s.%s", name, key) diff --git a/vendor/vendor.json b/vendor/vendor.json index 0136635a0..aee3d291f 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2564,9 +2564,10 @@ "revision": "6b17d669fac5e2f71c16658d781ec3fdd3802b69" }, { - "checksumSHA1": "4Js6Jlu93Wa0o6Kjt393L9Z7diE=", + "checksumSHA1": "MlX15lJuV8DYARX5RJY8rqrSEWQ=", "path": "github.com/mitchellh/mapstructure", - "revision": "281073eb9eb092240d33ef253c404f1cca550309" + "revision": "53818660ed4955e899c0bcafa97299a388bd7c8e", + "revisionTime": "2017-03-07T20:11:23Z" }, { "checksumSHA1": "e/MV3GL8ZOpqyNSKVPtMeqTRR/w=", From e4a5d3612761009add67205f28d14c3f4b7bec27 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 7 Apr 2017 15:30:53 -0700 Subject: [PATCH 18/23] core: EvalVariableBlock to decode maps and slices more carefully Previously this function was depending on the mapstructure behavior of failing with an error when trying to decode a map into a list or vice-versa, but mapstructure's WeakDecode behavior changed so that it will go to greater lengths to coerce the given value to fit into the target type, causing us to mis-handle certain ambigous cases. Here we exert a bit more control over what's going on by using 'reflect' to first check whether we have a slice or map value and only then try to decode into one with mapstructure. This allows us to still rely on mapstructure's ability to decode nested structures but ensure that lists and maps never get implicitly converted to each other. --- terraform/eval_variable.go | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/terraform/eval_variable.go b/terraform/eval_variable.go index b39a4d198..e39a33c2a 100644 --- a/terraform/eval_variable.go +++ b/terraform/eval_variable.go @@ -123,22 +123,27 @@ func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) { // Get our configuration rc := *n.Config for k, v := range rc.Config { - var vString string - if err := hilmapstructure.WeakDecode(v, &vString); err == nil { - n.VariableValues[k] = vString - continue - } + vKind := reflect.ValueOf(v).Type().Kind() - var vMap map[string]interface{} - if err := hilmapstructure.WeakDecode(v, &vMap); err == nil { - n.VariableValues[k] = vMap - continue - } - - var vSlice []interface{} - if err := hilmapstructure.WeakDecode(v, &vSlice); err == nil { - n.VariableValues[k] = vSlice - continue + switch vKind { + case reflect.Slice: + var vSlice []interface{} + if err := hilmapstructure.WeakDecode(v, &vSlice); err == nil { + n.VariableValues[k] = vSlice + continue + } + case reflect.Map: + var vMap map[string]interface{} + if err := hilmapstructure.WeakDecode(v, &vMap); err == nil { + n.VariableValues[k] = vMap + continue + } + default: + var vString string + if err := hilmapstructure.WeakDecode(v, &vString); err == nil { + n.VariableValues[k] = vString + continue + } } return nil, fmt.Errorf("Variable value for %s is not a string, list or map type", k) From e885ab81a8a9faac164a767498688ad6d78b47da Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Sat, 8 Apr 2017 09:57:05 +0100 Subject: [PATCH 19/23] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f649e54a..85c5ec2a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ FEATURES: + * **New Resource:** `aws_iam_openid_connect_provider` [GH-13456] * **New Resource:** `aws_lightsail_static_ip` [GH-13175] * **New Resource:** `aws_lightsail_static_ip_attachment` [GH-13207] * **New Resource:** `aws_ses_domain_identity` [GH-13098] From 8e87b107a98eddf34a9e96802620f0a0e66ec8a5 Mon Sep 17 00:00:00 2001 From: Pasha Palangpour Date: Sat, 8 Apr 2017 06:04:57 -0400 Subject: [PATCH 20/23] provider/ns1: No splitting answer on spf records. (#13260) * provider/ns1: No splitting answer on spf records. * provider/ns1: Adds acctest for SPF records. --- builtin/providers/ns1/resource_record.go | 2 +- builtin/providers/ns1/resource_record_test.go | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/builtin/providers/ns1/resource_record.go b/builtin/providers/ns1/resource_record.go index 88b1a96c9..add703c3a 100644 --- a/builtin/providers/ns1/resource_record.go +++ b/builtin/providers/ns1/resource_record.go @@ -236,7 +236,7 @@ func resourceDataToRecord(r *dns.Record, d *schema.ResourceData) error { var a *dns.Answer v := answer["answer"].(string) switch d.Get("type") { - case "TXT": + case "TXT", "SPF": a = dns.NewTXTAnswer(v) default: a = dns.NewAnswer(strings.Split(v, " ")) diff --git a/builtin/providers/ns1/resource_record_test.go b/builtin/providers/ns1/resource_record_test.go index e73a143cc..36095b579 100644 --- a/builtin/providers/ns1/resource_record_test.go +++ b/builtin/providers/ns1/resource_record_test.go @@ -71,6 +71,27 @@ func TestAccRecord_updated(t *testing.T) { }) } +func TestAccRecord_SPF(t *testing.T) { + var record dns.Record + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRecordSPF, + Check: resource.ComposeTestCheckFunc( + testAccCheckRecordExists("ns1_record.spf", &record), + testAccCheckRecordDomain(&record, "terraform-record-test.io"), + testAccCheckRecordTTL(&record, 86400), + testAccCheckRecordUseClientSubnet(&record, true), + testAccCheckRecordAnswerRdata(&record, "v=DKIM1; k=rsa; p=XXXXXXXX"), + ), + }, + }, + }) +} + func testAccCheckRecordExists(n string, record *dns.Record) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -297,3 +318,20 @@ resource "ns1_zone" "test" { zone = "terraform-record-test.io" } ` + +const testAccRecordSPF = ` +resource "ns1_record" "spf" { + zone = "${ns1_zone.test.zone}" + domain = "${ns1_zone.test.zone}" + type = "SPF" + ttl = 86400 + use_client_subnet = "true" + answers = { + answer = "v=DKIM1; k=rsa; p=XXXXXXXX" + } +} + +resource "ns1_zone" "test" { + zone = "terraform-record-test.io" +} +` From 6ac0d4c08cfe0e1a2ce3098b37a754edad3bf1f4 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Sat, 8 Apr 2017 11:06:10 +0100 Subject: [PATCH 21/23] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85c5ec2a8..1fc50fc96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ BUG FIXES: * provider/azurerm: Network Security Group - ignoring protocol casing at Import time [GH-13153] * provider/azurerm: Fix crash when importing Local Network Gateways [GH-13261] * provider/bitbucket: Fixed issue where provider would fail with an "EOF" error on some operations [GH-13390] + * provider/ns1: No splitting answer on SPF records. [GH-13260] * provider/openstack: Refresh volume_attachment from state if NotFound [GH-13342] * provider/openstack: Add SOFT_DELETED to delete status [GH-13444] * provider/profitbricks: Changed output type of ips variable of ip_block ProfitBricks resource [GH-13290] From b77d797e85ba5a730874e2217f5d407986468fd3 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Sat, 8 Apr 2017 12:51:00 -0500 Subject: [PATCH 22/23] provider/aws: Fix aws_ami_launch_permission refresh when AMI disappears (#13469) Launch permissions are implicitly nuked if an AMI is removed for any reason - Terraform should not error on refresh in this case, but rather just see the launch permissions as gone and react appropriately. --- .../aws/resource_aws_ami_launch_permission.go | 10 +++ ...resource_aws_ami_launch_permission_test.go | 76 ++++++++++++++----- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/builtin/providers/aws/resource_aws_ami_launch_permission.go b/builtin/providers/aws/resource_aws_ami_launch_permission.go index d1c738d39..278e9d9ab 100644 --- a/builtin/providers/aws/resource_aws_ami_launch_permission.go +++ b/builtin/providers/aws/resource_aws_ami_launch_permission.go @@ -2,7 +2,11 @@ package aws import ( "fmt" + "log" + "strings" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform/helper/schema" ) @@ -92,6 +96,12 @@ func hasLaunchPermission(conn *ec2.EC2, image_id string, account_id string) (boo Attribute: aws.String("launchPermission"), }) if err != nil { + // When an AMI disappears out from under a launch permission resource, we will + // see either InvalidAMIID.NotFound or InvalidAMIID.Unavailable. + if ec2err, ok := err.(awserr.Error); ok && strings.HasPrefix(ec2err.Code(), "InvalidAMIID") { + log.Printf("[DEBUG] %s no longer exists, so we'll drop launch permission for %s from the state", image_id, account_id) + return false, nil + } return false, err } diff --git a/builtin/providers/aws/resource_aws_ami_launch_permission_test.go b/builtin/providers/aws/resource_aws_ami_launch_permission_test.go index 0affa9161..4ccb35c7c 100644 --- a/builtin/providers/aws/resource_aws_ami_launch_permission_test.go +++ b/builtin/providers/aws/resource_aws_ami_launch_permission_test.go @@ -2,15 +2,18 @@ package aws import ( "fmt" - r "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" "os" "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + r "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" ) func TestAccAWSAMILaunchPermission_Basic(t *testing.T) { - image_id := "" - account_id := os.Getenv("AWS_ACCOUNT_ID") + imageID := "" + accountID := os.Getenv("AWS_ACCOUNT_ID") r.Test(t, r.TestCase{ PreCheck: func() { @@ -23,19 +26,36 @@ func TestAccAWSAMILaunchPermission_Basic(t *testing.T) { Steps: []r.TestStep{ // Scaffold everything r.TestStep{ - Config: testAccAWSAMILaunchPermissionConfig(account_id, true), + Config: testAccAWSAMILaunchPermissionConfig(accountID, true), Check: r.ComposeTestCheckFunc( - testCheckResourceGetAttr("aws_ami_copy.test", "id", &image_id), - testAccAWSAMILaunchPermissionExists(account_id, &image_id), + testCheckResourceGetAttr("aws_ami_copy.test", "id", &imageID), + testAccAWSAMILaunchPermissionExists(accountID, &imageID), ), }, // Drop just launch permission to test destruction r.TestStep{ - Config: testAccAWSAMILaunchPermissionConfig(account_id, false), + Config: testAccAWSAMILaunchPermissionConfig(accountID, false), Check: r.ComposeTestCheckFunc( - testAccAWSAMILaunchPermissionDestroyed(account_id, &image_id), + testAccAWSAMILaunchPermissionDestroyed(accountID, &imageID), ), }, + // Re-add everything so we can test when AMI disappears + r.TestStep{ + Config: testAccAWSAMILaunchPermissionConfig(accountID, true), + Check: r.ComposeTestCheckFunc( + testCheckResourceGetAttr("aws_ami_copy.test", "id", &imageID), + testAccAWSAMILaunchPermissionExists(accountID, &imageID), + ), + }, + // Here we delete the AMI to verify the follow-on refresh after this step + // should not error. + r.TestStep{ + Config: testAccAWSAMILaunchPermissionConfig(accountID, true), + Check: r.ComposeTestCheckFunc( + testAccAWSAMIDisappears(&imageID), + ), + ExpectNonEmptyPlan: true, + }, }, }) } @@ -58,31 +78,53 @@ func testCheckResourceGetAttr(name, key string, value *string) r.TestCheckFunc { } } -func testAccAWSAMILaunchPermissionExists(account_id string, image_id *string) r.TestCheckFunc { +func testAccAWSAMILaunchPermissionExists(accountID string, imageID *string) r.TestCheckFunc { return func(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn - if has, err := hasLaunchPermission(conn, *image_id, account_id); err != nil { + if has, err := hasLaunchPermission(conn, *imageID, accountID); err != nil { return err } else if !has { - return fmt.Errorf("launch permission does not exist for '%s' on '%s'", account_id, *image_id) + return fmt.Errorf("launch permission does not exist for '%s' on '%s'", accountID, *imageID) } return nil } } -func testAccAWSAMILaunchPermissionDestroyed(account_id string, image_id *string) r.TestCheckFunc { +func testAccAWSAMILaunchPermissionDestroyed(accountID string, imageID *string) r.TestCheckFunc { return func(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn - if has, err := hasLaunchPermission(conn, *image_id, account_id); err != nil { + if has, err := hasLaunchPermission(conn, *imageID, accountID); err != nil { return err } else if has { - return fmt.Errorf("launch permission still exists for '%s' on '%s'", account_id, *image_id) + return fmt.Errorf("launch permission still exists for '%s' on '%s'", accountID, *imageID) } return nil } } -func testAccAWSAMILaunchPermissionConfig(account_id string, includeLaunchPermission bool) string { +// testAccAWSAMIDisappears is technically a "test check function" but really it +// exists to perform a side effect of deleting an AMI out from under a resource +// so we can test that Terraform will react properly +func testAccAWSAMIDisappears(imageID *string) r.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + req := &ec2.DeregisterImageInput{ + ImageId: aws.String(*imageID), + } + + _, err := conn.DeregisterImage(req) + if err != nil { + return err + } + + if err := resourceAwsAmiWaitForDestroy(*imageID, conn); err != nil { + return err + } + return nil + } +} + +func testAccAWSAMILaunchPermissionConfig(accountID string, includeLaunchPermission bool) string { base := ` resource "aws_ami_copy" "test" { name = "launch-permission-test" @@ -101,5 +143,5 @@ resource "aws_ami_launch_permission" "self-test" { image_id = "${aws_ami_copy.test.id}" account_id = "%s" } -`, account_id) +`, accountID) } From 07ff095755010653fa4274c11d0da853c34b7996 Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Sat, 8 Apr 2017 20:51:29 +0300 Subject: [PATCH 23/23] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc50fc96..32e49f8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ BUG FIXES: * provider/aws: Fix DynamoDB issues about GSIs indexes [GH-13256] * provider/aws: Fix `aws_s3_bucket` drift detection of logging options [GH-13281] * provider/aws: Update ElasticTranscoderPreset to have default for MaxFrameRate [GH-13422] + * provider/aws: Fix aws_ami_launch_permission refresh when AMI disappears [GH-13469] * provider/azurerm: Network Security Group - ignoring protocol casing at Import time [GH-13153] * provider/azurerm: Fix crash when importing Local Network Gateways [GH-13261] * provider/bitbucket: Fixed issue where provider would fail with an "EOF" error on some operations [GH-13390]