From 4f1504cda5fb0c80d6db719a6ac2b3ae619d5248 Mon Sep 17 00:00:00 2001 From: Michal Jankowski Date: Wed, 7 Dec 2016 14:50:06 -0800 Subject: [PATCH 01/96] =?UTF-8?q?-=20Exercise=20SecondaryPrivateIpAddressC?= =?UTF-8?q?ount=20from=20AWS=20SDK=20-=20Update=20Terraform=E2=80=99s=20do?= =?UTF-8?q?cumentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aws/resource_aws_network_interface.go | 61 +++++++++++++++++++ .../aws/r/network_interface.markdown | 5 +- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index 5c9f8263e..857237141 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "log" + "math" "strconv" "time" @@ -33,6 +34,12 @@ func resourceAwsNetworkInterface() *schema.Resource { ForceNew: true, }, + "private_ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "private_ips": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -41,6 +48,12 @@ func resourceAwsNetworkInterface() *schema.Resource { Set: schema.HashString, }, + "private_ips_count": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "security_groups": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -110,6 +123,10 @@ func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) request.Description = aws.String(v.(string)) } + if v, ok := d.GetOk("private_ips_count"); ok { + request.SecondaryPrivateIpAddressCount = aws.Int64(int64(v.(int))) + } + log.Printf("[DEBUG] Creating network interface") resp, err := conn.CreateNetworkInterface(request) if err != nil { @@ -144,6 +161,7 @@ func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) e eni := describeResp.NetworkInterfaces[0] d.Set("subnet_id", eni.SubnetId) + d.Set("private_ip", eni.PrivateIpAddress) d.Set("private_ips", flattenNetworkInterfacesPrivateIPAddresses(eni.PrivateIpAddresses)) d.Set("security_groups", flattenGroupIdentifiers(eni.Groups)) d.Set("source_dest_check", eni.SourceDestCheck) @@ -300,6 +318,49 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) d.SetPartial("source_dest_check") + if d.HasChange("private_ips_count") { + o, n := d.GetChange("private_ips_count") + private_ips := d.Get("private_ips").(*schema.Set).List() + private_ips_filtered := private_ips[:0] + primary_ip := d.Get("private_ip") + + for _, ip := range private_ips { + if ip != primary_ip { + private_ips_filtered = append(private_ips_filtered, ip) + } + } + + if o != nil && o != 0 && n != nil && n != len(private_ips_filtered) { + + diff := n.(int) - o.(int) + + // Surplus of IPs, add the diff + if diff > 0 { + input := &ec2.AssignPrivateIpAddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + SecondaryPrivateIpAddressCount: aws.Int64(int64(diff)), + } + _, err := conn.AssignPrivateIpAddresses(input) + if err != nil { + return fmt.Errorf("Failure to assign Private IPs: %s", err) + } + } + + if diff < 0 { + input := &ec2.UnassignPrivateIpAddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + PrivateIpAddresses: expandStringList(private_ips_filtered[0:int(math.Abs(float64(diff)))]), + } + _, err := conn.UnassignPrivateIpAddresses(input) + if err != nil { + return fmt.Errorf("Failure to unassign Private IPs: %s", err) + } + } + + d.SetPartial("private_ips_count") + } + } + if d.HasChange("security_groups") { request := &ec2.ModifyNetworkInterfaceAttributeInput{ NetworkInterfaceId: aws.String(d.Id()), diff --git a/website/source/docs/providers/aws/r/network_interface.markdown b/website/source/docs/providers/aws/r/network_interface.markdown index e52033ed3..636d1bcb4 100644 --- a/website/source/docs/providers/aws/r/network_interface.markdown +++ b/website/source/docs/providers/aws/r/network_interface.markdown @@ -31,6 +31,7 @@ The following arguments are supported: * `subnet_id` - (Required) Subnet ID to create the ENI in. * `description` - (Optional) A description for the network interface. * `private_ips` - (Optional) List of private IPs to assign to the ENI. +* `private_ips_count` - (Optional) Number of private IPs to assign to the ENI. * `security_groups` - (Optional) List of security group IDs to assign to the ENI. * `attachment` - (Optional) Block to define the attachment of the ENI. Documented below. * `source_dest_check` - (Optional) Whether to enable source destination checking for the ENI. Default true. @@ -57,8 +58,8 @@ The following attributes are exported: ## Import -Network Interfaces can be imported using the `id`, e.g. +Network Interfaces can be imported using the `id`, e.g. ``` $ terraform import aws_network_interface.test eni-e5aa89a3 -``` \ No newline at end of file +``` From 411db71d7018891b8370eaf59b66b47e4617a40c Mon Sep 17 00:00:00 2001 From: Michal Jankowski Date: Wed, 7 Dec 2016 17:55:11 -0800 Subject: [PATCH 02/96] - Add simple resource to attach ENI with instance - Add proper documentation --- builtin/providers/aws/provider.go | 1 + ...source_aws_network_interface_attachment.go | 116 ++++++++++++++++++ .../r/network_interface_attachment.markdown | 36 ++++++ 3 files changed, 153 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_network_interface_attachment.go create mode 100644 website/source/docs/providers/aws/r/network_interface_attachment.markdown diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 7b7aaabd5..19367de98 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -298,6 +298,7 @@ func Provider() terraform.ResourceProvider { "aws_default_route_table": resourceAwsDefaultRouteTable(), "aws_network_acl_rule": resourceAwsNetworkAclRule(), "aws_network_interface": resourceAwsNetworkInterface(), + "aws_network_interface_attachment": resourceAwsNetworkInterfaceAttachment(), "aws_opsworks_application": resourceAwsOpsworksApplication(), "aws_opsworks_stack": resourceAwsOpsworksStack(), "aws_opsworks_java_app_layer": resourceAwsOpsworksJavaAppLayer(), diff --git a/builtin/providers/aws/resource_aws_network_interface_attachment.go b/builtin/providers/aws/resource_aws_network_interface_attachment.go new file mode 100644 index 000000000..a3bd33425 --- /dev/null +++ b/builtin/providers/aws/resource_aws_network_interface_attachment.go @@ -0,0 +1,116 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "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/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsNetworkInterfaceAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsNetworkInterfaceAttachmentCreate, + Read: resourceAwsNetworkInterfaceRead, + Delete: resourceAwsNetworkInterfaceAttachmentDelete, + + Schema: map[string]*schema.Schema{ + "device_index": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "instance_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "network_interface_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsNetworkInterfaceAttachmentCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + device_index := d.Get("device_index").(int) + instance_id := d.Get("instance_id").(string) + network_interface_id := d.Get("network_interface_id").(string) + + opts := &ec2.AttachNetworkInterfaceInput{ + DeviceIndex: aws.Int64(int64(device_index)), + InstanceId: aws.String(instance_id), + NetworkInterfaceId: aws.String(network_interface_id), + } + + log.Printf("[DEBUG] Attaching network interface (%s) to instance (%s)", network_interface_id, instance_id) + resp, err := conn.AttachNetworkInterface(opts) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + return fmt.Errorf("[WARN] Error attaching network interface (%s) to instance (%s), message: \"%s\", code: \"%s\"", + network_interface_id, instance_id, awsErr.Message(), awsErr.Code()) + } + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"false"}, + Target: []string{"true"}, + Refresh: networkInterfaceAttachmentRefreshFunc(conn, network_interface_id), + Timeout: 5 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for Volume (%s) to attach to Instance: %s, error: %s", network_interface_id, instance_id, err) + } + + d.SetId(*resp.AttachmentId) + return resourceAwsNetworkInterfaceRead(d, meta) +} + +func resourceAwsNetworkInterfaceAttachmentDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + network_interface_id := d.Get("network_interface_id").(string) + + detach_request := &ec2.DetachNetworkInterfaceInput{ + AttachmentId: aws.String(d.Id()), + Force: aws.Bool(true), + } + + _, detach_err := conn.DetachNetworkInterface(detach_request) + if detach_err != nil { + if awsErr, _ := detach_err.(awserr.Error); awsErr.Code() != "InvalidAttachmentID.NotFound" { + return fmt.Errorf("Error detaching ENI: %s", detach_err) + } + } + + log.Printf("[DEBUG] Waiting for ENI (%s) to become dettached", network_interface_id) + stateConf := &resource.StateChangeConf{ + Pending: []string{"true"}, + Target: []string{"false"}, + Refresh: networkInterfaceAttachmentRefreshFunc(conn, network_interface_id), + Timeout: 10 * time.Minute, + } + + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for ENI (%s) to become dettached: %s", network_interface_id, err) + } + + return nil +} diff --git a/website/source/docs/providers/aws/r/network_interface_attachment.markdown b/website/source/docs/providers/aws/r/network_interface_attachment.markdown new file mode 100644 index 000000000..ae56d338b --- /dev/null +++ b/website/source/docs/providers/aws/r/network_interface_attachment.markdown @@ -0,0 +1,36 @@ +--- +layout: "aws" +page_title: "AWS: aws_network_interface_attachment" +sidebar_current: "docs-aws-resource-network-interface-attachment" +description: |- + Attach an Elastic network interface (ENI) resource with EC2 instance. +--- + +# aws\_network\_interface\_attachment + +Attach an Elastic network interface (ENI) resource with EC2 instance. + +## Example Usage + +``` +resource "aws_network_interface_attachment" "test" { + instance_id = "${aws_instance.test.id}" + network_interface_id = "${aws_network_interface.test.id}" + device_index = 0 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `instance_id` - (Required) Instance ID to attach. +* `network_interface_id` - (Required) ENI ID to attach. +* `device_index` - (Required) Network interface index (int). + +## Attributes Reference + +The following attributes are exported: + +* `instance_id` - Instance ID. +* `network_interface_id` - Network interface ID. From 733f1ca1e716dd96317d04bcbc8479cc5cc0cec3 Mon Sep 17 00:00:00 2001 From: Pryz Date: Wed, 8 Mar 2017 12:36:01 -0800 Subject: [PATCH 03/96] Add coalescelist interpolation function --- config/interpolate_funcs.go | 25 +++++++++++++++++ config/interpolate_funcs_test.go | 27 +++++++++++++++++++ .../docs/configuration/interpolation.html.md | 3 +++ 3 files changed, 55 insertions(+) diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index ad543c308..d9a357a1d 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -60,6 +60,7 @@ func Funcs() map[string]ast.Function { "cidrnetmask": interpolationFuncCidrNetmask(), "cidrsubnet": interpolationFuncCidrSubnet(), "coalesce": interpolationFuncCoalesce(), + "coalescelist": interpolationFuncCoalesceList(), "compact": interpolationFuncCompact(), "concat": interpolationFuncConcat(), "distinct": interpolationFuncDistinct(), @@ -318,6 +319,30 @@ func interpolationFuncCoalesce() ast.Function { } } +// interpolationFuncCoalesceList implements the "coalescelist" function that +// returns the first non empty list from the provided input +func interpolationFuncCoalesceList() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeList}, + ReturnType: ast.TypeList, + Variadic: true, + VariadicType: ast.TypeList, + Callback: func(args []interface{}) (interface{}, error) { + if len(args) < 2 { + return nil, fmt.Errorf("must provide at least two arguments") + } + for _, arg := range args { + argument := arg.([]ast.Variable) + + if len(argument) > 0 { + return argument, nil + } + } + return make([]ast.Variable, 0), nil + }, + } +} + // interpolationFuncConcat implements the "concat" function that concatenates // multiple lists. func interpolationFuncConcat() ast.Function { diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 193fcd147..4f1c3fa0a 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -615,6 +615,33 @@ func TestInterpolateFuncCoalesce(t *testing.T) { }) } +func TestInterpolateFuncCoalesceList(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${coalescelist(list("first"), list("second"), list("third"))}`, + []interface{}{"first"}, + false, + }, + { + `${coalescelist(list(), list("second"), list("third"))}`, + []interface{}{"second"}, + false, + }, + { + `${coalescelist(list(), list(), list())}`, + []interface{}{}, + false, + }, + { + `${coalescelist(list("foo"))}`, + nil, + true, + }, + }, + }) +} + func TestInterpolateFuncConcat(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index 2d1e3052b..cb7ad8cfe 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -169,6 +169,9 @@ The supported built-in functions are: * `coalesce(string1, string2, ...)` - Returns the first non-empty value from the given arguments. At least two arguments must be provided. + * `coalescelist(list1, list2, ...)` - Returns the first non-empty list from + the given arguments. At least two arguments must be provided. + * `compact(list)` - Removes empty string elements from a list. This can be useful in some cases, for example when passing joined lists as module variables or when parsing module outputs. From 7aa2ce83416b0e44cc7b34db081eda69a29061b3 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 20 Apr 2017 17:26:50 -0400 Subject: [PATCH 04/96] add -reconfigure option for init The reconfigure flag will force init to ignore any saved backend state. This is useful when a user does not want any backend migration to happen, or if the saved configuration can't be loaded at all for some reason. --- command/init.go | 9 ++++++--- command/meta.go | 3 +++ command/meta_backend.go | 7 +++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/command/init.go b/command/init.go index 6b7fbaf21..54a244cdd 100644 --- a/command/init.go +++ b/command/init.go @@ -30,6 +30,7 @@ func (c *InitCommand) Run(args []string) int { cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") + cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { @@ -223,6 +224,10 @@ Options: times. The backend type must be in the configuration itself. + -force-copy Suppress prompts about copying state data. This is + equivalent to providing a "yes" to all confirmation + prompts. + -get=true Download any modules for this configuration. -input=true Ask for input if necessary. If false, will error if @@ -234,9 +239,7 @@ Options: -no-color If specified, output won't contain any color. - -force-copy Suppress prompts about copying state data. This is - equivalent to providing a "yes" to all confirmation - prompts. + -reconfigure Reconfigure the backend, ignoring any saved configuration. ` return strings.TrimSpace(helpText) } diff --git a/command/meta.go b/command/meta.go index c494d9697..0b9375f72 100644 --- a/command/meta.go +++ b/command/meta.go @@ -95,6 +95,8 @@ type Meta struct { // // forceInitCopy suppresses confirmation for copying state data during // init. + // + // reconfigure forces init to ignore any stored configuration. statePath string stateOutPath string backupPath string @@ -104,6 +106,7 @@ type Meta struct { stateLock bool stateLockTimeout time.Duration forceInitCopy bool + reconfigure bool } // initStatePaths is used to initialize the default values for diff --git a/command/meta_backend.go b/command/meta_backend.go index 415efa02c..6f75acc77 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -352,6 +352,13 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, error) { s = terraform.NewState() } + // if we want to force reconfiguration of the backend, we set the backend + // state to nil on this copy. This will direct us through the correct + // configuration path in the switch statement below. + if m.reconfigure { + s.Backend = nil + } + // Upon return, we want to set the state we're using in-memory so that // we can access it for commands. m.backendState = nil From 0e0f0b64b9ef7fbf3230a99f7681fd7aaa51773a Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 20 Apr 2017 17:29:40 -0400 Subject: [PATCH 05/96] add init -reconfigure test Check that we can reconfigure a backend ignoring the saved config, and without effecting the saved backend. --- command/meta_backend_test.go | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/command/meta_backend_test.go b/command/meta_backend_test.go index ffb6e3b61..aa4a02d2c 100644 --- a/command/meta_backend_test.go +++ b/command/meta_backend_test.go @@ -983,6 +983,59 @@ func TestMetaBackend_configuredChange(t *testing.T) { } } +// Reconfiguring with an already configured backend. +// This should ignore the existing backend config, and configure the new +// backend is if this is the first time. +func TestMetaBackend_reconfigureChange(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("backend-change-single-to-single"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + // Register the single-state backend + backendinit.Set("local-single", backendlocal.TestNewLocalSingle) + defer backendinit.Set("local-single", nil) + + // Setup the meta + m := testMetaBackend(t, nil) + + // this should not ask for input + m.input = false + + // cli flag -reconfigure + m.reconfigure = true + + // Get the backend + b, err := m.Backend(&BackendOpts{Init: true}) + if err != nil { + t.Fatalf("bad: %s", err) + } + + // Check the state + s, err := b.State(backend.DefaultStateName) + if err != nil { + t.Fatalf("bad: %s", err) + } + if err := s.RefreshState(); err != nil { + t.Fatalf("bad: %s", err) + } + newState := s.State() + if newState != nil || !newState.Empty() { + t.Fatal("state should be nil/empty after forced reconfiguration") + } + + // verify that the old state is still there + s = (&state.LocalState{Path: "local-state.tfstate"}) + if err := s.RefreshState(); err != nil { + t.Fatal(err) + } + oldState := s.State() + if oldState == nil || oldState.Empty() { + t.Fatal("original state should be untouched") + } +} + // Changing a configured backend, copying state func TestMetaBackend_configuredChangeCopy(t *testing.T) { // Create a temporary working directory that is empty From bb6ef3fd3eb0c05f95cb9810b36007c74380f8d1 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 20 Apr 2017 17:34:14 -0400 Subject: [PATCH 06/96] update init docs add -reconfigure, and fix ordering of the other flags --- website/source/docs/commands/init.html.markdown | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/website/source/docs/commands/init.html.markdown b/website/source/docs/commands/init.html.markdown index 1222b46c7..57aeaed89 100644 --- a/website/source/docs/commands/init.html.markdown +++ b/website/source/docs/commands/init.html.markdown @@ -49,6 +49,9 @@ The command-line flags are all optional. The list of available flags are: for the backend. This can be specified multiple times. Flags specified later in the line override those specified earlier if they conflict. +* `-force-copy` - Suppress prompts about copying state data. This is equivalent + to providing a "yes" to all confirmation prompts. + * `-get=true` - Download any modules for this configuration. * `-input=true` - Ask for input interactively if necessary. If this is false @@ -60,8 +63,7 @@ The command-line flags are all optional. The list of available flags are: * `-no-color` - If specified, output won't contain any color. -* `-force-copy` - Suppress prompts about copying state data. This is equivalent - to providing a "yes" to all confirmation prompts. +* `-reconfigure` - Reconfigure the backend, ignoring any saved configuration. ## Backend Config From 3af9aa283d795703be5856c1c22ae9cc25071955 Mon Sep 17 00:00:00 2001 From: Ola Karlsson Date: Fri, 21 Apr 2017 00:22:06 +0000 Subject: [PATCH 07/96] Adding Import to the Google network resource --- .../google/import_compute_network_test.go | 65 +++++++++++++++++++ .../google/resource_compute_network.go | 6 ++ 2 files changed, 71 insertions(+) create mode 100644 builtin/providers/google/import_compute_network_test.go diff --git a/builtin/providers/google/import_compute_network_test.go b/builtin/providers/google/import_compute_network_test.go new file mode 100644 index 000000000..8e6ab769b --- /dev/null +++ b/builtin/providers/google/import_compute_network_test.go @@ -0,0 +1,65 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccComputeNetwork_importBasic(t *testing.T) { + resourceName := "google_compute_network.foobar" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeNetwork_basic, + }, { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + //ImportStateVerifyIgnore: []string{"ipv4_range", "name"}, + }, + }, + }) +} + +func TestAccComputeNetwork_importAuto_subnet(t *testing.T) { + resourceName := "google_compute_network.bar" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeNetwork_auto_subnet, + }, { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeNetwork_importCustom_subnet(t *testing.T) { + resourceName := "google_compute_network.baz" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeNetwork_custom_subnet, + }, { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/google/resource_compute_network.go b/builtin/providers/google/resource_compute_network.go index 3356edcc8..ccd75ae08 100644 --- a/builtin/providers/google/resource_compute_network.go +++ b/builtin/providers/google/resource_compute_network.go @@ -14,6 +14,9 @@ func resourceComputeNetwork() *schema.Resource { Create: resourceComputeNetworkCreate, Read: resourceComputeNetworkRead, Delete: resourceComputeNetworkDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ @@ -142,6 +145,9 @@ func resourceComputeNetworkRead(d *schema.ResourceData, meta interface{}) error d.Set("gateway_ipv4", network.GatewayIPv4) d.Set("self_link", network.SelfLink) + d.Set("ipv4_range", network.IPv4Range) + d.Set("name", network.Name) + d.Set("auto_create_subnetworks", network.AutoCreateSubnetworks) return nil } From 081121d29b575d012209f83cf8c8eed35ad1b1a8 Mon Sep 17 00:00:00 2001 From: Devin Lundberg Date: Thu, 20 Apr 2017 23:30:09 -0700 Subject: [PATCH 08/96] Fix invalid MIME formatting in multipart cloudinit userdata (#13752) * Fix invalid MIME formatting in multipart cloudinit userdata Per https://tools.ietf.org/html/rfc822#appendix-B.2, MIME headers and Body need to be separated by two new lines (or CRLFs in this case). The email parser in python can handle this which is what cloud-init uses but this bug causes problems if you try to parse the multipart message by languages other than python. * Fix test cases --- builtin/providers/template/datasource_cloudinit_config.go | 2 +- .../providers/template/datasource_cloudinit_config_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/providers/template/datasource_cloudinit_config.go b/builtin/providers/template/datasource_cloudinit_config.go index 9fee9fb49..8a24c329a 100644 --- a/builtin/providers/template/datasource_cloudinit_config.go +++ b/builtin/providers/template/datasource_cloudinit_config.go @@ -140,7 +140,7 @@ func renderPartsToWriter(parts cloudInitParts, writer io.Writer) error { } writer.Write([]byte(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\n", mimeWriter.Boundary()))) - writer.Write([]byte("MIME-Version: 1.0\r\n")) + writer.Write([]byte("MIME-Version: 1.0\r\n\r\n")) for _, part := range parts { header := textproto.MIMEHeader{} diff --git a/builtin/providers/template/datasource_cloudinit_config_test.go b/builtin/providers/template/datasource_cloudinit_config_test.go index 80f37348f..7ea49ca7d 100644 --- a/builtin/providers/template/datasource_cloudinit_config_test.go +++ b/builtin/providers/template/datasource_cloudinit_config_test.go @@ -22,7 +22,7 @@ func TestRender(t *testing.T) { content = "baz" } }`, - "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n", + "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n", }, { `data "template_cloudinit_config" "foo" { @@ -35,7 +35,7 @@ func TestRender(t *testing.T) { filename = "foobar.sh" } }`, - "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Disposition: attachment; filename=\"foobar.sh\"\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n", + "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n\r\n--MIMEBOUNDARY\r\nContent-Disposition: attachment; filename=\"foobar.sh\"\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n", }, { `data "template_cloudinit_config" "foo" { @@ -51,7 +51,7 @@ func TestRender(t *testing.T) { content = "ffbaz" } }`, - "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nffbaz\r\n--MIMEBOUNDARY--\r\n", + "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nffbaz\r\n--MIMEBOUNDARY--\r\n", }, } From 43d518342c2dc55c66e2596f70e485dc20a9bde9 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 21 Apr 2017 08:44:40 +0200 Subject: [PATCH 09/96] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 704ec194d..4e9825115 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## 0.9.4 (Unreleased) +BACKWARDS INCOMPATIBILITIES / NOTES: + + * provider/template: Fix invalid MIME formatting in `template_cloudinit_config`. + While the change itself is not breaking the data source it may be referenced + e.g. in `aws_launch_configuration` and similar resources which are immutable + and the formatting change will therefore trigger recreation [GH-13752] + FEATURES: * **New Provider:** `opc` - Oracle Public Cloud [GH-13468] From 1654464a6476a21b736b2af1c218f05650f49ba2 Mon Sep 17 00:00:00 2001 From: Andrew King Date: Fri, 21 Apr 2017 16:48:34 +1000 Subject: [PATCH 10/96] Update environments.html.md (#13353) Use current local state directory --- website/source/docs/state/environments.html.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/state/environments.html.md b/website/source/docs/state/environments.html.md index e6fa12a9a..b7f3e2a3c 100644 --- a/website/source/docs/state/environments.html.md +++ b/website/source/docs/state/environments.html.md @@ -119,7 +119,7 @@ aren't any more complex than that. Terraform wraps this simple notion with a set of protections and support for remote state. For local state, Terraform stores the state environments in a folder -`terraform.state.d`. This folder should be committed to version control +`terraform.tfstate.d`. This folder should be committed to version control (just like local-only `terraform.tfstate`). For [remote state](/docs/state/remote.html), the environments are stored From 2a7b0e7e36fccf2d9974514eeb518fe5a74df204 Mon Sep 17 00:00:00 2001 From: Pasha Palangpour Date: Fri, 21 Apr 2017 05:38:05 -0400 Subject: [PATCH 11/96] provider/ns1: record documentation improvements (#13786) * provider/ns1: Adds acctest for SRV type records * ns1/provider: Adds dns record examples to documentation. --- builtin/providers/ns1/resource_record_test.go | 55 ++++++++++++++++--- .../docs/providers/ns1/r/record.html.markdown | 34 ++++++++++-- 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/builtin/providers/ns1/resource_record_test.go b/builtin/providers/ns1/resource_record_test.go index 36095b579..ec5075303 100644 --- a/builtin/providers/ns1/resource_record_test.go +++ b/builtin/providers/ns1/resource_record_test.go @@ -29,7 +29,7 @@ func TestAccRecord_basic(t *testing.T) { testAccCheckRecordUseClientSubnet(&record, true), testAccCheckRecordRegionName(&record, []string{"cal"}), // testAccCheckRecordAnswerMetaWeight(&record, 10), - testAccCheckRecordAnswerRdata(&record, "test1.terraform-record-test.io"), + testAccCheckRecordAnswerRdata(&record, 0, "test1.terraform-record-test.io"), ), }, }, @@ -52,7 +52,7 @@ func TestAccRecord_updated(t *testing.T) { testAccCheckRecordUseClientSubnet(&record, true), testAccCheckRecordRegionName(&record, []string{"cal"}), // testAccCheckRecordAnswerMetaWeight(&record, 10), - testAccCheckRecordAnswerRdata(&record, "test1.terraform-record-test.io"), + testAccCheckRecordAnswerRdata(&record, 0, "test1.terraform-record-test.io"), ), }, resource.TestStep{ @@ -64,7 +64,7 @@ func TestAccRecord_updated(t *testing.T) { testAccCheckRecordUseClientSubnet(&record, false), testAccCheckRecordRegionName(&record, []string{"ny", "wa"}), // testAccCheckRecordAnswerMetaWeight(&record, 5), - testAccCheckRecordAnswerRdata(&record, "test2.terraform-record-test.io"), + testAccCheckRecordAnswerRdata(&record, 0, "test2.terraform-record-test.io"), ), }, }, @@ -85,7 +85,31 @@ func TestAccRecord_SPF(t *testing.T) { testAccCheckRecordDomain(&record, "terraform-record-test.io"), testAccCheckRecordTTL(&record, 86400), testAccCheckRecordUseClientSubnet(&record, true), - testAccCheckRecordAnswerRdata(&record, "v=DKIM1; k=rsa; p=XXXXXXXX"), + testAccCheckRecordAnswerRdata(&record, 0, "v=DKIM1; k=rsa; p=XXXXXXXX"), + ), + }, + }, + }) +} + +func TestAccRecord_SRV(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: testAccRecordSRV, + Check: resource.ComposeTestCheckFunc( + testAccCheckRecordExists("ns1_record.srv", &record), + testAccCheckRecordDomain(&record, "_some-server._tcp.terraform-record-test.io"), + testAccCheckRecordTTL(&record, 86400), + testAccCheckRecordUseClientSubnet(&record, true), + testAccCheckRecordAnswerRdata(&record, 0, "10"), + testAccCheckRecordAnswerRdata(&record, 1, "0"), + testAccCheckRecordAnswerRdata(&record, 2, "2380"), + testAccCheckRecordAnswerRdata(&record, 3, "node-1.terraform-record-test.io"), ), }, }, @@ -206,12 +230,12 @@ func testAccCheckRecordAnswerMetaWeight(r *dns.Record, expected float64) resourc } } -func testAccCheckRecordAnswerRdata(r *dns.Record, expected string) resource.TestCheckFunc { +func testAccCheckRecordAnswerRdata(r *dns.Record, idx int, expected string) resource.TestCheckFunc { return func(s *terraform.State) error { recordAnswer := r.Answers[0] - recordAnswerString := recordAnswer.Rdata[0] + recordAnswerString := recordAnswer.Rdata[idx] if recordAnswerString != expected { - return fmt.Errorf("Answers[0].Rdata[0]: got: %#v want: %#v", recordAnswerString, expected) + return fmt.Errorf("Answers[0].Rdata[%d]: got: %#v want: %#v", idx, recordAnswerString, expected) } return nil } @@ -335,3 +359,20 @@ resource "ns1_zone" "test" { zone = "terraform-record-test.io" } ` + +const testAccRecordSRV = ` +resource "ns1_record" "srv" { + zone = "${ns1_zone.test.zone}" + domain = "_some-server._tcp.${ns1_zone.test.zone}" + type = "SRV" + ttl = 86400 + use_client_subnet = "true" + answers { + answer = "10 0 2380 node-1.${ns1_zone.test.zone}" + } +} + +resource "ns1_zone" "test" { + zone = "terraform-record-test.io" +} +` diff --git a/website/source/docs/providers/ns1/r/record.html.markdown b/website/source/docs/providers/ns1/r/record.html.markdown index fb03a78f5..9b7148b84 100644 --- a/website/source/docs/providers/ns1/r/record.html.markdown +++ b/website/source/docs/providers/ns1/r/record.html.markdown @@ -51,16 +51,38 @@ The following arguments are supported: * `ttl` - (Optional) The records' time to live. * `link` - (Optional) The target record to link to. This means this record is a 'linked' record, and it inherits all properties from its target. * `use_client_subnet` - (Optional) Whether to use EDNS client subnet data when available(in filter chain). -* `answers` - (Optional) The list of the RDATA fields for the records' specified type. Answers are documented below. -* `filters` - (Optional) The list of NS1 filters for the record(order matters). Filters are documented below. +* `answers` - (Optional) One or more NS1 answers for the records' specified type. Answers are documented below. +* `filters` - (Optional) One or more NS1 filters for the record(order matters). Filters are documented below. Answers (`answers`) support the following: -* `answer` - (Required) List of RDATA fields. -* `region` - (Required) The region this answer belongs to. +* `answer` - (Required) Space delimited string of RDATA fields dependent on the record type. + + A: + + answer = "1.2.3.4" + + CNAME: + + answer = "www.example.com" + + MX: + + answer = "5 mail.example.com" + + SRV: + + answer = "10 0 2380 node-1.example.com" + + SPF: + + answer = "v=DKIM1; k=rsa; p=XXXXXXXX" + + +* `region` - (Optional) The region(or group) name that this answer belongs to. Filters (`filters`) support the following: * `filter` - (Required) The type of filter. -* `disabled` - (Required) Determines whether the filter is applied in the filter chain. -* `config` - (Required) The filters' configuration. Simple key/value pairs determined by the filter type. +* `disabled` - (Optional) Determines whether the filter is applied in the filter chain. +* `config` - (Optional) The filters' configuration. Simple key/value pairs determined by the filter type. From 83c4d5d71d83763c1029bbd1266608602f7327ed Mon Sep 17 00:00:00 2001 From: stack72 Date: Fri, 21 Apr 2017 12:51:24 +0300 Subject: [PATCH 12/96] provider/google: Documenting the import process for compute_network --- .../providers/google/r/compute_network.html.markdown | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/source/docs/providers/google/r/compute_network.html.markdown b/website/source/docs/providers/google/r/compute_network.html.markdown index 0146dc8a9..a93136880 100644 --- a/website/source/docs/providers/google/r/compute_network.html.markdown +++ b/website/source/docs/providers/google/r/compute_network.html.markdown @@ -56,3 +56,12 @@ exported: * `name` - The unique name of the network. * `self_link` - The URI of the created resource. + + +## Import + +Networks can be imported using the `name`, e.g. + +``` +$ terraform import google_compute_network.public my_network_name +``` \ No newline at end of file From 7c77687a557409b510d52ddcc64d86638f8093ee Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Fri, 21 Apr 2017 12:53:36 +0300 Subject: [PATCH 13/96] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e9825115..e2656327a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ IMPROVEMENTS: * provider/azurerm: Allow Azure China region support [GH-13767] * provider/digitalocean: Export droplet prices [GH-13720] * provider/google: `google_compute_address` and `google_compute_global_address` are now importable [GH-13270] + * provider/google: `google_compute_network` is now importable [GH-13834] * provider/vault: `vault_generic_secret` resource can now optionally detect drift if it has appropriate access [GH-11776] BUG FIXES: From 80d940d154c8e468e41adad77feaff957382b24b Mon Sep 17 00:00:00 2001 From: Gauthier Wallet Date: Fri, 21 Apr 2017 05:53:48 -0400 Subject: [PATCH 14/96] provider/aws: Added Cognito Identity Pool (#13783) --- builtin/providers/aws/config.go | 3 + .../import_aws_cognito_identity_pool_test.go | 30 ++ builtin/providers/aws/provider.go | 1 + .../aws/resource_aws_cognito_identity_pool.go | 238 +++++++++++ ...resource_aws_cognito_identity_pool_test.go | 371 ++++++++++++++++++ builtin/providers/aws/structure.go | 72 ++++ builtin/providers/aws/validators.go | 73 ++++ builtin/providers/aws/validators_test.go | 167 +++++++- .../aws/r/cognito_identity_pool.markdown | 78 ++++ website/source/layouts/aws.erb | 32 +- 10 files changed, 1052 insertions(+), 13 deletions(-) create mode 100644 builtin/providers/aws/import_aws_cognito_identity_pool_test.go create mode 100644 builtin/providers/aws/resource_aws_cognito_identity_pool.go create mode 100644 builtin/providers/aws/resource_aws_cognito_identity_pool_test.go create mode 100644 website/source/docs/providers/aws/r/cognito_identity_pool.markdown diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 17105d259..78fa93deb 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -28,6 +28,7 @@ import ( "github.com/aws/aws-sdk-go/service/codecommit" "github.com/aws/aws-sdk-go/service/codedeploy" "github.com/aws/aws-sdk-go/service/codepipeline" + "github.com/aws/aws-sdk-go/service/cognitoidentity" "github.com/aws/aws-sdk-go/service/configservice" "github.com/aws/aws-sdk-go/service/databasemigrationservice" "github.com/aws/aws-sdk-go/service/directoryservice" @@ -111,6 +112,7 @@ type AWSClient struct { cloudwatchconn *cloudwatch.CloudWatch cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs cloudwatcheventsconn *cloudwatchevents.CloudWatchEvents + cognitoconn *cognitoidentity.CognitoIdentity configconn *configservice.ConfigService dmsconn *databasemigrationservice.DatabaseMigrationService dsconn *directoryservice.DirectoryService @@ -306,6 +308,7 @@ func (c *Config) Client() (interface{}, error) { client.codebuildconn = codebuild.New(sess) client.codedeployconn = codedeploy.New(sess) client.configconn = configservice.New(sess) + client.cognitoconn = cognitoidentity.New(sess) client.dmsconn = databasemigrationservice.New(sess) client.codepipelineconn = codepipeline.New(sess) client.dsconn = directoryservice.New(sess) diff --git a/builtin/providers/aws/import_aws_cognito_identity_pool_test.go b/builtin/providers/aws/import_aws_cognito_identity_pool_test.go new file mode 100644 index 000000000..bdd2caec8 --- /dev/null +++ b/builtin/providers/aws/import_aws_cognito_identity_pool_test.go @@ -0,0 +1,30 @@ +package aws + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAWSCognitoIdentityPool_importBasic(t *testing.T) { + resourceName := "aws_cognito_identity_pool.main" + rName := acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoIdentityPoolConfig_basic(rName), + }, + + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 889d9afcf..a353d5eb3 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -258,6 +258,7 @@ func Provider() terraform.ResourceProvider { "aws_config_configuration_recorder": resourceAwsConfigConfigurationRecorder(), "aws_config_configuration_recorder_status": resourceAwsConfigConfigurationRecorderStatus(), "aws_config_delivery_channel": resourceAwsConfigDeliveryChannel(), + "aws_cognito_identity_pool": resourceAwsCognitoIdentityPool(), "aws_autoscaling_lifecycle_hook": resourceAwsAutoscalingLifecycleHook(), "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), "aws_codedeploy_app": resourceAwsCodeDeployApp(), diff --git a/builtin/providers/aws/resource_aws_cognito_identity_pool.go b/builtin/providers/aws/resource_aws_cognito_identity_pool.go new file mode 100644 index 000000000..b85472cf9 --- /dev/null +++ b/builtin/providers/aws/resource_aws_cognito_identity_pool.go @@ -0,0 +1,238 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/cognitoidentity" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsCognitoIdentityPool() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCognitoIdentityPoolCreate, + Read: resourceAwsCognitoIdentityPoolRead, + Update: resourceAwsCognitoIdentityPoolUpdate, + Delete: resourceAwsCognitoIdentityPoolDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "identity_pool_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateCognitoIdentityPoolName, + }, + + "cognito_identity_providers": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCognitoIdentityProvidersClientId, + }, + "provider_name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCognitoIdentityProvidersProviderName, + }, + "server_side_token_check": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, + + "developer_provider_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, // Forcing a new resource since it cannot be edited afterwards + ValidateFunc: validateCognitoProviderDeveloperName, + }, + + "allow_unauthenticated_identities": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "openid_connect_provider_arns": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateArn, + }, + }, + + "saml_provider_arns": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateArn, + }, + }, + + "supported_login_providers": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCognitoSupportedLoginProviders, + }, + }, + }, + } +} + +func resourceAwsCognitoIdentityPoolCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoconn + log.Print("[DEBUG] Creating Cognito Identity Pool") + + params := &cognitoidentity.CreateIdentityPoolInput{ + IdentityPoolName: aws.String(d.Get("identity_pool_name").(string)), + AllowUnauthenticatedIdentities: aws.Bool(d.Get("allow_unauthenticated_identities").(bool)), + } + + if v, ok := d.GetOk("developer_provider_name"); ok { + params.DeveloperProviderName = aws.String(v.(string)) + } + + if v, ok := d.GetOk("supported_login_providers"); ok { + params.SupportedLoginProviders = expandCognitoSupportedLoginProviders(v.(map[string]interface{})) + } + + if v, ok := d.GetOk("cognito_identity_providers"); ok { + params.CognitoIdentityProviders = expandCognitoIdentityProviders(v.(*schema.Set)) + } + + if v, ok := d.GetOk("saml_provider_arns"); ok { + params.SamlProviderARNs = expandStringList(v.([]interface{})) + } + + if v, ok := d.GetOk("openid_connect_provider_arns"); ok { + params.OpenIdConnectProviderARNs = expandStringList(v.([]interface{})) + } + + entity, err := conn.CreateIdentityPool(params) + if err != nil { + return fmt.Errorf("Error creating Cognito Identity Pool: %s", err) + } + + d.SetId(*entity.IdentityPoolId) + + return resourceAwsCognitoIdentityPoolRead(d, meta) +} + +func resourceAwsCognitoIdentityPoolRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoconn + log.Printf("[DEBUG] Reading Cognito Identity Pool: %s", d.Id()) + + ip, err := conn.DescribeIdentityPool(&cognitoidentity.DescribeIdentityPoolInput{ + IdentityPoolId: aws.String(d.Id()), + }) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" { + d.SetId("") + return nil + } + return err + } + + d.Set("identity_pool_name", ip.IdentityPoolName) + d.Set("allow_unauthenticated_identities", ip.AllowUnauthenticatedIdentities) + d.Set("developer_provider_name", ip.DeveloperProviderName) + + if ip.CognitoIdentityProviders != nil { + if err := d.Set("cognito_identity_providers", flattenCognitoIdentityProviders(ip.CognitoIdentityProviders)); err != nil { + return fmt.Errorf("[DEBUG] Error setting cognito_identity_providers error: %#v", err) + } + } + + if ip.OpenIdConnectProviderARNs != nil { + if err := d.Set("openid_connect_provider_arns", flattenStringList(ip.OpenIdConnectProviderARNs)); err != nil { + return fmt.Errorf("[DEBUG] Error setting openid_connect_provider_arns error: %#v", err) + } + } + + if ip.SamlProviderARNs != nil { + if err := d.Set("saml_provider_arns", flattenStringList(ip.SamlProviderARNs)); err != nil { + return fmt.Errorf("[DEBUG] Error setting saml_provider_arns error: %#v", err) + } + } + + if ip.SupportedLoginProviders != nil { + if err := d.Set("supported_login_providers", flattenCognitoSupportedLoginProviders(ip.SupportedLoginProviders)); err != nil { + return fmt.Errorf("[DEBUG] Error setting supported_login_providers error: %#v", err) + } + } + + return nil +} + +func resourceAwsCognitoIdentityPoolUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoconn + log.Print("[DEBUG] Updating Cognito Identity Pool") + + params := &cognitoidentity.IdentityPool{ + IdentityPoolId: aws.String(d.Id()), + AllowUnauthenticatedIdentities: aws.Bool(d.Get("allow_unauthenticated_identities").(bool)), + IdentityPoolName: aws.String(d.Get("identity_pool_name").(string)), + } + + if d.HasChange("developer_provider_name") { + params.DeveloperProviderName = aws.String(d.Get("developer_provider_name").(string)) + } + + if d.HasChange("cognito_identity_providers") { + params.CognitoIdentityProviders = expandCognitoIdentityProviders(d.Get("cognito_identity_providers").(*schema.Set)) + } + + if d.HasChange("supported_login_providers") { + params.SupportedLoginProviders = expandCognitoSupportedLoginProviders(d.Get("supported_login_providers").(map[string]interface{})) + } + + if d.HasChange("openid_connect_provider_arns") { + params.OpenIdConnectProviderARNs = expandStringList(d.Get("openid_connect_provider_arns").([]interface{})) + } + + if d.HasChange("saml_provider_arns") { + params.SamlProviderARNs = expandStringList(d.Get("saml_provider_arns").([]interface{})) + } + + _, err := conn.UpdateIdentityPool(params) + if err != nil { + return fmt.Errorf("Error creating Cognito Identity Pool: %s", err) + } + + return resourceAwsCognitoIdentityPoolRead(d, meta) +} + +func resourceAwsCognitoIdentityPoolDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoconn + log.Printf("[DEBUG] Deleting Cognito Identity Pool: %s", d.Id()) + + return resource.Retry(5*time.Minute, func() *resource.RetryError { + _, err := conn.DeleteIdentityPool(&cognitoidentity.DeleteIdentityPoolInput{ + IdentityPoolId: aws.String(d.Id()), + }) + + if err == nil { + return nil + } + + return resource.NonRetryableError(err) + }) +} diff --git a/builtin/providers/aws/resource_aws_cognito_identity_pool_test.go b/builtin/providers/aws/resource_aws_cognito_identity_pool_test.go new file mode 100644 index 000000000..6ee0b1955 --- /dev/null +++ b/builtin/providers/aws/resource_aws_cognito_identity_pool_test.go @@ -0,0 +1,371 @@ +package aws + +import ( + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/cognitoidentity" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSCognitoIdentityPool_basic(t *testing.T) { + name := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + updatedName := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoIdentityPoolDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoIdentityPoolConfig_basic(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "allow_unauthenticated_identities", "false"), + ), + }, + { + Config: testAccAWSCognitoIdentityPoolConfig_basic(updatedName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", updatedName)), + ), + }, + }, + }) +} + +func TestAccAWSCognitoIdentityPool_supportedLoginProviders(t *testing.T) { + name := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoIdentityPoolDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoIdentityPoolConfig_supportedLoginProviders(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "supported_login_providers.graph.facebook.com", "7346241598935555"), + ), + }, + { + Config: testAccAWSCognitoIdentityPoolConfig_supportedLoginProvidersModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "supported_login_providers.graph.facebook.com", "7346241598935552"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "supported_login_providers.accounts.google.com", "123456789012.apps.googleusercontent.com"), + ), + }, + { + Config: testAccAWSCognitoIdentityPoolConfig_basic(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + ), + }, + }, + }) +} + +func TestAccAWSCognitoIdentityPool_openidConnectProviderArns(t *testing.T) { + name := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoIdentityPoolDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoIdentityPoolConfig_openidConnectProviderArns(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "openid_connect_provider_arns.#", "1"), + ), + }, + { + Config: testAccAWSCognitoIdentityPoolConfig_openidConnectProviderArnsModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "openid_connect_provider_arns.#", "2"), + ), + }, + { + Config: testAccAWSCognitoIdentityPoolConfig_basic(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + ), + }, + }, + }) +} + +func TestAccAWSCognitoIdentityPool_samlProviderArns(t *testing.T) { + name := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoIdentityPoolDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoIdentityPoolConfig_samlProviderArns(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "saml_provider_arns.#", "1"), + ), + }, + { + Config: testAccAWSCognitoIdentityPoolConfig_samlProviderArnsModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "saml_provider_arns.#", "1"), + ), + }, + { + Config: testAccAWSCognitoIdentityPoolConfig_basic(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + resource.TestCheckNoResourceAttr("aws_cognito_identity_pool.main", "saml_provider_arns.#"), + ), + }, + }, + }) +} + +func TestAccAWSCognitoIdentityPool_cognitoIdentityProviders(t *testing.T) { + name := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoIdentityPoolDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoIdentityPoolConfig_cognitoIdentityProviders(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.66456389.client_id", "7lhlkkfbfb4q5kpp90urffao"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.66456389.provider_name", "cognito-idp.us-east-1.amazonaws.com/us-east-1_Zr231apJu"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.66456389.server_side_token_check", "false"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3571192419.client_id", "7lhlkkfbfb4q5kpp90urffao"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3571192419.provider_name", "cognito-idp.us-east-1.amazonaws.com/us-east-1_Ab129faBb"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3571192419.server_side_token_check", "false"), + ), + }, + { + Config: testAccAWSCognitoIdentityPoolConfig_cognitoIdentityProvidersModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3661724441.client_id", "6lhlkkfbfb4q5kpp90urffae"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3661724441.provider_name", "cognito-idp.us-east-1.amazonaws.com/us-east-1_Zr231apJu"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3661724441.server_side_token_check", "false"), + ), + }, + { + Config: testAccAWSCognitoIdentityPoolConfig_basic(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"), + resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)), + ), + }, + }, + }) +} + +func testAccCheckAWSCognitoIdentityPoolExists(n string) 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 errors.New("No Cognito Identity Pool ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).cognitoconn + + _, err := conn.DescribeIdentityPool(&cognitoidentity.DescribeIdentityPoolInput{ + IdentityPoolId: aws.String(rs.Primary.ID), + }) + + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckAWSCognitoIdentityPoolDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).cognitoconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cognito_identity_pool" { + continue + } + + _, err := conn.DescribeIdentityPool(&cognitoidentity.DescribeIdentityPoolInput{ + IdentityPoolId: aws.String(rs.Primary.ID), + }) + + if err != nil { + if wserr, ok := err.(awserr.Error); ok && wserr.Code() == "ResourceNotFoundException" { + return nil + } + return err + } + } + + return nil +} + +func testAccAWSCognitoIdentityPoolConfig_basic(name string) string { + return fmt.Sprintf(` +resource "aws_cognito_identity_pool" "main" { + identity_pool_name = "identity pool %s" + allow_unauthenticated_identities = false + developer_provider_name = "my.developer" +} +`, name) +} + +func testAccAWSCognitoIdentityPoolConfig_supportedLoginProviders(name string) string { + return fmt.Sprintf(` +resource "aws_cognito_identity_pool" "main" { + identity_pool_name = "identity pool %s" + allow_unauthenticated_identities = false + + supported_login_providers { + "graph.facebook.com" = "7346241598935555" + } +} +`, name) +} + +func testAccAWSCognitoIdentityPoolConfig_supportedLoginProvidersModified(name string) string { + return fmt.Sprintf(` +resource "aws_cognito_identity_pool" "main" { + identity_pool_name = "identity pool %s" + allow_unauthenticated_identities = false + + supported_login_providers { + "graph.facebook.com" = "7346241598935552" + "accounts.google.com" = "123456789012.apps.googleusercontent.com" + } +} +`, name) +} + +func testAccAWSCognitoIdentityPoolConfig_openidConnectProviderArns(name string) string { + return fmt.Sprintf(` +resource "aws_cognito_identity_pool" "main" { + identity_pool_name = "identity pool %s" + allow_unauthenticated_identities = false + + openid_connect_provider_arns = ["arn:aws:iam::123456789012:oidc-provider/server.example.com"] +} +`, name) +} + +func testAccAWSCognitoIdentityPoolConfig_openidConnectProviderArnsModified(name string) string { + return fmt.Sprintf(` +resource "aws_cognito_identity_pool" "main" { + identity_pool_name = "identity pool %s" + allow_unauthenticated_identities = false + + openid_connect_provider_arns = ["arn:aws:iam::123456789012:oidc-provider/foo.example.com", "arn:aws:iam::123456789012:oidc-provider/bar.example.com"] +} +`, name) +} + +func testAccAWSCognitoIdentityPoolConfig_samlProviderArns(name string) string { + return fmt.Sprintf(` +resource "aws_iam_saml_provider" "default" { + name = "myprovider-%s" + saml_metadata_document = "${file("./test-fixtures/saml-metadata.xml")}" +} + +resource "aws_cognito_identity_pool" "main" { + identity_pool_name = "identity pool %s" + allow_unauthenticated_identities = false + + saml_provider_arns = ["${aws_iam_saml_provider.default.arn}"] +} +`, name, name) +} + +func testAccAWSCognitoIdentityPoolConfig_samlProviderArnsModified(name string) string { + return fmt.Sprintf(` +resource "aws_iam_saml_provider" "default" { + name = "default-%s" + saml_metadata_document = "${file("./test-fixtures/saml-metadata.xml")}" +} + +resource "aws_iam_saml_provider" "secondary" { + name = "secondary-%s" + saml_metadata_document = "${file("./test-fixtures/saml-metadata.xml")}" +} + +resource "aws_cognito_identity_pool" "main" { + identity_pool_name = "identity pool %s" + allow_unauthenticated_identities = false + + saml_provider_arns = ["${aws_iam_saml_provider.secondary.arn}"] +} +`, name, name, name) +} + +func testAccAWSCognitoIdentityPoolConfig_cognitoIdentityProviders(name string) string { + return fmt.Sprintf(` +resource "aws_cognito_identity_pool" "main" { + identity_pool_name = "identity pool %s" + allow_unauthenticated_identities = false + + cognito_identity_providers { + client_id = "7lhlkkfbfb4q5kpp90urffao" + provider_name = "cognito-idp.us-east-1.amazonaws.com/us-east-1_Ab129faBb" + server_side_token_check = false + } + + cognito_identity_providers { + client_id = "7lhlkkfbfb4q5kpp90urffao" + provider_name = "cognito-idp.us-east-1.amazonaws.com/us-east-1_Zr231apJu" + server_side_token_check = false + } +} +`, name) +} + +func testAccAWSCognitoIdentityPoolConfig_cognitoIdentityProvidersModified(name string) string { + return fmt.Sprintf(` +resource "aws_cognito_identity_pool" "main" { + identity_pool_name = "identity pool %s" + allow_unauthenticated_identities = false + + cognito_identity_providers { + client_id = "6lhlkkfbfb4q5kpp90urffae" + provider_name = "cognito-idp.us-east-1.amazonaws.com/us-east-1_Zr231apJu" + server_side_token_check = false + } +} +`, name) +} diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index dfe053b9a..d6dd6e358 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -14,6 +14,7 @@ import ( "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + "github.com/aws/aws-sdk-go/service/cognitoidentity" "github.com/aws/aws-sdk-go/service/configservice" "github.com/aws/aws-sdk-go/service/directoryservice" "github.com/aws/aws-sdk-go/service/ec2" @@ -1925,3 +1926,74 @@ func flattenApiGatewayUsagePlanQuota(s *apigateway.QuotaSettings) []map[string]i return []map[string]interface{}{settings} } + +func expandCognitoSupportedLoginProviders(config map[string]interface{}) map[string]*string { + m := map[string]*string{} + for k, v := range config { + s := v.(string) + m[k] = &s + } + return m +} + +func flattenCognitoSupportedLoginProviders(config map[string]*string) map[string]string { + m := map[string]string{} + for k, v := range config { + m[k] = *v + } + return m +} + +func expandCognitoIdentityProviders(s *schema.Set) []*cognitoidentity.Provider { + ips := make([]*cognitoidentity.Provider, 0) + + for _, v := range s.List() { + s := v.(map[string]interface{}) + + ip := &cognitoidentity.Provider{} + + if sv, ok := s["client_id"].(string); ok { + ip.ClientId = aws.String(sv) + } + + if sv, ok := s["provider_name"].(string); ok { + ip.ProviderName = aws.String(sv) + } + + if sv, ok := s["server_side_token_check"].(bool); ok { + ip.ServerSideTokenCheck = aws.Bool(sv) + } + + ips = append(ips, ip) + } + + return ips +} + +func flattenCognitoIdentityProviders(ips []*cognitoidentity.Provider) []map[string]interface{} { + values := make([]map[string]interface{}, 0) + + for _, v := range ips { + ip := make(map[string]interface{}) + + if v == nil { + return nil + } + + if v.ClientId != nil { + ip["client_id"] = *v.ClientId + } + + if v.ProviderName != nil { + ip["provider_name"] = *v.ProviderName + } + + if v.ServerSideTokenCheck != nil { + ip["server_side_token_check"] = *v.ServerSideTokenCheck + } + + values = append(values, ip) + } + + return values +} diff --git a/builtin/providers/aws/validators.go b/builtin/providers/aws/validators.go index 9a7bf0e0a..c6a4edb03 100644 --- a/builtin/providers/aws/validators.go +++ b/builtin/providers/aws/validators.go @@ -1218,3 +1218,76 @@ func validateAwsKmsName(v interface{}, k string) (ws []string, es []error) { } return } + +func validateCognitoIdentityPoolName(v interface{}, k string) (ws []string, errors []error) { + val := v.(string) + if !regexp.MustCompile("^[\\w _]+$").MatchString(val) { + errors = append(errors, fmt.Errorf("%q must contain only alphanumeric caracters and spaces", k)) + } + + return +} + +func validateCognitoProviderDeveloperName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if len(value) > 100 { + errors = append(errors, fmt.Errorf("%q cannot be longer than 100 caracters", k)) + } + + if !regexp.MustCompile("^[\\w._-]+$").MatchString(value) { + errors = append(errors, fmt.Errorf("%q must contain only alphanumeric caracters, dots, underscores and hyphens", k)) + } + + return +} + +func validateCognitoSupportedLoginProviders(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if len(value) < 1 { + errors = append(errors, fmt.Errorf("%q cannot be less than 1 character", k)) + } + + if len(value) > 128 { + errors = append(errors, fmt.Errorf("%q cannot be longer than 128 caracters", k)) + } + + if !regexp.MustCompile("^[\\w.;_/-]+$").MatchString(value) { + errors = append(errors, fmt.Errorf("%q must contain only alphanumeric caracters, dots, semicolons, underscores, slashes and hyphens", k)) + } + + return +} + +func validateCognitoIdentityProvidersClientId(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if len(value) < 1 { + errors = append(errors, fmt.Errorf("%q cannot be less than 1 character", k)) + } + + if len(value) > 128 { + errors = append(errors, fmt.Errorf("%q cannot be longer than 128 caracters", k)) + } + + if !regexp.MustCompile("^[\\w_]+$").MatchString(value) { + errors = append(errors, fmt.Errorf("%q must contain only alphanumeric caracters and underscores", k)) + } + + return +} + +func validateCognitoIdentityProvidersProviderName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if len(value) < 1 { + errors = append(errors, fmt.Errorf("%q cannot be less than 1 character", k)) + } + + if len(value) > 128 { + errors = append(errors, fmt.Errorf("%q cannot be longer than 128 caracters", k)) + } + + if !regexp.MustCompile("^[\\w._:/-]+$").MatchString(value) { + errors = append(errors, fmt.Errorf("%q must contain only alphanumeric caracters, dots, underscores, colons, slashes and hyphens", k)) + } + + return +} diff --git a/builtin/providers/aws/validators_test.go b/builtin/providers/aws/validators_test.go index 4638f0ba0..26b14a484 100644 --- a/builtin/providers/aws/validators_test.go +++ b/builtin/providers/aws/validators_test.go @@ -2011,5 +2011,170 @@ func TestValidateAwsKmsName(t *testing.T) { t.Fatalf("AWS KMS Alias Name validation failed: %v", errors) } } - +} + +func TestValidateCognitoIdentityPoolName(t *testing.T) { + validValues := []string{ + "123", + "1 2 3", + "foo", + "foo bar", + "foo_bar", + "1foo 2bar 3", + } + + for _, s := range validValues { + _, errors := validateCognitoIdentityPoolName(s, "identity_pool_name") + if len(errors) > 0 { + t.Fatalf("%q should be a valid Cognito Identity Pool Name: %v", s, errors) + } + } + + invalidValues := []string{ + "1-2-3", + "foo!", + "foo-bar", + "foo-bar", + "foo1-bar2", + } + + for _, s := range invalidValues { + _, errors := validateCognitoIdentityPoolName(s, "identity_pool_name") + if len(errors) == 0 { + t.Fatalf("%q should not be a valid Cognito Identity Pool Name: %v", s, errors) + } + } +} + +func TestValidateCognitoProviderDeveloperName(t *testing.T) { + validValues := []string{ + "1", + "foo", + "1.2", + "foo1-bar2-baz3", + "foo_bar", + } + + for _, s := range validValues { + _, errors := validateCognitoProviderDeveloperName(s, "developer_provider_name") + if len(errors) > 0 { + t.Fatalf("%q should be a valid Cognito Provider Developer Name: %v", s, errors) + } + } + + invalidValues := []string{ + "foo!", + "foo:bar", + "foo/bar", + "foo;bar", + } + + for _, s := range invalidValues { + _, errors := validateCognitoProviderDeveloperName(s, "developer_provider_name") + if len(errors) == 0 { + t.Fatalf("%q should not be a valid Cognito Provider Developer Name: %v", s, errors) + } + } +} + +func TestValidateCognitoSupportedLoginProviders(t *testing.T) { + validValues := []string{ + "foo", + "7346241598935552", + "123456789012.apps.googleusercontent.com", + "foo_bar", + "foo;bar", + "foo/bar", + "foo-bar", + "xvz1evFS4wEEPTGEFPHBog;kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw", + strings.Repeat("W", 128), + } + + for _, s := range validValues { + _, errors := validateCognitoSupportedLoginProviders(s, "supported_login_providers") + if len(errors) > 0 { + t.Fatalf("%q should be a valid Cognito Supported Login Providers: %v", s, errors) + } + } + + invalidValues := []string{ + "", + strings.Repeat("W", 129), // > 128 + "foo:bar_baz", + "foobar,foobaz", + "foobar=foobaz", + } + + for _, s := range invalidValues { + _, errors := validateCognitoSupportedLoginProviders(s, "supported_login_providers") + if len(errors) == 0 { + t.Fatalf("%q should not be a valid Cognito Supported Login Providers: %v", s, errors) + } + } +} + +func TestValidateCognitoIdentityProvidersClientId(t *testing.T) { + validValues := []string{ + "7lhlkkfbfb4q5kpp90urffao", + "12345678", + "foo_123", + strings.Repeat("W", 128), + } + + for _, s := range validValues { + _, errors := validateCognitoIdentityProvidersClientId(s, "client_id") + if len(errors) > 0 { + t.Fatalf("%q should be a valid Cognito Identity Provider Client ID: %v", s, errors) + } + } + + invalidValues := []string{ + "", + strings.Repeat("W", 129), // > 128 + "foo-bar", + "foo:bar", + "foo;bar", + } + + for _, s := range invalidValues { + _, errors := validateCognitoIdentityProvidersClientId(s, "client_id") + if len(errors) == 0 { + t.Fatalf("%q should not be a valid Cognito Identity Provider Client ID: %v", s, errors) + } + } +} + +func TestValidateCognitoIdentityProvidersProviderName(t *testing.T) { + validValues := []string{ + "foo", + "7346241598935552", + "foo_bar", + "foo:bar", + "foo/bar", + "foo-bar", + "cognito-idp.us-east-1.amazonaws.com/us-east-1_Zr231apJu", + strings.Repeat("W", 128), + } + + for _, s := range validValues { + _, errors := validateCognitoIdentityProvidersProviderName(s, "provider_name") + if len(errors) > 0 { + t.Fatalf("%q should be a valid Cognito Identity Provider Name: %v", s, errors) + } + } + + invalidValues := []string{ + "", + strings.Repeat("W", 129), // > 128 + "foo;bar_baz", + "foobar,foobaz", + "foobar=foobaz", + } + + for _, s := range invalidValues { + _, errors := validateCognitoIdentityProvidersProviderName(s, "provider_name") + if len(errors) == 0 { + t.Fatalf("%q should not be a valid Cognito Identity Provider Name: %v", s, errors) + } + } } diff --git a/website/source/docs/providers/aws/r/cognito_identity_pool.markdown b/website/source/docs/providers/aws/r/cognito_identity_pool.markdown new file mode 100644 index 000000000..5dfe696b6 --- /dev/null +++ b/website/source/docs/providers/aws/r/cognito_identity_pool.markdown @@ -0,0 +1,78 @@ +--- +layout: "aws" +page_title: "AWS: aws_cognito_identity_pool" +sidebar_current: "docs-aws-resource-cognito-identity-pool" +description: |- + Provides an AWS Cognito Identity Pool. +--- + +# aws\_cognito\_identity\_pool + +Provides an AWS Cognito Identity Pool. + +## Example Usage + +``` +resource "aws_iam_saml_provider" "default" { + name = "my-saml-provider" + saml_metadata_document = "${file("saml-metadata.xml")}" +} + +resource "aws_cognito_identity_pool" "main" { + identity_pool_name = "identity pool" + allow_unauthenticated_identities = false + + cognito_identity_providers { + client_id = "6lhlkkfbfb4q5kpp90urffae" + provider_name = "cognito-idp.us-east-1.amazonaws.com/us-east-1_Tv0493apJ" + server_side_token_check = false + } + + cognito_identity_providers { + client_id = "7kodkvfqfb4qfkp39eurffae" + provider_name = "cognito-idp.us-east-1.amazonaws.com/eu-west-1_Zr231apJu" + server_side_token_check = false + } + + supported_login_providers { + "graph.facebook.com" = "7346241598935552" + "accounts.google.com" = "123456789012.apps.googleusercontent.com" + } + + saml_provider_arns = ["${aws_iam_saml_provider.default.arn}"] + openid_connect_provider_arns = ["arn:aws:iam::123456789012:oidc-provider/foo.example.com"] +} +``` + +## Argument Reference + +The Cognito Identity Pool argument layout is a structure composed of several sub-resources - these resources are laid out below. + +* `identity_pool_name` (Required) - The Cognito Identity Pool name. +* `allow_unauthenticated_identities` (Required) - Whether the identity pool supports unauthenticated logins or not. +* `developer_provider_name` (Optional) - The "domain" by which Cognito will refer to your users. This name acts as a placeholder that allows your +backend and the Cognito service to communicate about the developer provider. +* `cognito_identity_providers` (Optional) - An array of [Amazon Cognito Identity user pools](#cognito-identity-providers) and their client IDs. +* `openid_connect_provider_arns` (Optional) - A list of OpendID Connect provider ARNs. +* `saml_provider_arns` (Optional) - An array of Amazon Resource Names (ARNs) of the SAML provider for your identity. +* `supported_login_providers` (Optional) - Key-Value pairs mapping provider names to provider app IDs. + +#### Cognito Identity Providers + + * `client_id` (Optional) - The client ID for the Amazon Cognito Identity User Pool. + * `provider_name` (Optional) - The provider name for an Amazon Cognito Identity User Pool. + * `server_side_token_check` (Optional) - Whether server-side token validation is enabled for the identity provider’s token or not. + +## Attributes Reference + +In addition to the arguments, which are exported, the following attributes are exported: + +* `id` - An identity pool ID in the format REGION:GUID. + +## Import + +Cognito Identity Pool can be imported using the name, e.g. + +``` +$ terraform import aws_cognito_identity_pool.mypool +``` diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 28ff4dfc1..6cbb731b8 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -203,19 +203,18 @@ - > - App Autoscaling Resources - + > CloudFormation Resources @@ -346,6 +345,15 @@ + > + Cognito Resources + + + > Config Resources