diff --git a/.changelog/30121.txt b/.changelog/30121.txt new file mode 100644 index 000000000000..494e9798f56b --- /dev/null +++ b/.changelog/30121.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_emr_cluster: Add `placement_group_config` argument +``` diff --git a/internal/service/emr/cluster.go b/internal/service/emr/cluster.go index 65ec7c34d7b2..56be4ca5c116 100644 --- a/internal/service/emr/cluster.go +++ b/internal/service/emr/cluster.go @@ -460,6 +460,29 @@ func ResourceCluster() *schema.Resource { ForceNew: true, Required: true, }, + "placement_group_config": { + Type: schema.TypeList, + ForceNew: true, + Optional: true, + ConfigMode: schema.SchemaConfigModeAttr, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "instance_role": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringInSlice(emr.InstanceRoleType_Values(), false), + }, + "placement_strategy": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(emr.PlacementGroupStrategy_Values(), false), + }, + }, + }, + }, "release_label": { Type: schema.TypeString, ForceNew: true, @@ -962,6 +985,11 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int params.AutoTerminationPolicy = expandAutoTerminationPolicy(v.([]interface{})) } + if v, ok := d.GetOk("placement_group_config"); ok { + placementGroupConfigs := v.([]interface{}) + params.PlacementGroupConfigs = expandPlacementGroupConfigs(placementGroupConfigs) + } + var resp *emr.RunJobFlowOutput err := retry.RetryContext(ctx, propagationTimeout, func() *retry.RetryError { var err error @@ -1176,6 +1204,10 @@ func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta inter return sdkdiag.AppendErrorf(diags, "setting auto_termination_policy: %s", err) } + if err := d.Set("placement_group_config", flattenPlacementGroupConfigs(cluster.PlacementGroups)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting placement_group_config: %s", err) + } + return diags } @@ -2263,3 +2295,43 @@ func flattenAutoTerminationPolicy(atp *emr.AutoTerminationPolicy) []map[string]i return result } + +func expandPlacementGroupConfigs(placementGroupConfigs []interface{}) []*emr.PlacementGroupConfig { + placementGroupConfigsOut := []*emr.PlacementGroupConfig{} + + for _, raw := range placementGroupConfigs { + placementGroupAttributes := raw.(map[string]interface{}) + instanceRole := placementGroupAttributes["instance_role"].(string) + + placementGroupConfig := &emr.PlacementGroupConfig{ + InstanceRole: aws.String(instanceRole), + } + if v, ok := placementGroupAttributes["placement_strategy"]; ok && v.(string) != "" { + placementGroupConfig.PlacementStrategy = aws.String(v.(string)) + } + placementGroupConfigsOut = append(placementGroupConfigsOut, placementGroupConfig) + } + + return placementGroupConfigsOut +} + +func flattenPlacementGroupConfigs(placementGroupSpecifications []*emr.PlacementGroupConfig) []interface{} { + if placementGroupSpecifications == nil { + return []interface{}{} + } + + placementGroupConfigs := make([]interface{}, 0) + + for _, pgc := range placementGroupSpecifications { + placementGroupConfig := make(map[string]interface{}) + + placementGroupConfig["instance_role"] = aws.StringValue(pgc.InstanceRole) + + if pgc.PlacementStrategy != nil { + placementGroupConfig["placement_strategy"] = aws.StringValue(pgc.PlacementStrategy) + } + placementGroupConfigs = append(placementGroupConfigs, placementGroupConfig) + } + + return placementGroupConfigs +} diff --git a/internal/service/emr/cluster_test.go b/internal/service/emr/cluster_test.go index 7c202f9258bd..312f8d9a1c70 100644 --- a/internal/service/emr/cluster_test.go +++ b/internal/service/emr/cluster_test.go @@ -1114,6 +1114,50 @@ func TestAccEMRCluster_Bootstrap_ordering(t *testing.T) { }) } +func TestAccEMRCluster_PlacementGroupConfigs(t *testing.T) { + ctx := acctest.Context(t) + var cluster emr.Cluster + + resourceName := "aws_emr_cluster.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_PlacementGroup(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(ctx, resourceName, &cluster), + resource.TestCheckResourceAttr(resourceName, "placement_group_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "placement_group_config.0.instance_role", "MASTER"), + resource.TestCheckResourceAttr(resourceName, "placement_group_config.0.placement_strategy", "SPREAD"), + ), + }, + { + Config: testAccClusterConfig_PlacementGroupWithOptionalUnset(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(ctx, resourceName, &cluster), + resource.TestCheckResourceAttr(resourceName, "placement_group_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "placement_group_config.0.instance_role", "MASTER"), + resource.TestCheckResourceAttr(resourceName, "placement_group_config.0.placement_strategy", "SPREAD"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "cluster_state", // Ignore RUNNING versus WAITING changes + "configurations", + "keep_job_flow_alive_when_no_steps", + }, + }, + }, + }) +} + func TestAccEMRCluster_terminationProtected(t *testing.T) { ctx := acctest.Context(t) var cluster emr.Cluster @@ -1795,7 +1839,8 @@ resource "aws_security_group" "test" { } tags = { - Name = %[1]q + Name = %[1]q + for-use-with-amazon-emr-managed-policies = true } # EMR will modify ingress rules @@ -1811,7 +1856,8 @@ resource "aws_subnet" "test" { vpc_id = aws_vpc.test.id tags = { - Name = %[1]q + Name = %[1]q + for-use-with-amazon-emr-managed-policies = true } } @@ -1937,6 +1983,36 @@ resource "aws_iam_role_policy_attachment" "emr_service" { `, rName) } +func testAccClusterConfig_baseIAMServiceRolev2(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "emr_service" { + name = "%[1]s_default_role" + + assume_role_policy = <