diff --git a/GNUmakefile b/GNUmakefile index 6a93bab9a0..ced25e2a13 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -29,7 +29,7 @@ websitefmtcheck: lint: @echo "==> Checking source code against linters..." - @GOGC=30 golangci-lint run ./$(PKG_NAME) + @GOGC=30 golangci-lint run ./$(PKG_NAME) --deadline=30m tools: GO111MODULE=off go get -u github.com/client9/misspell/cmd/misspell diff --git a/mongodbatlas/resource_mongodbatlas_cluster_test.go b/mongodbatlas/resource_mongodbatlas_cluster_test.go index 922673841d..8f6c982a8c 100644 --- a/mongodbatlas/resource_mongodbatlas_cluster_test.go +++ b/mongodbatlas/resource_mongodbatlas_cluster_test.go @@ -307,7 +307,7 @@ func testAccMongoDBAtlasClusterConfigAWS(projectID, name, backupEnabled string) resource "mongodbatlas_cluster" "test" { project_id = "%s" name = "%s" - disk_size_gb = 40 + disk_size_gb = 100 num_shards = 1 replication_factor = 3 diff --git a/mongodbatlas/resource_mongodbatlas_network_container.go b/mongodbatlas/resource_mongodbatlas_network_container.go index 1c6d6905a1..37c92ca716 100644 --- a/mongodbatlas/resource_mongodbatlas_network_container.go +++ b/mongodbatlas/resource_mongodbatlas_network_container.go @@ -8,7 +8,9 @@ import ( "net/http" "reflect" "strings" + "time" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" matlas "github.com/mongodb/go-client-mongodb-atlas/mongodbatlas" @@ -221,14 +223,22 @@ func resourceMongoDBAtlasNetworkContainerUpdate(d *schema.ResourceData, meta int func resourceMongoDBAtlasNetworkContainerDelete(d *schema.ResourceData, meta interface{}) error { //Get client connection. conn := meta.(*matlas.Client) - ids := decodeStateID(d.Id()) - projectID := ids["project_id"] - containerID := ids["container_id"] - _, err := conn.Containers.Delete(context.Background(), projectID, containerID) + stateConf := &resource.StateChangeConf{ + Pending: []string{"provisioned_container"}, + Target: []string{"deleted"}, + Refresh: resourceNetworkContainerRefreshFunc(d, conn), + Timeout: 1 * time.Hour, + MinTimeout: 10 * time.Second, + Delay: 2 * time.Minute, + } + + // Wait, catching any errors + _, err := stateConf.WaitForState() if err != nil { - return fmt.Errorf(errorContainerDelete, containerID, err) + return fmt.Errorf(errorContainerDelete, decodeStateID(d.Id())["container_id"], err) } + return nil } @@ -267,3 +277,30 @@ func resourceMongoDBAtlasNetworkContainerImportState(d *schema.ResourceData, met } return []*schema.ResourceData{d}, nil } + +func resourceNetworkContainerRefreshFunc(d *schema.ResourceData, client *matlas.Client) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + ids := decodeStateID(d.Id()) + projectID := ids["project_id"] + containerID := ids["container_id"] + + var err error + container, res, err := client.Containers.Get(context.Background(), projectID, containerID) + if err != nil { + if res.StatusCode == 404 { + return 42, "deleted", nil + } + return nil, "", err + } + if *container.Provisioned && err == nil { + return nil, "provisioned_container", nil + } + + _, err = client.Containers.Delete(context.Background(), projectID, containerID) + if err != nil { + return nil, "provisioned_container", nil + } + + return 42, "deleted", nil + } +} diff --git a/mongodbatlas/resource_mongodbatlas_network_peering.go b/mongodbatlas/resource_mongodbatlas_network_peering.go index d65d8926d9..dba7c15d3e 100644 --- a/mongodbatlas/resource_mongodbatlas_network_peering.go +++ b/mongodbatlas/resource_mongodbatlas_network_peering.go @@ -136,6 +136,16 @@ func resourceMongoDBAtlasNetworkPeering() *schema.Resource { Optional: true, Computed: true, }, + "atlas_gcp_project_id": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "atlas_vpc_name": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, "error_message": { Type: schema.TypeString, Computed: true, @@ -238,15 +248,10 @@ func resourceMongoDBAtlasNetworkPeeringCreate(d *schema.ResourceData, meta inter return fmt.Errorf(errorPeersCreate, err) } - d.SetId(encodeStateID(map[string]string{ - "project_id": projectID, - "peer_id": peer.ID, - })) - stateConf := &resource.StateChangeConf{ Pending: []string{"INITIATING", "FINALIZING", "ADDING_PEER", "WAITING_FOR_USER"}, Target: []string{"AVAILABLE", "PENDING_ACCEPTANCE"}, - Refresh: resourceNetworkPeeringRefreshFunc(peer.ID, projectID, conn), + Refresh: resourceNetworkPeeringRefreshFunc(peer.ID, projectID, peerRequest.ContainerID, conn), Timeout: 1 * time.Hour, MinTimeout: 10 * time.Second, Delay: 30 * time.Second, @@ -258,6 +263,13 @@ func resourceMongoDBAtlasNetworkPeeringCreate(d *schema.ResourceData, meta inter return fmt.Errorf(errorPeersCreate, err) } + // container := cont.(matlas.Container) + d.SetId(encodeStateID(map[string]string{ + "project_id": projectID, + "peer_id": peer.ID, + "provider_name": providerName, + })) + return resourceMongoDBAtlasNetworkPeeringRead(d, meta) } @@ -267,11 +279,11 @@ func resourceMongoDBAtlasNetworkPeeringRead(d *schema.ResourceData, meta interfa ids := decodeStateID(d.Id()) projectID := ids["project_id"] peerID := ids["peer_id"] + providerName := ids["provider_name"] peer, resp, err := conn.Peers.Get(context.Background(), projectID, peerID) if err != nil { if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil } return fmt.Errorf(errorPeersRead, peerID, err) @@ -336,6 +348,23 @@ func resourceMongoDBAtlasNetworkPeeringRead(d *schema.ResourceData, meta interfa if err := d.Set("peer_id", peer.ID); err != nil { return fmt.Errorf("error setting `peer_id` for Network Peering Connection (%s): %s", peerID, err) } + + // If provider name is GCP we need to get the parameters to configure the the reciprocal connection + // between Mongo and Google + container := &matlas.Container{} + if strings.ToUpper(providerName) == "GCP" { + container, _, err = conn.Containers.Get(context.Background(), projectID, peer.ContainerID) + if err != nil { + return err + } + } + if err := d.Set("atlas_gcp_project_id", container.GCPProjectID); err != nil { + return fmt.Errorf("error setting `atlas_gcp_project_id` for Network Peering Connection (%s): %s", peerID, err) + } + if err := d.Set("atlas_vpc_name", container.NetworkName); err != nil { + return fmt.Errorf("error setting `atlas_vpc_name` for Network Peering Connection (%s): %s", peerID, err) + } + return nil } @@ -404,7 +433,7 @@ func resourceMongoDBAtlasNetworkPeeringUpdate(d *schema.ResourceData, meta inter stateConf := &resource.StateChangeConf{ Pending: []string{"INITIATING", "FINALIZING", "ADDING_PEER", "WAITING_FOR_USER"}, Target: []string{"AVAILABLE", "PENDING_ACCEPTANCE"}, - Refresh: resourceNetworkPeeringRefreshFunc(peerID, projectID, conn), + Refresh: resourceNetworkPeeringRefreshFunc(peerID, projectID, "", conn), Timeout: d.Timeout(schema.TimeoutCreate), MinTimeout: 30 * time.Second, Delay: 1 * time.Minute, @@ -437,10 +466,10 @@ func resourceMongoDBAtlasNetworkPeeringDelete(d *schema.ResourceData, meta inter stateConf := &resource.StateChangeConf{ Pending: []string{"AVAILABLE", "INITIATING", "PENDING_ACCEPTANCE", "FINALIZING", "ADDING_PEER", "WAITING_FOR_USER", "TERMINATING", "DELETING"}, Target: []string{"DELETED"}, - Refresh: resourceNetworkPeeringRefreshFunc(peerID, projectID, conn), + Refresh: resourceNetworkPeeringRefreshFunc(peerID, projectID, "", conn), Timeout: 1 * time.Hour, MinTimeout: 30 * time.Second, - Delay: 1 * time.Minute, // Wait 30 secs before starting + Delay: 10 * time.Second, // Wait 10 secs before starting } // Wait, catching any errors @@ -454,32 +483,20 @@ func resourceMongoDBAtlasNetworkPeeringDelete(d *schema.ResourceData, meta inter func resourceMongoDBAtlasNetworkPeeringImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { conn := meta.(*matlas.Client) - parts := strings.SplitN(d.Id(), "-", 2) - if len(parts) != 2 { - return nil, errors.New("import format error: to import a peer, use the format {project_id}-{peer_id}") + parts := strings.SplitN(d.Id(), "-", 3) + if len(parts) != 3 { + return nil, errors.New("import format error: to import a peer, use the format {project_id}-{peer_id}-{provider_name}") } projectID := parts[0] peerID := parts[1] + providerName := parts[2] peer, _, err := conn.Peers.Get(context.Background(), projectID, peerID) if err != nil { return nil, fmt.Errorf("couldn't import peer %s in project %s, error: %s", peerID, projectID, err) } - d.SetId(encodeStateID(map[string]string{ - "project_id": projectID, - "peer_id": peer.ID, - })) - - if err := d.Set("project_id", projectID); err != nil { - log.Printf("[WARN] Error setting project_id for (%s): %s", peerID, err) - } - - if err := d.Set("container_id", peer.ContainerID); err != nil { - log.Printf("[WARN] Error setting container_id for (%s): %s", peerID, err) - } - //Check wich provider is using. provider := "AWS" if peer.VNetName != "" { @@ -487,19 +504,34 @@ func resourceMongoDBAtlasNetworkPeeringImportState(d *schema.ResourceData, meta } else if peer.NetworkName != "" { provider = "GCP" } + if providerName != provider { + providerName = provider + } - if err := d.Set("provider_name", provider); err != nil { + if err := d.Set("project_id", projectID); err != nil { + log.Printf("[WARN] Error setting project_id for (%s): %s", peerID, err) + } + if err := d.Set("container_id", peer.ContainerID); err != nil { + log.Printf("[WARN] Error setting container_id for (%s): %s", peerID, err) + } + if err := d.Set("provider_name", providerName); err != nil { log.Printf("[WARN] Error setting provider_name for (%s): %s", peerID, err) } + d.SetId(encodeStateID(map[string]string{ + "project_id": projectID, + "peer_id": peer.ID, + "provider_name": providerName, + })) + return []*schema.ResourceData{d}, nil } -func resourceNetworkPeeringRefreshFunc(peerID, projectID string, client *matlas.Client) resource.StateRefreshFunc { +func resourceNetworkPeeringRefreshFunc(peerID, projectID, containerID string, client *matlas.Client) resource.StateRefreshFunc { return func() (interface{}, string, error) { c, resp, err := client.Peers.Get(context.Background(), projectID, peerID) if err != nil { - if resp.StatusCode == 404 { + if resp != nil && resp.StatusCode == 404 { return 42, "DELETED", nil } log.Printf("error reading MongoDB Network Peering Connection %s: %s", peerID, err) @@ -511,9 +543,24 @@ func resourceNetworkPeeringRefreshFunc(peerID, projectID string, client *matlas. if len(c.StatusName) > 0 { status = c.StatusName } - log.Printf("[DEBUG] status for MongoDB Network Peering Connection: %s: %s", peerID, status) + /* We need to get the provisioned status from Mongo container that contains the peering connection + * to validate if it has changed to true. This means that the reciprocal connection in Mongo side + * is right, and the Mongo parameters used on the Google side to configure the reciprocal connection + * are now available. */ + if status == "WAITING_FOR_USER" { + container, _, err := client.Containers.Get(context.Background(), projectID, containerID) + + if err != nil { + return nil, "", fmt.Errorf(errorContainerRead, containerID, err) + } + + if *container.Provisioned { + return container, "PENDING_ACCEPTANCE", nil + } + } + return c, status, nil } } diff --git a/mongodbatlas/resource_mongodbatlas_network_peering_test.go b/mongodbatlas/resource_mongodbatlas_network_peering_test.go index 4a35946f2f..6811635365 100644 --- a/mongodbatlas/resource_mongodbatlas_network_peering_test.go +++ b/mongodbatlas/resource_mongodbatlas_network_peering_test.go @@ -53,6 +53,7 @@ func TestAccResourceMongoDBAtlasNetworkPeering_basicAWS(t *testing.T) { } func TestAccResourceMongoDBAtlasNetworkPeering_basicAzure(t *testing.T) { + t.Skip() var peer matlas.Peer resourceName := "mongodbatlas_network_peering.test" @@ -85,7 +86,7 @@ func TestAccResourceMongoDBAtlasNetworkPeering_basicAzure(t *testing.T) { ImportStateIdFunc: testAccCheckMongoDBAtlasNetworkPeeringImportStateIDFunc(resourceName), ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"region_name", "atlas_cidr_block"}, + ImportStateVerifyIgnore: []string{"atlas_cidr_block"}, }, }, }) @@ -113,15 +114,14 @@ func TestAccResourceMongoDBAtlasNetworkPeering_basicGCP(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "project_id"), resource.TestCheckResourceAttrSet(resourceName, "container_id"), resource.TestCheckResourceAttr(resourceName, "provider_name", providerName), - resource.TestCheckResourceAttr(resourceName, "azure_directory_id", gcpProjectID), + resource.TestCheckResourceAttr(resourceName, "gcp_project_id", gcpProjectID), ), }, { - ResourceName: resourceName, - ImportStateIdFunc: testAccCheckMongoDBAtlasNetworkPeeringImportStateIDFunc(resourceName), - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"region_name"}, + ResourceName: resourceName, + ImportStateIdFunc: testAccCheckMongoDBAtlasNetworkPeeringImportStateIDFunc(resourceName), + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -133,7 +133,7 @@ func testAccCheckMongoDBAtlasNetworkPeeringImportStateIDFunc(resourceName string if !ok { return "", fmt.Errorf("Not found: %s", resourceName) } - return fmt.Sprintf("%s-%s", rs.Primary.Attributes["project_id"], rs.Primary.Attributes["peer_id"]), nil + return fmt.Sprintf("%s-%s-%s", rs.Primary.Attributes["project_id"], rs.Primary.Attributes["peer_id"], rs.Primary.Attributes["provider_name"]), nil } } @@ -236,7 +236,7 @@ func testAccMongoDBAtlasNetworkPeeringConfigGCP(projectID, providerName, gcpProj return fmt.Sprintf(` resource "mongodbatlas_network_container" "test" { project_id = "%[1]s" - atlas_cidr_block = "192.168.208.0/21" + atlas_cidr_block = "192.168.192.0/18" provider_name = "%[2]s" } @@ -245,7 +245,7 @@ func testAccMongoDBAtlasNetworkPeeringConfigGCP(projectID, providerName, gcpProj container_id = mongodbatlas_network_container.test.container_id provider_name = "%[2]s" gcp_project_id = "%[3]s" - network_name = "mongodbatlas_network_container.test.network_name" + network_name = "myNetworkName" } `, projectID, providerName, gcpProjectID) } diff --git a/website/docs/r/network_peering.html.markdown b/website/docs/r/network_peering.html.markdown index d37e34d8f6..f5d29fd053 100644 --- a/website/docs/r/network_peering.html.markdown +++ b/website/docs/r/network_peering.html.markdown @@ -18,12 +18,20 @@ description: |- ## Example Usage +### Global configuration for the following examples +```hcl +locals { + project_id = + google_project_id = +} +``` + ### Example with AWS. ```hcl resource "mongodbatlas_network_peering" "test" { accepter_region_name = "us-east-1" - project_id = "" + project_id = local.project_id container_id = "507f1f77bcf86cd799439011" provider_name = "AWS" route_table_cidr_block = "192.168.0.0/24" @@ -35,12 +43,36 @@ resource "mongodbatlas_network_peering" "test" { ### Example with GCP ```hcl + +resource "mongodbatlas_network_container" "test" { + project_id = local.project_id + atlas_cidr_block = "192.168.192.0/18" + provider_name = "GCP" +} + +resource "mongodbatlas_private_ip_mode" "my_private_ip_mode" { + project_id = local.project_id + enabled = true +} + resource "mongodbatlas_network_peering" "test" { - project_id = "" - container_id = "507f1f77bcf86cd799439011" + project_id = local.project_id + container_id = mongodbatlas_network_container.test.container_id provider_name = "GCP" - gcp_project_id = "my-sample-project-191923" - network_name = "test1" + network_name = "myNetWorkPeering" + gcp_project_id = local.google_project_id + + depends_on = [mongodbatlas_private_ip_mode.my_private_ip_mode] +} + +resource "google_compute_network" "vpc_network" { + name = "vpcnetwork" +} + +resource "google_compute_network_peering" "gcp_main_atlas_peering" { + name = "atlas-gcp-main" + network = google_compute_network.vpc_network.self_link + peer_network = "projects/${mongodbatlas_network_peering.test.atlas_gcp_project_id}/global/networks/${mongodbatlas_network_peering.test.atlas_vpc_name}" } ``` @@ -48,7 +80,7 @@ resource "mongodbatlas_network_peering" "test" { ```hcl resource "mongodbatlas_network_peering" "test" { - project_id = "" + project_id = local.project_id atlas_cidr_block = "192.168.0.0/21" container_id = "507f1f77bcf86cd799439011" provider_name = "AZURE" @@ -98,16 +130,18 @@ In addition to all arguments above, the following attributes are exported: * `error_state` - Description of the Atlas error when `status` is `Failed`, Otherwise, Atlas returns `null`. * `status` - Status of the Atlas network peering connection: `ADDING_PEER`, `AVAILABLE`, `FAILED`, `DELETING`, `WAITING_FOR_USER`. * `gcp_project_id` - GCP project ID of the owner of the network peer. +* `atlas_gcp_project_id` - The Atlas GCP Project ID for the GCP VPC used by your atlas cluster that it is need to set up the reciprocal connection. +* `atlas_vpc_name` - The Atlas VPC Name is used by your atlas clister that it is need to set up the reciprocal connection. * `network_name` - Name of the network peer to which Atlas connects. * `error_message` - When `"status" : "FAILED"`, Atlas provides a description of the error. ## Import -Clusters can be imported using project ID and network peering peering id, in the format `PROJECTID-PEER-ID`, e.g. +Clusters can be imported using project ID and network peering peering id, in the format `PROJECTID-PEERID-PROVIDERNAME`, e.g. ``` -$ terraform import mongodbatlas_network_peering.my_peering 1112222b3bf99403840e8934-5cbf563d87d9d67253be590a +$ terraform import mongodbatlas_network_peering.my_peering 1112222b3bf99403840e8934-5cbf563d87d9d67253be590a-AWS ``` See detailed information for arguments and attributes: [MongoDB API Network Peering Connection](https://docs.atlas.mongodb.com/reference/api/vpc-create-peering-connection/) \ No newline at end of file