diff --git a/README.md b/README.md index 414af63ea8..3979c05b66 100644 --- a/README.md +++ b/README.md @@ -205,3 +205,12 @@ $ make testacc Thanks --------------------------- We'd like to thank [Akshay Karle](https://github.com/akshaykarle) for writing the first version of a Terraform Provider for MongoDB Atlas and paving the way for the creation of this one. + +# Running the integration tests + +The integration tests helps the validation for resources interacting with third party providers (aws, azure or gcp) using terratest [environment setup details](integration-testing/README.md) + +``` + cd integration-testing + go test -tags=integration +``` diff --git a/examples/atlas-cloud-provider-access/aws/aws-roles.tf b/examples/atlas-cloud-provider-access/aws/aws-roles.tf new file mode 100644 index 0000000000..6ed5b48b23 --- /dev/null +++ b/examples/atlas-cloud-provider-access/aws/aws-roles.tf @@ -0,0 +1,41 @@ +resource "aws_iam_role_policy" "test_policy" { + name = "mongo_setup_policy" + role = aws_iam_role.test_role.id + + policy = <<-EOF + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } + ] + } + EOF +} + +resource "aws_iam_role" "test_role" { + name = "mongo_setup_test_role" + + assume_role_policy = <= roleID }) + + if index < len(roles.AWSIAMRoles) && roles.AWSIAMRoles[index].RoleID == roleID { + targetRole = &(roles.AWSIAMRoles[index]) + } + + return +} diff --git a/mongodbatlas/resource_mongodbatlas_cloud_provider_access_setup.go b/mongodbatlas/resource_mongodbatlas_cloud_provider_access_setup.go new file mode 100644 index 0000000000..8cebb967cf --- /dev/null +++ b/mongodbatlas/resource_mongodbatlas_cloud_provider_access_setup.go @@ -0,0 +1,213 @@ +package mongodbatlas + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + matlas "go.mongodb.org/atlas/mongodbatlas" +) + +/* + mongodb_atlas_cloud_provider_access_setup + -> Creates the the information from the mongodbatlas side + -> The delete deletes and deauthorize the role +*/ + +func resourceMongoDBAtlasCloudProviderAccessSetup() *schema.Resource { + return &schema.Resource{ + Read: resourceMongoDBAtlasCloudProviderAccessSetupRead, + Create: resourceMongoDBAtlasCloudProviderAccessSetupCreate, + Update: resourceMongoDBAtlasCloudProviderAccessAuthorizationPlaceHolder, + Delete: resourceMongoDBAtlasCloudProviderAccessSetupDelete, + Importer: &schema.ResourceImporter{ + State: resourceMongoDBAtlasCloudProviderAccessSetupImportState, + }, + + Schema: map[string]*schema.Schema{ + "project_id": { + Type: schema.TypeString, + Required: true, + }, + // Note: when new providers will be added, this will trigger a recreate + "provider_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"AWS"}, false), + }, + "aws": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "atlas_aws_account_arn": { + Type: schema.TypeString, + Computed: true, + }, + "atlas_assumed_role_external_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "created_date": { + Type: schema.TypeString, + Computed: true, + }, + "role_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceMongoDBAtlasCloudProviderAccessSetupRead(d *schema.ResourceData, meta interface{}) error { + // sadly there is no just get API + conn := meta.(*matlas.Client) + ids := decodeStateID(d.Id()) + projectID := ids["project_id"] + providerName := ids["provider_name"] + + roles, _, err := conn.CloudProviderAccess.ListRoles(context.Background(), projectID) + + if err != nil { + return fmt.Errorf(errorGetRead, err) + } + + // aws specific + if providerName == "AWS" { + var targetRole matlas.AWSIAMRole + // searching in roles + for i := range roles.AWSIAMRoles { + role := &(roles.AWSIAMRoles[i]) + if role.RoleID == ids["id"] && role.ProviderName == ids["provider_name"] { + targetRole = *role + } + } + // Not Found + if targetRole.RoleID == "" && !d.IsNewResource() { + d.SetId("") + return nil + } + + roleSchema := roleToSchemaSetup(&targetRole) + + for key, val := range roleSchema { + if err := d.Set(key, val); err != nil { + return fmt.Errorf(errorGetRead, err) + } + } + } else { + // planning for the future multiple providers + return fmt.Errorf(errorGetRead, + fmt.Sprintf("unsopported provider type %s", providerName)) + } + + return nil +} + +func resourceMongoDBAtlasCloudProviderAccessSetupCreate(d *schema.ResourceData, meta interface{}) error { + projectID := d.Get("project_id").(string) + + conn := meta.(*matlas.Client) + + requestParameters := &matlas.CloudProviderAccessRoleRequest{ + ProviderName: d.Get("provider_name").(string), + } + + role, _, err := conn.CloudProviderAccess.CreateRole(context.Background(), projectID, requestParameters) + + if err != nil { + return fmt.Errorf(errorCloudProviderAccessCreate, err) + } + + // once multiple providers enable here do a switch, select for provider type + roleSchema := roleToSchemaSetup(role) + + d.SetId(encodeStateID(map[string]string{ + "id": role.RoleID, + "project_id": projectID, + "provider_name": role.ProviderName, + })) + + for key, val := range roleSchema { + if err := d.Set(key, val); err != nil { + return fmt.Errorf(errorCloudProviderAccessCreate, err) + } + } + + return nil +} + +func resourceMongoDBAtlasCloudProviderAccessSetupDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*matlas.Client) + ids := decodeStateID(d.Id()) + + projectID := ids["project_id"] + roleID := ids["id"] + providerName := ids["provider_name"] + + req := &matlas.CloudProviderDeauthorizationRequest{ + ProviderName: providerName, + RoleID: roleID, + GroupID: projectID, + } + + _, err := conn.CloudProviderAccess.DeauthorizeRole(context.Background(), req) + + if err != nil { + return fmt.Errorf(errorCloudProviderAccessDelete, err) + } + + return nil +} + +func roleToSchemaSetup(role *matlas.AWSIAMRole) map[string]interface{} { + out := map[string]interface{}{ + "provider_name": role.ProviderName, + "aws": map[string]interface{}{ + "atlas_aws_account_arn": role.AtlasAWSAccountARN, + "atlas_assumed_role_external_id": role.AtlasAssumedRoleExternalID, + }, + "created_date": role.CreatedDate, + "role_id": role.RoleID, + } + + return out +} + +func resourceMongoDBAtlasCloudProviderAccessSetupImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + projectID, providerName, roleID, err := splitCloudProviderAccessID(d.Id()) + + if err != nil { + return nil, fmt.Errorf(errorCloudProviderAccessImporter, err) + } + + // searching id in internal format + d.SetId(encodeStateID(map[string]string{ + "id": roleID, + "project_id": projectID, + "provider_name": providerName, + })) + + err = resourceMongoDBAtlasCloudProviderAccessSetupRead(d, meta) + + if err != nil { + return nil, fmt.Errorf(errorCloudProviderAccessImporter, err) + } + + // case of not found + if d.Id() == "" { + return nil, fmt.Errorf(errorCloudProviderAccessImporter, " Resource not found at the cloud please check your id") + } + + // params syncing + if err = d.Set("project_id", projectID); err != nil { + return nil, fmt.Errorf(errorCloudProviderAccessImporter, err) + } + + return []*schema.ResourceData{d}, nil +} diff --git a/mongodbatlas/resource_mongodbatlas_cloud_provider_access_setup_test.go b/mongodbatlas/resource_mongodbatlas_cloud_provider_access_setup_test.go new file mode 100644 index 0000000000..161859349b --- /dev/null +++ b/mongodbatlas/resource_mongodbatlas_cloud_provider_access_setup_test.go @@ -0,0 +1,83 @@ +package mongodbatlas + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + matlas "go.mongodb.org/atlas/mongodbatlas" +) + +const ( + createProviderAccessSetupRole = ` + resource "mongodbatlas_cloud_provider_access_setup" "%[1]s" { + project_id = "%[2]s" + provider_name = "%[3]s" + } + + ` +) + +func TestAccResourceMongoDBAtlasCloudProviderAccessSetup_basic(t *testing.T) { + var ( + name = "test_basic" + acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + resourceName = "mongodbatlas_cloud_provider_access_setup." + name + projectID = os.Getenv("MONGODB_ATLAS_PROJECT_ID") + targetRole = matlas.AWSIAMRole{} + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + // same as regular cloud provider access resource + CheckDestroy: testAccCheckMongoDBAtlasProviderAccessDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(createProviderAccessSetupRole, name, projectID, "AWS"), + Check: resource.ComposeTestCheckFunc( + // same as regular cloud resource + testAccCheckMongoDBAtlasProviderAccessExists(resourceName, &targetRole), + resource.TestCheckResourceAttrSet(resourceName, "aws.atlas_assumed_role_external_id"), + resource.TestCheckResourceAttrSet(resourceName, "aws.atlas_aws_account_arn"), + ), + }, + }, + }, + ) +} + +func TestAccResourceMongoDBAtlasCloudProviderAccessSetup_importBasic(t *testing.T) { + var ( + name = "test_basic" + acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + resourceName = "mongodbatlas_cloud_provider_access_setup." + name + projectID = os.Getenv("MONGODB_ATLAS_PROJECT_ID") + targetRole = matlas.AWSIAMRole{} + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMongoDBAtlasProviderAccessDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(createProviderAccessSetupRole, name, projectID, "AWS"), + Check: resource.ComposeTestCheckFunc( + // same as regular cloud provider because we are just checking in the api + testAccCheckMongoDBAtlasProviderAccessExists(resourceName, &targetRole), + resource.TestCheckResourceAttrSet(resourceName, "aws.atlas_assumed_role_external_id"), + resource.TestCheckResourceAttrSet(resourceName, "aws.atlas_aws_account_arn"), + ), + }, + { + ResourceName: resourceName, + // ID remains the same project-id, provider-name and id for consistency + ImportStateIdFunc: testAccCheckMongoDBAtlasCloudProviderAccessImportStateIDFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }, + ) +} diff --git a/website/docs/d/cloud_provider_access.markdown b/website/docs/d/cloud_provider_access.markdown index 6625691dba..61c5fd1206 100644 --- a/website/docs/d/cloud_provider_access.markdown +++ b/website/docs/d/cloud_provider_access.markdown @@ -13,7 +13,7 @@ description: |- -> **NOTE:** Groups and projects are synonymous terms. You may find `groupId` in the official documentation. ## Example Usage -``` +```hcl resource "mongodbatlas_cloud_provider_access" "test_role" { project_id = "" provider_name = "AWS" diff --git a/website/docs/d/cloud_provider_access_setup.markdown b/website/docs/d/cloud_provider_access_setup.markdown new file mode 100644 index 0000000000..5b96e868b9 --- /dev/null +++ b/website/docs/d/cloud_provider_access_setup.markdown @@ -0,0 +1,45 @@ +--- +layout: "mongodbatlas" +page_title: "MongoDB Atlas: mongodbatlas_cloud_provider_access_setup" +sidebar_current: "docs-mongodbatlas-datasource-cloud-provider-access-setup" +description: |- + Allows you to get the a single role for cloud provider access setup +--- + +# mongodbatlas_cloud_provider_access + +`mongodbatlas_cloud_provider_access` allows you to get a single role for a provider access role setup, currently only AWS is supported. + +-> **NOTE:** Groups and projects are synonymous terms. You may find `groupId` in the official documentation. + +## Example Usage +```hcl +resource "mongodbatlas_cloud_provider_access_setup" "test_role" { + project_id = "" + provider_name = "AWS" +} + +data "mongodbatlas_cloud_provider_access_setup" "single_setup" { + project_id = mongodbatlas_cloud_provider_access_setup.test_role.project_id + provider_name = mongodbatlas_cloud_provider_access_setup.test_role.provider_name + role_id = mongodbatlas_cloud_provider_access_setup.test_role.role_id +} +``` + +## Argument Reference + +* `project_id` - (Required) The unique ID for the project to get all Cloud Provider Access +* `provider_name` - (Required) cloud provider name, currently only AWS is supported +* `role_id` - (Required) unique role id among all the aws roles provided by mongodb atlas + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - Autogenerated Unique ID for this data source. +* `aws` - aws related role information + * `atlas_assumed_role_external_id` - Unique external ID Atlas uses when assuming the IAM role in your AWS account. + * `atlas_aws_account_arn` - ARN associated with the Atlas AWS account used to assume IAM roles in your AWS account. +* `created_date` - Date on which this role was created. + +See [MongoDB Atlas API](https://docs.atlas.mongodb.com/reference/api/cloud-provider-access-get-roles/) Documentation for more information. \ No newline at end of file diff --git a/website/docs/r/cloud_provider_access.markdown b/website/docs/r/cloud_provider_access.markdown index 89eea9e5e6..083365d301 100644 --- a/website/docs/r/cloud_provider_access.markdown +++ b/website/docs/r/cloud_provider_access.markdown @@ -6,9 +6,18 @@ description: |- Provides a Cloud Provider Access settings resource for registration, authorization, and deauthorization --- -# mongodbatlas_cloud_provider_access +# Cloud Provider Access Configuration Paths -`mongodbatlas_cloud_provider_access` Allows you to register and authorize AWS IAM roles in Atlas. +The MongoDB Atlas provider offers two paths to perform an authorization for a cloud provider role. + +* A single resource path using the `mongodbatlas_cloud_provider_access` that at provision time sets up all the required configuration for a given provider, then with a subsequent update it can perform the authorize of the role. + +* A two resource path, consisting of `mongodbatlas_cloud_provider_access_setup` and `mongodbatlas_cloud_provider_access_authorization`. The first resource, `mongodbatlas_cloud_provider_access_setup`, only generates +the initial configuration (create, delete operations). The second resource, `mongodbatlas_cloud_provider_access_authorization`, helps to perform the authorization using the role_id of the first resource. This path is helpful in a multi-provider Terraform file, and allows a single and decoupled apply. + +## mongodbatlas_cloud_provider_access + +`mongodbatlas_cloud_provider_access` Allows you to register and authorize AWS IAM roles in Atlas. This is the resource to use for the single resource path described above. -> **NOTE:** Groups and projects are synonymous terms. You may find `groupId` in the official documentation. @@ -27,7 +36,7 @@ resource "mongodbatlas_cloud_provider_access" "test_role" { ## Argument Reference -* `project_id` - (Required) The unique ID for the project to get all Cloud Provider Access +* `project_id` - (Required) The unique ID for the project * `provider_name` - (Required) The cloud provider for which to create a new role. Currently only AWS is supported. * `iam_assumed_role_arn` - (Optional) - ARN of the IAM Role that Atlas assumes when accessing resources in your AWS account. This value is required after the creation (register of the role) as part of [Set Up Unified AWS Access](https://docs.atlas.mongodb.com/security/set-up-unified-aws-access/#set-up-unified-aws-access). @@ -52,7 +61,7 @@ Once the resource is created add the field `iam_assumed_role_arn` see [Set Up Un resource "mongodbatlas_cloud_provider_access" "test_role" { project_id = "" provider_name = "AWS" - iam_assumed_role_arn = "arn:aws:iam::520983883852:role/mongodb_ec2_s3" + iam_assumed_role_arn = "arn:aws:iam::772401394250:role/test-user-role" } ``` @@ -65,4 +74,89 @@ The Cloud Provider Access resource can be imported using project ID and the prov $ terraform import mongodbatlas_cloud_provider_access.my_role 1112222b3bf99403840e8934-AWS-5fc17d476f7a33224f5b224e ``` -See [MongoDB Atlas API](https://docs.atlas.mongodb.com/reference/api/cloud-provider-access-create-one-role/) Documentation for more information. \ No newline at end of file +## mongodbatlas_cloud_provider_access (optional) + +This is the first resource in the two-resource path as described above. + +`mongodbatlas_cloud_provider_access_setup` Allows you to only register AWS IAM roles in Atlas. + +-> **NOTE:** Groups and projects are synonymous terms. You may find `groupId` in the official documentation. + +## Example Usage + +```hcl + +resource "mongodbatlas_cloud_provider_access_setup" "test_role" { + project_id = "" + provider_name = "AWS" +} + +``` + +## Argument Reference + +* `project_id` - (Required) The unique ID for the project +* `provider_name` - (Required) The cloud provider for which to create a new role. Currently only AWS is supported. + + +## Attributes Reference + +* `id` - Unique identifier used by terraform for internal management. +* `aws` - aws related arn roles + * `atlas_assumed_role_external_id` - Unique external ID Atlas uses when assuming the IAM role in your AWS account. + * `atlas_aws_account_arn` - ARN associated with the Atlas AWS account used to assume IAM roles in your AWS account. +* `created_date` - Date on which this role was created. +* `role_id` - Unique ID of this role. + +## Import: mongodbatlas_cloud_provider_access_setup +For consistency is has the same format as the regular mongodbatlas_cloud_provider_access resource +can be imported using project ID and the provider name and mongodbatlas role id, in the format +`project_id`-`provider_name`-`role_id`, e.g. + +``` +$ terraform import mongodbatlas_cloud_provider_access_setup.my_role 1112222b3bf99403840e8934-AWS-5fc17d476f7a33224f5b224e +``` + +## mongodbatlas_cloud_provider_authorization (optional) + +This is the second resource in the two-resource path as described above. +`mongodbatlas_cloud_provider_access_authorization` Allows you to authorize an AWS IAM roles in Atlas. + +## Example Usage +```hcl + +resource "mongodbatlas_cloud_provider_access_setup" "setup_only" { + project_id = "" + provider_name = "AWS" +} + + +resource "mongodbatlas_cloud_provider_access_authorization" "auth_role" { + project_id = mongodbatlas_cloud_provider_access_setup.setup_only.project_id + role_id = mongodbatlas_cloud_provider_access_setup.setup_only.role_id + + aws = { + iam_assumed_role_arn = "arn:aws:iam::772401394250:role/test-user-role" + } +} + +``` + +## Argument Reference + +* `project_id` - (Required) The unique ID for the project +* `role_id` - (Required) Unique ID of this role returned by mongodb atlas api + +Conditional +* `aws` + * `iam_assumed_role_arn` - (Required) ARN of the IAM Role that Atlas assumes when accessing resources in your AWS account. This value is required after the creation (register of the role) as part of [Set Up Unified AWS Access](https://docs.atlas.mongodb.com/security/set-up-unified-aws-access/#set-up-unified-aws-access). + + +## Attributes Reference + +* `id` - Unique identifier used by terraform for internal management. +* `authorized_date` - Date on which this role was authorized. +* `feature_usages` - Atlas features this AWS IAM role is linked to. + + +See [MongoDB Atlas API](https://docs.atlas.mongodb.com/reference/api/cloud-provider-access-create-one-role/) Documentation for more information.