From 310518b625cc23ffa85112760a39a4f787f2a7d1 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 4 Jun 2021 19:23:34 +0300 Subject: [PATCH 01/15] add fsx win fs support --- aws/resource_aws_ecs_task_definition.go | 541 +++++++++++++++---- aws/resource_aws_ecs_task_definition_test.go | 75 +++ aws/structure.go | 188 ------- 3 files changed, 508 insertions(+), 296 deletions(-) diff --git a/aws/resource_aws_ecs_task_definition.go b/aws/resource_aws_ecs_task_definition.go index df7e4f04967..ec4d438c90f 100644 --- a/aws/resource_aws_ecs_task_definition.go +++ b/aws/resource_aws_ecs_task_definition.go @@ -61,9 +61,10 @@ func resourceAwsEcsTaskDefinition() *schema.Resource { }, "family": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), }, "revision": { @@ -115,16 +116,11 @@ func resourceAwsEcsTaskDefinition() *schema.Resource { }, "network_mode": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - ecs.NetworkModeBridge, - ecs.NetworkModeHost, - ecs.NetworkModeAwsvpc, - ecs.NetworkModeNone, - }, false), + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ecs.NetworkMode_Values(), false), }, "volume": { @@ -153,14 +149,11 @@ func resourceAwsEcsTaskDefinition() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "scope": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - ecs.ScopeShared, - ecs.ScopeTask, - }, false), + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ecs.Scope_Values(), false), }, "autoprovision": { Type: schema.TypeBool, @@ -207,13 +200,10 @@ func resourceAwsEcsTaskDefinition() *schema.Resource { Default: "/", }, "transit_encryption": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - ecs.EFSTransitEncryptionEnabled, - ecs.EFSTransitEncryptionDisabled, - }, false), + Type: schema.TypeString, + ForceNew: true, + Optional: true, + ValidateFunc: validation.StringInSlice(ecs.EFSTransitEncryption_Values(), false), }, "transit_encryption_port": { Type: schema.TypeInt, @@ -234,13 +224,51 @@ func resourceAwsEcsTaskDefinition() *schema.Resource { Optional: true, }, "iam": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + ValidateFunc: validation.StringInSlice(ecs.EFSAuthorizationConfigIAM_Values(), false), + }, + }, + }, + }, + }, + }, + }, + "fsx_windows_file_server_volume_configuration": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "file_system_id": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + "root_directory": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + "authorization_config": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "credentials_parameter": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validateArn, + }, + "domain": { Type: schema.TypeString, ForceNew: true, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - ecs.EFSAuthorizationConfigIAMEnabled, - ecs.EFSAuthorizationConfigIAMDisabled, - }, false), + Required: true, }, }, }, @@ -261,12 +289,10 @@ func resourceAwsEcsTaskDefinition() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "type": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - ecs.TaskDefinitionPlacementConstraintTypeMemberOf, - }, false), + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringInSlice(ecs.TaskDefinitionPlacementConstraintType_Values(), false), }, "expression": { Type: schema.TypeString, @@ -281,28 +307,28 @@ func resourceAwsEcsTaskDefinition() *schema.Resource { Type: schema.TypeSet, Optional: true, ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "EC2", + "FARGATE", + "EXTERNAL", + }, false), + }, }, "ipc_mode": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - ecs.IpcModeHost, - ecs.IpcModeNone, - ecs.IpcModeTask, - }, false), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ecs.IpcMode_Values(), false), }, "pid_mode": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - ecs.PidModeHost, - ecs.PidModeTask, - }, false), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ecs.PidMode_Values(), false), }, "proxy_configuration": { @@ -324,13 +350,11 @@ func resourceAwsEcsTaskDefinition() *schema.Resource { ForceNew: true, }, "type": { - Type: schema.TypeString, - Default: ecs.ProxyConfigurationTypeAppmesh, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - ecs.ProxyConfigurationTypeAppmesh, - }, false), + Type: schema.TypeString, + Default: ecs.ProxyConfigurationTypeAppmesh, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ecs.ProxyConfigurationType_Values(), false), }, }, }, @@ -430,20 +454,11 @@ func resourceAwsEcsTaskDefinitionCreate(d *schema.ResourceData, meta interface{} constraints := d.Get("placement_constraints").(*schema.Set).List() if len(constraints) > 0 { - var pc []*ecs.TaskDefinitionPlacementConstraint - for _, raw := range constraints { - p := raw.(map[string]interface{}) - t := p["type"].(string) - e := p["expression"].(string) - if err := validateAwsEcsPlacementConstraint(t, e); err != nil { - return err - } - pc = append(pc, &ecs.TaskDefinitionPlacementConstraint{ - Type: aws.String(t), - Expression: aws.String(e), - }) + cons, err := expandEcsTaskDefinitionPlacementConstraints(constraints) + if err != nil { + return err } - input.PlacementConstraints = pc + input.PlacementConstraints = cons } if v, ok := d.GetOk("requires_compatibilities"); ok && v.(*schema.Set).Len() > 0 { @@ -452,30 +467,7 @@ func resourceAwsEcsTaskDefinitionCreate(d *schema.ResourceData, meta interface{} proxyConfigs := d.Get("proxy_configuration").([]interface{}) if len(proxyConfigs) > 0 { - proxyConfig := proxyConfigs[0] - configMap := proxyConfig.(map[string]interface{}) - - containerName := configMap["container_name"].(string) - proxyType := configMap["type"].(string) - - rawProperties := configMap["properties"].(map[string]interface{}) - - properties := make([]*ecs.KeyValuePair, len(rawProperties)) - i := 0 - for name, value := range rawProperties { - properties[i] = &ecs.KeyValuePair{ - Name: aws.String(name), - Value: aws.String(value.(string)), - } - i++ - } - - var ecsProxyConfig ecs.ProxyConfiguration - ecsProxyConfig.ContainerName = aws.String(containerName) - ecsProxyConfig.Type = aws.String(proxyType) - ecsProxyConfig.Properties = properties - - input.ProxyConfiguration = &ecsProxyConfig + input.ProxyConfiguration = expandEcsTaskDefinitionProxyConfiguration(proxyConfigs) } log.Printf("[DEBUG] Registering ECS task definition: %s", input) @@ -558,23 +550,23 @@ func resourceAwsEcsTaskDefinitionRead(d *schema.ResourceData, meta interface{}) } if err := d.Set("volume", flattenEcsVolumes(taskDefinition.Volumes)); err != nil { - return fmt.Errorf("error setting volume: %s", err) + return fmt.Errorf("error setting volume: %w", err) } if err := d.Set("inference_accelerator", flattenEcsInferenceAccelerators(taskDefinition.InferenceAccelerators)); err != nil { - return fmt.Errorf("error setting inference accelerators: %s", err) + return fmt.Errorf("error setting inference accelerators: %w", err) } if err := d.Set("placement_constraints", flattenPlacementConstraints(taskDefinition.PlacementConstraints)); err != nil { - log.Printf("[ERR] Error setting placement_constraints for (%s): %s", d.Id(), err) + return fmt.Errorf("error setting placement_constraints: %w", err) } if err := d.Set("requires_compatibilities", flattenStringList(taskDefinition.RequiresCompatibilities)); err != nil { - return fmt.Errorf("error setting requires_compatibilities: %s", err) + return fmt.Errorf("error setting requires_compatibilities: %w", err) } if err := d.Set("proxy_configuration", flattenProxyConfiguration(taskDefinition.ProxyConfiguration)); err != nil { - return fmt.Errorf("error setting proxy_configuration: %s", err) + return fmt.Errorf("error setting proxy_configuration: %w", err) } return nil @@ -587,8 +579,8 @@ func flattenPlacementConstraints(pcs []*ecs.TaskDefinitionPlacementConstraint) [ results := make([]map[string]interface{}, 0) for _, pc := range pcs { c := make(map[string]interface{}) - c["type"] = *pc.Type - c["expression"] = *pc.Expression + c["type"] = aws.StringValue(pc.Type) + c["expression"] = aws.StringValue(pc.Expression) results = append(results, c) } return results @@ -677,7 +669,28 @@ func resourceAwsEcsTaskDefinitionVolumeHash(v interface{}) int { buf.WriteString(fmt.Sprintf("%s-", v.(string))) } } + } + if v, ok := m["fsx_windows_file_server_volume_configuration"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + m := v.([]interface{})[0].(map[string]interface{}) + + if v, ok := m["file_system_id"]; ok && v.(string) != "" { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + if v, ok := m["root_directory"]; ok && v.(string) != "" { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + if v, ok := m["authorization_config"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + m := v.([]interface{})[0].(map[string]interface{}) + if v, ok := m["credentials_parameter"]; ok && v.(string) != "" { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + if v, ok := m["domain"]; ok && v.(string) != "" { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + } } return hashcode.String(buf.String()) @@ -687,8 +700,8 @@ func flattenEcsInferenceAccelerators(list []*ecs.InferenceAccelerator) []map[str result := make([]map[string]interface{}, 0, len(list)) for _, iAcc := range list { l := map[string]interface{}{ - "device_name": *iAcc.DeviceName, - "device_type": *iAcc.DeviceType, + "device_name": aws.StringValue(iAcc.DeviceName), + "device_type": aws.StringValue(iAcc.DeviceType), } result = append(result, l) @@ -709,3 +722,315 @@ func expandEcsInferenceAccelerators(configured []interface{}) []*ecs.InferenceAc return iAccs } + +func expandEcsTaskDefinitionPlacementConstraints(constraints []interface{}) ([]*ecs.TaskDefinitionPlacementConstraint, error) { + var pc []*ecs.TaskDefinitionPlacementConstraint + for _, raw := range constraints { + p := raw.(map[string]interface{}) + t := p["type"].(string) + e := p["expression"].(string) + if err := validateAwsEcsPlacementConstraint(t, e); err != nil { + return nil, err + } + pc = append(pc, &ecs.TaskDefinitionPlacementConstraint{ + Type: aws.String(t), + Expression: aws.String(e), + }) + } + + return pc, nil +} + +func expandEcsTaskDefinitionProxyConfiguration(proxyConfigs []interface{}) *ecs.ProxyConfiguration { + proxyConfig := proxyConfigs[0] + configMap := proxyConfig.(map[string]interface{}) + + rawProperties := configMap["properties"].(map[string]interface{}) + + properties := make([]*ecs.KeyValuePair, len(rawProperties)) + i := 0 + for name, value := range rawProperties { + properties[i] = &ecs.KeyValuePair{ + Name: aws.String(name), + Value: aws.String(value.(string)), + } + i++ + } + + ecsProxyConfig := &ecs.ProxyConfiguration{ + ContainerName: aws.String(configMap["container_name"].(string)), + Type: aws.String(configMap["type"].(string)), + Properties: properties, + } + + return ecsProxyConfig +} + +func expandEcsVolumes(configured []interface{}) []*ecs.Volume { + volumes := make([]*ecs.Volume, 0, len(configured)) + + // Loop over our configured volumes and create + // an array of aws-sdk-go compatible objects + for _, lRaw := range configured { + data := lRaw.(map[string]interface{}) + + l := &ecs.Volume{ + Name: aws.String(data["name"].(string)), + } + + hostPath := data["host_path"].(string) + if hostPath != "" { + l.Host = &ecs.HostVolumeProperties{ + SourcePath: aws.String(hostPath), + } + } + + if v, ok := data["docker_volume_configuration"].([]interface{}); ok && len(v) > 0 { + l.DockerVolumeConfiguration = expandEcsVolumesDockerVolume(v) + } + + if v, ok := data["efs_volume_configuration"].([]interface{}); ok && len(v) > 0 { + l.EfsVolumeConfiguration = expandEcsVolumesEFSVolume(v) + } + + if v, ok := data["fsx_windows_file_server_volume_configuration"].([]interface{}); ok && len(v) > 0 { + l.FsxWindowsFileServerVolumeConfiguration = expandEcsVolumesFsxWinVolume(v) + } + + volumes = append(volumes, l) + } + + return volumes +} + +func expandEcsVolumesDockerVolume(configList []interface{}) *ecs.DockerVolumeConfiguration { + config := configList[0].(map[string]interface{}) + dockerVol := &ecs.DockerVolumeConfiguration{} + + if v, ok := config["scope"].(string); ok && v != "" { + dockerVol.Scope = aws.String(v) + } + + if v, ok := config["autoprovision"]; ok && v != "" { + scope := dockerVol.Scope + if scope == nil || *scope != ecs.ScopeTask || v.(bool) { + dockerVol.Autoprovision = aws.Bool(v.(bool)) + } + } + + if v, ok := config["driver"].(string); ok && v != "" { + dockerVol.Driver = aws.String(v) + } + + if v, ok := config["driver_opts"].(map[string]interface{}); ok && len(v) > 0 { + dockerVol.DriverOpts = expandStringMap(v) + } + + if v, ok := config["labels"].(map[string]interface{}); ok && len(v) > 0 { + dockerVol.Labels = expandStringMap(v) + } + + return dockerVol +} + +func expandEcsVolumesEFSVolume(efsConfig []interface{}) *ecs.EFSVolumeConfiguration { + config := efsConfig[0].(map[string]interface{}) + efsVol := &ecs.EFSVolumeConfiguration{} + + if v, ok := config["file_system_id"].(string); ok && v != "" { + efsVol.FileSystemId = aws.String(v) + } + + if v, ok := config["root_directory"].(string); ok && v != "" { + efsVol.RootDirectory = aws.String(v) + } + if v, ok := config["transit_encryption"].(string); ok && v != "" { + efsVol.TransitEncryption = aws.String(v) + } + + if v, ok := config["transit_encryption_port"].(int); ok && v > 0 { + efsVol.TransitEncryptionPort = aws.Int64(int64(v)) + } + authConfig, ok := config["authorization_config"].([]interface{}) + if ok && len(authConfig) > 0 { + authconfig := authConfig[0].(map[string]interface{}) + efsVol.RootDirectory = nil + efsVol.AuthorizationConfig = &ecs.EFSAuthorizationConfig{} + + if v, ok := authconfig["access_point_id"].(string); ok && v != "" { + efsVol.AuthorizationConfig.AccessPointId = aws.String(v) + } + + if v, ok := authconfig["iam"].(string); ok && v != "" { + efsVol.AuthorizationConfig.Iam = aws.String(v) + } + } + + return efsVol +} + +func expandEcsVolumesFsxWinVolume(fsxWinConfig []interface{}) *ecs.FSxWindowsFileServerVolumeConfiguration { + config := fsxWinConfig[0].(map[string]interface{}) + fsxVol := &ecs.FSxWindowsFileServerVolumeConfiguration{} + + if v, ok := config["file_system_id"].(string); ok && v != "" { + fsxVol.FileSystemId = aws.String(v) + } + + if v, ok := config["root_directory"].(string); ok && v != "" { + fsxVol.RootDirectory = aws.String(v) + } + + authConfig, ok := config["authorization_config"].([]interface{}) + if ok && len(authConfig) > 0 { + authconfig := authConfig[0].(map[string]interface{}) + fsxVol.AuthorizationConfig = &ecs.FSxWindowsFileServerAuthorizationConfig{} + + if v, ok := authconfig["credentials_parameter"].(string); ok && v != "" { + fsxVol.AuthorizationConfig.CredentialsParameter = aws.String(v) + } + + if v, ok := authconfig["domain"].(string); ok && v != "" { + fsxVol.AuthorizationConfig.Domain = aws.String(v) + } + } + + return fsxVol +} + +func flattenEcsVolumes(list []*ecs.Volume) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + for _, volume := range list { + l := map[string]interface{}{ + "name": aws.StringValue(volume.Name), + } + + if volume.Host != nil && volume.Host.SourcePath != nil { + l["host_path"] = aws.StringValue(volume.Host.SourcePath) + } + + if volume.DockerVolumeConfiguration != nil { + l["docker_volume_configuration"] = flattenDockerVolumeConfiguration(volume.DockerVolumeConfiguration) + } + + if volume.EfsVolumeConfiguration != nil { + l["efs_volume_configuration"] = flattenEFSVolumeConfiguration(volume.EfsVolumeConfiguration) + } + + if volume.FsxWindowsFileServerVolumeConfiguration != nil { + l["fsx_windows_file_server_volume_configuration"] = flattenFsxWinVolumeConfiguration(volume.FsxWindowsFileServerVolumeConfiguration) + } + + result = append(result, l) + } + return result +} + +func flattenDockerVolumeConfiguration(config *ecs.DockerVolumeConfiguration) []interface{} { + var items []interface{} + m := make(map[string]interface{}) + + if v := config.Scope; v != nil { + m["scope"] = aws.StringValue(v) + } + + if v := config.Autoprovision; v != nil { + m["autoprovision"] = aws.BoolValue(v) + } + + if v := config.Driver; v != nil { + m["driver"] = aws.StringValue(v) + } + + if config.DriverOpts != nil { + m["driver_opts"] = pointersMapToStringList(config.DriverOpts) + } + + if v := config.Labels; v != nil { + m["labels"] = pointersMapToStringList(v) + } + + items = append(items, m) + return items +} + +func flattenEFSVolumeConfiguration(config *ecs.EFSVolumeConfiguration) []interface{} { + var items []interface{} + m := make(map[string]interface{}) + if config != nil { + if v := config.FileSystemId; v != nil { + m["file_system_id"] = aws.StringValue(v) + } + + if v := config.RootDirectory; v != nil { + m["root_directory"] = aws.StringValue(v) + } + if v := config.TransitEncryption; v != nil { + m["transit_encryption"] = aws.StringValue(v) + } + + if v := config.TransitEncryptionPort; v != nil { + m["transit_encryption_port"] = int(aws.Int64Value(v)) + } + + if v := config.AuthorizationConfig; v != nil { + m["authorization_config"] = flattenEFSVolumeAuthorizationConfig(v) + } + } + + items = append(items, m) + return items +} + +func flattenEFSVolumeAuthorizationConfig(config *ecs.EFSAuthorizationConfig) []interface{} { + var items []interface{} + m := make(map[string]interface{}) + if config != nil { + if v := config.AccessPointId; v != nil { + m["access_point_id"] = aws.StringValue(v) + } + if v := config.Iam; v != nil { + m["iam"] = aws.StringValue(v) + } + } + + items = append(items, m) + return items +} + +func flattenFsxWinVolumeConfiguration(config *ecs.FSxWindowsFileServerVolumeConfiguration) []interface{} { + var items []interface{} + m := make(map[string]interface{}) + if config != nil { + if v := config.FileSystemId; v != nil { + m["file_system_id"] = aws.StringValue(v) + } + + if v := config.RootDirectory; v != nil { + m["root_directory"] = aws.StringValue(v) + } + + if v := config.AuthorizationConfig; v != nil { + m["authorization_config"] = flattenFsxWinVolumeAuthorizationConfig(v) + } + } + + items = append(items, m) + return items +} + +func flattenFsxWinVolumeAuthorizationConfig(config *ecs.FSxWindowsFileServerAuthorizationConfig) []interface{} { + var items []interface{} + m := make(map[string]interface{}) + if config != nil { + if v := config.CredentialsParameter; v != nil { + m["credentials_parameters"] = aws.StringValue(v) + } + if v := config.Domain; v != nil { + m["domain"] = aws.StringValue(v) + } + } + + items = append(items, m) + return items +} diff --git a/aws/resource_aws_ecs_task_definition_test.go b/aws/resource_aws_ecs_task_definition_test.go index b6206f7d76a..03e5a09bdd8 100644 --- a/aws/resource_aws_ecs_task_definition_test.go +++ b/aws/resource_aws_ecs_task_definition_test.go @@ -303,6 +303,35 @@ func TestAccAWSEcsTaskDefinition_withEFSAccessPoint(t *testing.T) { }) } +func TestAccAWSEcsTaskDefinition_withFsxWinFileSystem(t *testing.T) { + var def ecs.TaskDefinition + + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_ecs_task_definition.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, ecs.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcsTaskDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEcsTaskDefinitionWithFsxVolume(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsTaskDefinitionExists(resourceName, &def), + resource.TestCheckResourceAttr(resourceName, "volume.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSEcsTaskDefinitionImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAWSEcsTaskDefinition_withTaskScopedDockerVolume(t *testing.T) { var def ecs.TaskDefinition @@ -2204,6 +2233,52 @@ TASK_DEFINITION `, tdName) } +func testAccAWSEcsTaskDefinitionWithFsxVolume(tdName string) string { + return testAccAwsFsxWindowsFileSystemConfigSubnetIds1() + fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = %[1]q + recovery_window_in_days = 0 +} + +resource "aws_secretsmanager_secret_version" "test" { + secret_id = aws_secretsmanager_secret.test.id + secret_string = jsonencode({ username : "admin", password : "${aws_directory_service_directory.test.password}" }) +} + +resource "aws_ecs_task_definition" "test" { + family = %[1]q + + container_definitions = < 0 { - config := configList[0].(map[string]interface{}) - l.DockerVolumeConfiguration = &ecs.DockerVolumeConfiguration{} - - if v, ok := config["scope"].(string); ok && v != "" { - l.DockerVolumeConfiguration.Scope = aws.String(v) - } - - if v, ok := config["autoprovision"]; ok && v != "" { - scope := l.DockerVolumeConfiguration.Scope - if scope == nil || *scope != ecs.ScopeTask || v.(bool) { - l.DockerVolumeConfiguration.Autoprovision = aws.Bool(v.(bool)) - } - } - - if v, ok := config["driver"].(string); ok && v != "" { - l.DockerVolumeConfiguration.Driver = aws.String(v) - } - - if v, ok := config["driver_opts"].(map[string]interface{}); ok && len(v) > 0 { - l.DockerVolumeConfiguration.DriverOpts = expandStringMap(v) - } - - if v, ok := config["labels"].(map[string]interface{}); ok && len(v) > 0 { - l.DockerVolumeConfiguration.Labels = expandStringMap(v) - } - } - - efsConfig, ok := data["efs_volume_configuration"].([]interface{}) - if ok && len(efsConfig) > 0 { - config := efsConfig[0].(map[string]interface{}) - l.EfsVolumeConfiguration = &ecs.EFSVolumeConfiguration{} - - if v, ok := config["file_system_id"].(string); ok && v != "" { - l.EfsVolumeConfiguration.FileSystemId = aws.String(v) - } - - if v, ok := config["root_directory"].(string); ok && v != "" { - l.EfsVolumeConfiguration.RootDirectory = aws.String(v) - } - if v, ok := config["transit_encryption"].(string); ok && v != "" { - l.EfsVolumeConfiguration.TransitEncryption = aws.String(v) - } - - if v, ok := config["transit_encryption_port"].(int); ok && v > 0 { - l.EfsVolumeConfiguration.TransitEncryptionPort = aws.Int64(int64(v)) - } - authConfig, ok := config["authorization_config"].([]interface{}) - if ok && len(authConfig) > 0 { - authconfig := authConfig[0].(map[string]interface{}) - l.EfsVolumeConfiguration.RootDirectory = nil - l.EfsVolumeConfiguration.AuthorizationConfig = &ecs.EFSAuthorizationConfig{} - - if v, ok := authconfig["access_point_id"].(string); ok && v != "" { - l.EfsVolumeConfiguration.AuthorizationConfig.AccessPointId = aws.String(v) - } - - if v, ok := authconfig["iam"].(string); ok && v != "" { - l.EfsVolumeConfiguration.AuthorizationConfig.Iam = aws.String(v) - } - } - } - - volumes = append(volumes, l) - } - - return volumes -} - // Takes JSON in a string. Decodes JSON into // an array of ecs.ContainerDefinition compatible objects func expandEcsContainerDefinitions(rawDefinitions string) ([]*ecs.ContainerDefinition, error) { @@ -682,103 +591,6 @@ func flattenListeners(list []*elb.ListenerDescription) []map[string]interface{} return result } -// Flattens an array of Volumes into a []map[string]interface{} -func flattenEcsVolumes(list []*ecs.Volume) []map[string]interface{} { - result := make([]map[string]interface{}, 0, len(list)) - for _, volume := range list { - l := map[string]interface{}{ - "name": *volume.Name, - } - - if volume.Host != nil && volume.Host.SourcePath != nil { - l["host_path"] = *volume.Host.SourcePath - } - - if volume.DockerVolumeConfiguration != nil { - l["docker_volume_configuration"] = flattenDockerVolumeConfiguration(volume.DockerVolumeConfiguration) - } - - if volume.EfsVolumeConfiguration != nil { - l["efs_volume_configuration"] = flattenEFSVolumeConfiguration(volume.EfsVolumeConfiguration) - } - - result = append(result, l) - } - return result -} - -func flattenDockerVolumeConfiguration(config *ecs.DockerVolumeConfiguration) []interface{} { - var items []interface{} - m := make(map[string]interface{}) - - if v := config.Scope; v != nil { - m["scope"] = aws.StringValue(v) - } - - if v := config.Autoprovision; v != nil { - m["autoprovision"] = aws.BoolValue(v) - } - - if v := config.Driver; v != nil { - m["driver"] = aws.StringValue(v) - } - - if config.DriverOpts != nil { - m["driver_opts"] = pointersMapToStringList(config.DriverOpts) - } - - if v := config.Labels; v != nil { - m["labels"] = pointersMapToStringList(v) - } - - items = append(items, m) - return items -} - -func flattenEFSVolumeConfiguration(config *ecs.EFSVolumeConfiguration) []interface{} { - var items []interface{} - m := make(map[string]interface{}) - if config != nil { - if v := config.FileSystemId; v != nil { - m["file_system_id"] = aws.StringValue(v) - } - - if v := config.RootDirectory; v != nil { - m["root_directory"] = aws.StringValue(v) - } - if v := config.TransitEncryption; v != nil { - m["transit_encryption"] = aws.StringValue(v) - } - - if v := config.TransitEncryptionPort; v != nil { - m["transit_encryption_port"] = int(aws.Int64Value(v)) - } - - if v := config.AuthorizationConfig; v != nil { - m["authorization_config"] = flattenEFSVolumeAuthorizationConfig(v) - } - } - - items = append(items, m) - return items -} - -func flattenEFSVolumeAuthorizationConfig(config *ecs.EFSAuthorizationConfig) []interface{} { - var items []interface{} - m := make(map[string]interface{}) - if config != nil { - if v := config.AccessPointId; v != nil { - m["access_point_id"] = aws.StringValue(v) - } - if v := config.Iam; v != nil { - m["iam"] = aws.StringValue(v) - } - } - - items = append(items, m) - return items -} - // Flattens an array of ECS LoadBalancers into a []map[string]interface{} func flattenEcsLoadBalancers(list []*ecs.LoadBalancer) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(list)) From 092ab0584165a2c49d51eba6f4f85d1da786dec0 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 4 Jun 2021 19:27:19 +0300 Subject: [PATCH 02/15] changelog --- .changelog/19670.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/19670.txt diff --git a/.changelog/19670.txt b/.changelog/19670.txt new file mode 100644 index 00000000000..aaa44144676 --- /dev/null +++ b/.changelog/19670.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_ecs_task_definition: Add plan time validation for `family` and `requires_compatibilities`. +``` + +```release-note:enhancement +resource/aws_ecs_task_definition: Add support for `fsx_windows_file_server_volume_configuration`. +``` \ No newline at end of file From d74c7e1e55fc44f9feafea4544478a020c124f59 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 4 Jun 2021 19:28:47 +0300 Subject: [PATCH 03/15] fmt --- aws/resource_aws_ecs_task_definition_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_ecs_task_definition_test.go b/aws/resource_aws_ecs_task_definition_test.go index 03e5a09bdd8..41e9ab4de71 100644 --- a/aws/resource_aws_ecs_task_definition_test.go +++ b/aws/resource_aws_ecs_task_definition_test.go @@ -2267,7 +2267,7 @@ TASK_DEFINITION fsx_windows_file_server_volume_configuration { file_system_id = aws_fsx_windows_file_system.test.id - root_directory = "\\data" + root_directory = "\\data" authorization_config { credentials_parameter = aws_secretsmanager_secret_version.test.arn From e2bb2a5583c462636973a6efc8566e72aefc5680 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 4 Jun 2021 21:32:52 +0300 Subject: [PATCH 04/15] add iam role to test --- aws/resource_aws_ecs_task_definition_test.go | 44 +++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_ecs_task_definition_test.go b/aws/resource_aws_ecs_task_definition_test.go index 41e9ab4de71..f8442e346ff 100644 --- a/aws/resource_aws_ecs_task_definition_test.go +++ b/aws/resource_aws_ecs_task_definition_test.go @@ -2235,6 +2235,8 @@ TASK_DEFINITION func testAccAWSEcsTaskDefinitionWithFsxVolume(tdName string) string { return testAccAwsFsxWindowsFileSystemConfigSubnetIds1() + fmt.Sprintf(` +data "aws_partition" "current" {} + resource "aws_secretsmanager_secret" "test" { name = %[1]q recovery_window_in_days = 0 @@ -2242,11 +2244,45 @@ resource "aws_secretsmanager_secret" "test" { resource "aws_secretsmanager_secret_version" "test" { secret_id = aws_secretsmanager_secret.test.id - secret_string = jsonencode({ username : "admin", password : "${aws_directory_service_directory.test.password}" }) + secret_string = jsonencode({ username : "admin", password : aws_directory_service_directory.test.password }) +} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = jsonencode({ + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Principal": { + "Service": "ecs-tasks.${data.aws_partition.current.dns_suffix}" + }, + "Effect": "Allow", + "Sid": "" + } + ] +}) +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +resource "aws_iam_role_policy_attachment" "test2" { + role = aws_iam_role.test.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/SecretsManagerReadWrite" +} + +resource "aws_iam_role_policy_attachment" "test3" { + role = aws_iam_role.test.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonFSxReadOnlyAccess" } resource "aws_ecs_task_definition" "test" { - family = %[1]q + family = %[1]q + execution_role_arn = aws_iam_role.test.arn container_definitions = < Date: Fri, 4 Jun 2021 21:33:02 +0300 Subject: [PATCH 05/15] fix arg --- aws/resource_aws_ecs_task_definition.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_ecs_task_definition.go b/aws/resource_aws_ecs_task_definition.go index ec4d438c90f..2d2acab4d2d 100644 --- a/aws/resource_aws_ecs_task_definition.go +++ b/aws/resource_aws_ecs_task_definition.go @@ -1024,7 +1024,7 @@ func flattenFsxWinVolumeAuthorizationConfig(config *ecs.FSxWindowsFileServerAuth m := make(map[string]interface{}) if config != nil { if v := config.CredentialsParameter; v != nil { - m["credentials_parameters"] = aws.StringValue(v) + m["credentials_parameter"] = aws.StringValue(v) } if v := config.Domain; v != nil { m["domain"] = aws.StringValue(v) From d938d778176a4ad3552151d2cb296e197f31ea86 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 4 Jun 2021 21:38:04 +0300 Subject: [PATCH 06/15] fmt --- aws/resource_aws_ecs_task_definition_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aws/resource_aws_ecs_task_definition_test.go b/aws/resource_aws_ecs_task_definition_test.go index f8442e346ff..99bac8de877 100644 --- a/aws/resource_aws_ecs_task_definition_test.go +++ b/aws/resource_aws_ecs_task_definition_test.go @@ -2312,9 +2312,11 @@ TASK_DEFINITION } } - depends_on = [aws_iam_role_policy_attachment.test, - aws_iam_role_policy_attachment.test2, - aws_iam_role_policy_attachment.test3] + depends_on = [ + aws_iam_role_policy_attachment.test, + aws_iam_role_policy_attachment.test2, + aws_iam_role_policy_attachment.test3 + ] } `, tdName) } From 1c5d35312c4a67369b688add454da3cc0ed69c49 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 4 Jun 2021 21:43:14 +0300 Subject: [PATCH 07/15] fmt --- aws/resource_aws_ecs_task_definition_test.go | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_ecs_task_definition_test.go b/aws/resource_aws_ecs_task_definition_test.go index 99bac8de877..214d46bd839 100644 --- a/aws/resource_aws_ecs_task_definition_test.go +++ b/aws/resource_aws_ecs_task_definition_test.go @@ -2251,18 +2251,18 @@ resource "aws_iam_role" "test" { name = %[1]q assume_role_policy = jsonencode({ - "Version": "2012-10-17", - "Statement": [ - { - "Action": "sts:AssumeRole", - "Principal": { - "Service": "ecs-tasks.${data.aws_partition.current.dns_suffix}" - }, - "Effect": "Allow", - "Sid": "" - } - ] -}) + "Version" : "2012-10-17", + "Statement" : [ + { + "Action" : "sts:AssumeRole", + "Principal" : { + "Service" : "ecs-tasks.${data.aws_partition.current.dns_suffix}" + }, + "Effect" : "Allow", + "Sid" : "" + } + ] + }) } resource "aws_iam_role_policy_attachment" "test" { From 5fe4b73200e917271c4d828fe0f8889890cbd2eb Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 4 Jun 2021 21:55:37 +0300 Subject: [PATCH 08/15] move to ecs from structure --- aws/resource_aws_ecs_task_definition.go | 22 ++++++++++++++++++++++ aws/structure.go | 24 ------------------------ 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/aws/resource_aws_ecs_task_definition.go b/aws/resource_aws_ecs_task_definition.go index 2d2acab4d2d..124d39e621e 100644 --- a/aws/resource_aws_ecs_task_definition.go +++ b/aws/resource_aws_ecs_task_definition.go @@ -2,12 +2,14 @@ package aws import ( "bytes" + "encoding/json" "fmt" "log" "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/private/protocol/json/jsonutil" "github.com/aws/aws-sdk-go/service/ecs" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" @@ -1034,3 +1036,23 @@ func flattenFsxWinVolumeAuthorizationConfig(config *ecs.FSxWindowsFileServerAuth items = append(items, m) return items } + +func flattenEcsContainerDefinitions(definitions []*ecs.ContainerDefinition) (string, error) { + b, err := jsonutil.BuildJSON(definitions) + if err != nil { + return "", err + } + + return string(b), nil +} + +func expandEcsContainerDefinitions(rawDefinitions string) ([]*ecs.ContainerDefinition, error) { + var definitions []*ecs.ContainerDefinition + + err := json.Unmarshal([]byte(rawDefinitions), &definitions) + if err != nil { + return nil, fmt.Errorf("Error decoding JSON: %s", err) + } + + return definitions, nil +} diff --git a/aws/structure.go b/aws/structure.go index 4887de3a2af..abaaf21ca6f 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -11,7 +11,6 @@ import ( "strings" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/private/protocol/json/jsonutil" "github.com/aws/aws-sdk-go/service/apigateway" "github.com/aws/aws-sdk-go/service/appmesh" "github.com/aws/aws-sdk-go/service/autoscaling" @@ -96,19 +95,6 @@ func expandListeners(configured []interface{}) ([]*elb.Listener, error) { return listeners, nil } -// Takes JSON in a string. Decodes JSON into -// an array of ecs.ContainerDefinition compatible objects -func expandEcsContainerDefinitions(rawDefinitions string) ([]*ecs.ContainerDefinition, error) { - var definitions []*ecs.ContainerDefinition - - err := json.Unmarshal([]byte(rawDefinitions), &definitions) - if err != nil { - return nil, fmt.Errorf("Error decoding JSON: %s", err) - } - - return definitions, nil -} - // Takes the result of flatmap. Expand for an array of load balancers and // returns ecs.LoadBalancer compatible objects func expandEcsLoadBalancers(configured []interface{}) []*ecs.LoadBalancer { @@ -613,16 +599,6 @@ func flattenEcsLoadBalancers(list []*ecs.LoadBalancer) []map[string]interface{} return result } -// Encodes an array of ecs.ContainerDefinitions into a JSON string -func flattenEcsContainerDefinitions(definitions []*ecs.ContainerDefinition) (string, error) { - b, err := jsonutil.BuildJSON(definitions) - if err != nil { - return "", err - } - - return string(b), nil -} - // Flattens an array of Options into a []map[string]interface{} func flattenOptions(apiOptions []*rds.Option, optionConfigurations []*rds.OptionConfiguration) []map[string]interface{} { result := make([]map[string]interface{}, 0) From 275be2859071963c0a7e18b497b0d0984ae5f5c0 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 4 Jun 2021 22:09:46 +0300 Subject: [PATCH 09/15] better break down of structs --- aws/resource_aws_ecs_task_definition.go | 54 +++++++++++++++---------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/aws/resource_aws_ecs_task_definition.go b/aws/resource_aws_ecs_task_definition.go index 124d39e621e..243d5d249e3 100644 --- a/aws/resource_aws_ecs_task_definition.go +++ b/aws/resource_aws_ecs_task_definition.go @@ -853,22 +853,27 @@ func expandEcsVolumesEFSVolume(efsConfig []interface{}) *ecs.EFSVolumeConfigurat if v, ok := config["transit_encryption_port"].(int); ok && v > 0 { efsVol.TransitEncryptionPort = aws.Int64(int64(v)) } - authConfig, ok := config["authorization_config"].([]interface{}) - if ok && len(authConfig) > 0 { - authconfig := authConfig[0].(map[string]interface{}) + if v, ok := config["authorization_config"].([]interface{}); ok && len(v) > 0 { efsVol.RootDirectory = nil - efsVol.AuthorizationConfig = &ecs.EFSAuthorizationConfig{} + efsVol.AuthorizationConfig = expandEcsVolumesEFSVolumeAuthorizationConfig(v) + } - if v, ok := authconfig["access_point_id"].(string); ok && v != "" { - efsVol.AuthorizationConfig.AccessPointId = aws.String(v) - } + return efsVol +} - if v, ok := authconfig["iam"].(string); ok && v != "" { - efsVol.AuthorizationConfig.Iam = aws.String(v) - } +func expandEcsVolumesEFSVolumeAuthorizationConfig(efsConfig []interface{}) *ecs.EFSAuthorizationConfig { + authconfig := efsConfig[0].(map[string]interface{}) + auth := &ecs.EFSAuthorizationConfig{} + + if v, ok := authconfig["access_point_id"].(string); ok && v != "" { + auth.AccessPointId = aws.String(v) } - return efsVol + if v, ok := authconfig["iam"].(string); ok && v != "" { + auth.Iam = aws.String(v) + } + + return auth } func expandEcsVolumesFsxWinVolume(fsxWinConfig []interface{}) *ecs.FSxWindowsFileServerVolumeConfiguration { @@ -883,21 +888,26 @@ func expandEcsVolumesFsxWinVolume(fsxWinConfig []interface{}) *ecs.FSxWindowsFil fsxVol.RootDirectory = aws.String(v) } - authConfig, ok := config["authorization_config"].([]interface{}) - if ok && len(authConfig) > 0 { - authconfig := authConfig[0].(map[string]interface{}) - fsxVol.AuthorizationConfig = &ecs.FSxWindowsFileServerAuthorizationConfig{} + if v, ok := config["authorization_config"].([]interface{}); ok && len(v) > 0 { + fsxVol.AuthorizationConfig = expandEcsVolumesFsxWinVolumeAuthorizationConfig(v) + } - if v, ok := authconfig["credentials_parameter"].(string); ok && v != "" { - fsxVol.AuthorizationConfig.CredentialsParameter = aws.String(v) - } + return fsxVol +} - if v, ok := authconfig["domain"].(string); ok && v != "" { - fsxVol.AuthorizationConfig.Domain = aws.String(v) - } +func expandEcsVolumesFsxWinVolumeAuthorizationConfig(config []interface{}) *ecs.FSxWindowsFileServerAuthorizationConfig { + authconfig := config[0].(map[string]interface{}) + auth := &ecs.FSxWindowsFileServerAuthorizationConfig{} + + if v, ok := authconfig["credentials_parameter"].(string); ok && v != "" { + auth.CredentialsParameter = aws.String(v) } - return fsxVol + if v, ok := authconfig["domain"].(string); ok && v != "" { + auth.Domain = aws.String(v) + } + + return auth } func flattenEcsVolumes(list []*ecs.Volume) []map[string]interface{} { From fdce2015c734dd388729f93badd2fbc82e22ae87 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 4 Jun 2021 23:26:07 +0300 Subject: [PATCH 10/15] docs --- .../docs/r/ecs_task_definition.html.markdown | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/website/docs/r/ecs_task_definition.html.markdown b/website/docs/r/ecs_task_definition.html.markdown index 0aa9f1f2caa..f441a323d05 100644 --- a/website/docs/r/ecs_task_definition.html.markdown +++ b/website/docs/r/ecs_task_definition.html.markdown @@ -128,6 +128,34 @@ resource "aws_ecs_task_definition" "service" { } ``` +### Example Using `fsx_windows_file_server_volume_configuration` + +```terraform +resource "aws_ecs_task_definition" "service" { + family = "service" + container_definitions = file("task-definitions/service.json") + + volume { + name = "service-storage" + + fsx_windows_file_server_volume_configuration { + file_system_id = aws_fsx_windows_file_system.test.id + root_directory = "\\data" + + authorization_config { + credentials_parameter = aws_secretsmanager_secret_version.test.arn + domain = aws_directory_service_directory.test.name + } + } + } +} + +resource "aws_secretsmanager_secret_version" "test" { + secret_id = aws_secretsmanager_secret.test.id + secret_string = jsonencode({ username : "admin", password : aws_directory_service_directory.test.password }) +} +``` + ### Example Using `container_definitions` and `inference_accelerator` ```terraform @@ -198,6 +226,7 @@ The following arguments are optional: * `docker_volume_configuration` - (Optional) Configuration block to configure a [docker volume](#docker_volume_configuration). Detailed below. * `efs_volume_configuration` - (Optional) Configuration block for an [EFS volume](#efs_volume_configuration). Detailed below. +* `fsx_windows_file_server_volume_configuration` - (Optional) Configuration block for an [FSX Windows File Server volume](#fsx_windows_file_server_volume_configuration). Detailed below. * `host_path` - (Optional) Path on the host container instance that is presented to the container. If not set, ECS will create a nonpersistent data volume that starts empty and is deleted after the task has finished. * `name` - (Required) Name of the volume. This name is referenced in the `sourceVolume` parameter of container definition in the `mountPoints` section. @@ -222,11 +251,24 @@ For more information, see [Specifying an EFS volume in your Task Definition Deve * `transit_encryption_port` - (Optional) Port to use for transit encryption. If you do not specify a transit encryption port, it will use the port selection strategy that the Amazon EFS mount helper uses. * `authorization_config` - (Optional) Configuration block for [authorization](#authorization_config) for the Amazon EFS file system. Detailed below. -### authorization_config +#### authorization_config * `access_point_id` - (Optional) Access point ID to use. If an access point is specified, the root directory value will be relative to the directory set for the access point. If specified, transit encryption must be enabled in the EFSVolumeConfiguration. * `iam` - (Optional) Whether or not to use the Amazon ECS task IAM role defined in a task definition when mounting the Amazon EFS file system. If enabled, transit encryption must be enabled in the EFSVolumeConfiguration. Valid values: `ENABLED`, `DISABLED`. If this parameter is omitted, the default value of `DISABLED` is used. +### fsx_windows_file_server_volume_configuration + +For more information, see [Specifying an FSX Windows File Server volume in your Task Definition Developer Guide](https://docs.amazonaws.cn/en_us/AmazonECS/latest/developerguide/tutorial-wfsx-volumes.html) + +* `file_system_id` - (Required) The Amazon FSx for Windows File Server file system ID to use. +* `root_directory` - (Required) The directory within the Amazon FSx for Windows File Server file system to mount as the root directory inside the host. +* `authorization_config` - (OptiRequiredonal) Configuration block for [authorization](#authorization_config) for the Amazon FSx for Windows File Server file system detailed below. + +#### authorization_config + +* `credentials_parameter` - (Required) The authorization credential option to use. The authorization credential options can be provided using either the Amazon Resource Name (ARN) of an AWS Secrets Manager secret or AWS Systems Manager Parameter Store parameter. The ARNs refer to the stored credentials. +* `domain` - (Required) A fully qualified domain name hosted by an AWS Directory Service Managed Microsoft AD (Active Directory) or self-hosted AD on Amazon EC2. + ### placement_constraints * `expression` - (Optional) Cluster Query Language expression to apply to the constraint. For more information, see [Cluster Query Language in the Amazon EC2 Container Service Developer Guide](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-query-language.html). From 79b9c0a573548f3f6cb2cc09552f6f1834423e35 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 5 Jun 2021 00:22:52 +0300 Subject: [PATCH 11/15] tests --- aws/resource_aws_ecs_task_definition_test.go | 57 ++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/aws/resource_aws_ecs_task_definition_test.go b/aws/resource_aws_ecs_task_definition_test.go index 214d46bd839..3f441f8815c 100644 --- a/aws/resource_aws_ecs_task_definition_test.go +++ b/aws/resource_aws_ecs_task_definition_test.go @@ -147,6 +147,19 @@ func TestAccAWSEcsTaskDefinition_withDockerVolume(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsTaskDefinitionExists(resourceName, &def), resource.TestCheckResourceAttr(resourceName, "volume.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "volume.*", map[string]string{ + "name": tdName, + "docker_volume_configuration.#": "1", + "docker_volume_configuration.0.driver": "local", + "docker_volume_configuration.0.scope": "shared", + "docker_volume_configuration.0.autoprovision": "true", + "docker_volume_configuration.0.driver_opts.%": "2", + "docker_volume_configuration.0.driver_opts.device": "tmpfs", + "docker_volume_configuration.0.driver_opts.uid": "1000", + "docker_volume_configuration.0.labels.%": "2", + "docker_volume_configuration.0.labels.environment": "test", + "docker_volume_configuration.0.labels.stack": "april", + }), ), }, { @@ -176,6 +189,11 @@ func TestAccAWSEcsTaskDefinition_withDockerVolumeMinimalConfig(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsTaskDefinitionExists(resourceName, &def), resource.TestCheckResourceAttr(resourceName, "volume.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "volume.*", map[string]string{ + "name": tdName, + "docker_volume_configuration.#": "1", + "docker_volume_configuration.0.autoprovision": "true", + }), ), }, { @@ -205,6 +223,11 @@ func TestAccAWSEcsTaskDefinition_withEFSVolumeMinimal(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsTaskDefinitionExists(resourceName, &def), resource.TestCheckResourceAttr(resourceName, "volume.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "volume.*", map[string]string{ + "name": tdName, + "efs_volume_configuration.#": "1", + }), + resource.TestCheckTypeSetElemAttrPair(resourceName, "volume.*.efs_volume_configuration.0.file_system_id", "aws_efs_file_system.test", "id"), ), }, { @@ -234,6 +257,12 @@ func TestAccAWSEcsTaskDefinition_withEFSVolume(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsTaskDefinitionExists(resourceName, &def), resource.TestCheckResourceAttr(resourceName, "volume.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "volume.*", map[string]string{ + "name": tdName, + "efs_volume_configuration.#": "1", + "efs_volume_configuration.0.root_directory": "/home/test", + }), + resource.TestCheckTypeSetElemAttrPair(resourceName, "volume.*.efs_volume_configuration.0.file_system_id", "aws_efs_file_system.test", "id"), ), }, { @@ -262,6 +291,14 @@ func TestAccAWSEcsTaskDefinition_withTransitEncryptionEFSVolume(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsTaskDefinitionExists(resourceName, &def), resource.TestCheckResourceAttr(resourceName, "volume.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "volume.*", map[string]string{ + "name": tdName, + "efs_volume_configuration.#": "1", + "efs_volume_configuration.0.root_directory": "/home/test", + "efs_volume_configuration.0.transit_encryption": "ENABLED", + "efs_volume_configuration.0.transit_encryption_port": "2999", + }), + resource.TestCheckTypeSetElemAttrPair(resourceName, "volume.*.efs_volume_configuration.0.file_system_id", "aws_efs_file_system.test", "id"), ), }, { @@ -291,6 +328,17 @@ func TestAccAWSEcsTaskDefinition_withEFSAccessPoint(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsTaskDefinitionExists(resourceName, &def), resource.TestCheckResourceAttr(resourceName, "volume.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "volume.*", map[string]string{ + "name": tdName, + "efs_volume_configuration.#": "1", + "efs_volume_configuration.0.root_directory": "/", + "efs_volume_configuration.0.transit_encryption": "ENABLED", + "efs_volume_configuration.0.transit_encryption_port": "2999", + "efs_volume_configuration.0.authorization_config.#": "1", + "efs_volume_configuration.0.authorization_config.0.iam": "DISABLED", + }), + resource.TestCheckTypeSetElemAttrPair(resourceName, "volume.*.efs_volume_configuration.0.file_system_id", "aws_efs_file_system.test", "id"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "volume.*.efs_volume_configuration.0.authorization_config.0.access_point_id", "aws_efs_access_point.test", "id"), ), }, { @@ -320,6 +368,15 @@ func TestAccAWSEcsTaskDefinition_withFsxWinFileSystem(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsTaskDefinitionExists(resourceName, &def), resource.TestCheckResourceAttr(resourceName, "volume.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "volume.*", map[string]string{ + "name": rName, + "fsx_windows_file_server_volume_configuration.#": "1", + "fsx_windows_file_server_volume_configuration.0.root_directory": "\\data", + "fsx_windows_file_server_volume_configuration.0.authorization_config.#": "1", + }), + resource.TestCheckTypeSetElemAttrPair(resourceName, "volume.*.fsx_windows_file_server_volume_configuration.0.file_system_id", "aws_fsx_windows_file_system.test", "id"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "volume.*.fsx_windows_file_server_volume_configuration.0.authorization_config.0.credentials_parameter", "aws_secretsmanager_secret_version.test", "arn"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "volume.*.fsx_windows_file_server_volume_configuration.0.authorization_config.0.domain", "aws_directory_service_directory.test", "name"), ), }, { From 056477369e1ceea9348243346e39869cd8d062a1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Jun 2021 06:22:55 -0400 Subject: [PATCH 12/15] Suppress GovCloud acceptance testing errors. --- aws/resource_aws_ecs_task_definition_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/aws/resource_aws_ecs_task_definition_test.go b/aws/resource_aws_ecs_task_definition_test.go index 3f441f8815c..bf015941135 100644 --- a/aws/resource_aws_ecs_task_definition_test.go +++ b/aws/resource_aws_ecs_task_definition_test.go @@ -15,6 +15,8 @@ import ( ) func init() { + RegisterServiceErrorCheckFunc(ecs.EndpointsID, testAccErrorCheckSkipECS) + resource.AddTestSweepers("aws_ecs_task_definition", &resource.Sweeper{ Name: "aws_ecs_task_definition", F: testSweepEcsTaskDefinitions, @@ -65,6 +67,12 @@ func testSweepEcsTaskDefinitions(region string) error { return sweeperErrs.ErrorOrNil() } +func testAccErrorCheckSkipECS(t *testing.T) resource.ErrorCheckFunc { + return testAccErrorCheckSkipMessagesContaining(t, + "Unsupported field 'inferenceAccelerators'", + ) +} + func TestAccAWSEcsTaskDefinition_basic(t *testing.T) { var def ecs.TaskDefinition @@ -357,6 +365,10 @@ func TestAccAWSEcsTaskDefinition_withFsxWinFileSystem(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_ecs_task_definition.test" + if testAccGetPartition() == "aws-us-gov" { + t.Skip("Amazon FSx for Windows File Server volumes for ECS tasks are not supported in GovCloud partition") + } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, ecs.EndpointsID), From 7677eb75d7f79d7333997c22da9e2c1b7b5c9781 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Mon, 7 Jun 2021 11:02:55 +0300 Subject: [PATCH 13/15] docs --- aws/resource_aws_gamelift_build.go | 3 +- aws/resource_aws_gamelift_fleet.go | 184 +++++++++++++++--- aws/resource_aws_gamelift_fleet_test.go | 85 ++++---- .../docs/r/ecs_task_definition.html.markdown | 4 +- 4 files changed, 196 insertions(+), 80 deletions(-) diff --git a/aws/resource_aws_gamelift_build.go b/aws/resource_aws_gamelift_build.go index a78e2ec0fa0..7aaa476774f 100644 --- a/aws/resource_aws_gamelift_build.go +++ b/aws/resource_aws_gamelift_build.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" ) func resourceAwsGameliftBuild() *schema.Resource { @@ -93,7 +94,7 @@ func resourceAwsGameliftBuildCreate(d *schema.ResourceData, meta interface{}) er } log.Printf("[INFO] Creating Gamelift Build: %s", input) var out *gamelift.CreateBuildOutput - err := resource.Retry(30*time.Second, func() *resource.RetryError { + err := resource.Retry(iamwaiter.PropagationTimeout, func() *resource.RetryError { var err error out, err = conn.CreateBuild(&input) if err != nil { diff --git a/aws/resource_aws_gamelift_fleet.go b/aws/resource_aws_gamelift_fleet.go index 38ae1164bc1..fe104354a1f 100644 --- a/aws/resource_aws_gamelift_fleet.go +++ b/aws/resource_aws_gamelift_fleet.go @@ -23,7 +23,9 @@ func resourceAwsGameliftFleet() *schema.Resource { Read: resourceAwsGameliftFleetRead, Update: resourceAwsGameliftFleetUpdate, Delete: resourceAwsGameliftFleetDelete, - + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(70 * time.Minute), Delete: schema.DefaultTimeout(20 * time.Minute), @@ -39,20 +41,41 @@ func resourceAwsGameliftFleet() *schema.Resource { Required: true, ForceNew: true, }, - "ec2_instance_type": { - Type: schema.TypeString, - Required: true, + "certificate_configuration": { + Type: schema.TypeList, + Optional: true, ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "certificate_type": { + Type: schema.TypeString, + Optional: true, + Default: gamelift.CertificateTypeDisabled, + ValidateFunc: validation.StringInSlice(gamelift.CertificateType_Values(), false), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if d.Get("certificate_configuration.0.certificate_type").(string) == "" { + return true + } + + return true + }, + }, + }, + }, + }, + "ec2_instance_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(gamelift.EC2InstanceType_Values(), false), }, "fleet_type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: gamelift.FleetTypeOnDemand, - ValidateFunc: validation.StringInSlice([]string{ - gamelift.FleetTypeOnDemand, - gamelift.FleetTypeSpot, - }, false), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: gamelift.FleetTypeOnDemand, + ValidateFunc: validation.StringInSlice(gamelift.FleetType_Values(), false), }, "name": { Type: schema.TypeString, @@ -87,12 +110,9 @@ func resourceAwsGameliftFleet() *schema.Resource { ValidateFunc: validateCIDRNetworkAddress, }, "protocol": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - gamelift.IpProtocolTcp, - gamelift.IpProtocolUdp, - }, false), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(gamelift.IpProtocol_Values(), false), }, "to_port": { Type: schema.TypeInt, @@ -107,6 +127,20 @@ func resourceAwsGameliftFleet() *schema.Resource { Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + // "location_configuration": { + // Type: schema.TypeList, + // Optional: true, + // Computed: true + // MaxItems: 100, + // Elem: &schema.Resource{ + // Schema: map[string]*schema.Schema{ + // "location": { + // Type: schema.TypeString, + // Optional: true, + // }, + // }, + // }, + // }, "metric_groups": { Type: schema.TypeList, Optional: true, @@ -117,13 +151,10 @@ func resourceAwsGameliftFleet() *schema.Resource { }, }, "new_game_session_protection_policy": { - Type: schema.TypeString, - Optional: true, - Default: gamelift.ProtectionPolicyNoProtection, - ValidateFunc: validation.StringInSlice([]string{ - gamelift.ProtectionPolicyNoProtection, - gamelift.ProtectionPolicyFullProtection, - }, false), + Type: schema.TypeString, + Optional: true, + Default: gamelift.ProtectionPolicyNoProtection, + ValidateFunc: validation.StringInSlice(gamelift.ProtectionPolicy_Values(), false), }, "operating_system": { Type: schema.TypeString, @@ -191,6 +222,17 @@ func resourceAwsGameliftFleet() *schema.Resource { }, }, }, + "peer_vpc_aws_account_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateAwsAccountId, + }, + "peer_vpc_id": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + }, "tags": tagsSchema(), "tags_all": tagsSchemaComputed(), @@ -238,6 +280,15 @@ func resourceAwsGameliftFleetCreate(d *schema.ResourceData, meta interface{}) er if v, ok := d.GetOk("runtime_configuration"); ok { input.RuntimeConfiguration = expandGameliftRuntimeConfiguration(v.([]interface{})) } + if v, ok := d.GetOk("certificate_configuration"); ok { + input.CertificateConfiguration = expandGameliftFleetCertificateConfiguration(v.([]interface{})) + } + if v, ok := d.GetOk("peer_vpc_aws_account_id"); ok { + input.PeerVpcAwsAccountId = aws.String(v.(string)) + } + if v, ok := d.GetOk("peer_vpc_id"); ok { + input.PeerVpcId = aws.String(v.(string)) + } log.Printf("[INFO] Creating Gamelift Fleet: %s", input) var out *gamelift.CreateFleetOutput @@ -340,6 +391,7 @@ func resourceAwsGameliftFleetRead(d *schema.ResourceData, meta interface{}) erro arn := aws.StringValue(fleet.FleetArn) d.Set("build_id", fleet.BuildId) d.Set("description", fleet.Description) + d.Set("ec2_instance_type", fleet.InstanceType) d.Set("arn", arn) d.Set("log_paths", aws.StringValueSlice(fleet.LogPaths)) d.Set("metric_groups", flattenStringList(fleet.MetricGroups)) @@ -348,7 +400,25 @@ func resourceAwsGameliftFleetRead(d *schema.ResourceData, meta interface{}) erro d.Set("instance_role_arn", fleet.InstanceRoleArn) d.Set("new_game_session_protection_policy", fleet.NewGameSessionProtectionPolicy) d.Set("operating_system", fleet.OperatingSystem) - d.Set("resource_creation_limit_policy", flattenGameliftResourceCreationLimitPolicy(fleet.ResourceCreationLimitPolicy)) + + if err := d.Set("resource_creation_limit_policy", flattenGameliftResourceCreationLimitPolicy(fleet.ResourceCreationLimitPolicy)); err != nil { + return fmt.Errorf("error setting resource_creation_limit_policy: %w", err) + } + + if err := d.Set("certificate_configuration", flattenGameliftFleetCertificateConfiguration(fleet.CertificateConfiguration)); err != nil { + return fmt.Errorf("error setting certificate_configuration: %w", err) + } + + runtimeOut, err := conn.DescribeRuntimeConfiguration(&gamelift.DescribeRuntimeConfigurationInput{ + FleetId: aws.String(d.Id()), + }) + if err != nil { + return fmt.Errorf("error describing Game Lift Fleet Runtime Configuration (%s): %w", arn, err) + } + if err := d.Set("runtime_configuration", flattenGameliftFleetRuntimeConfiguration(runtimeOut.RuntimeConfiguration)); err != nil { + return fmt.Errorf("error setting runtime_configuration: %w", err) + } + tags, err := keyvaluetags.GameliftListTags(conn, arn) if isAWSErr(err, gamelift.ErrCodeInvalidRequestException, fmt.Sprintf("Resource %s is not in a taggable state", d.Id())) { @@ -356,7 +426,7 @@ func resourceAwsGameliftFleetRead(d *schema.ResourceData, meta interface{}) erro } if err != nil { - return fmt.Errorf("error listing tags for Game Lift Fleet (%s): %s", arn, err) + return fmt.Errorf("error listing tags for Game Lift Fleet (%s): %w", arn, err) } tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) @@ -547,8 +617,8 @@ func flattenGameliftResourceCreationLimitPolicy(policy *gamelift.ResourceCreatio } m := make(map[string]interface{}) - m["new_game_sessions_per_creator"] = *policy.NewGameSessionsPerCreator - m["policy_period_in_minutes"] = *policy.PolicyPeriodInMinutes + m["new_game_sessions_per_creator"] = aws.Int64Value(policy.NewGameSessionsPerCreator) + m["policy_period_in_minutes"] = aws.Int64Value(policy.PolicyPeriodInMinutes) return []interface{}{m} } @@ -679,3 +749,59 @@ OUTER: } return } + +func flattenGameliftFleetRuntimeConfiguration(config *gamelift.RuntimeConfiguration) []interface{} { + if config == nil { + return []interface{}{} + } + + m := make(map[string]interface{}) + m["game_session_activation_timeout_seconds"] = aws.Int64Value(config.GameSessionActivationTimeoutSeconds) + m["max_concurrent_game_session_activations"] = aws.Int64Value(config.MaxConcurrentGameSessionActivations) + m["server_process"] = flattenGameliftFleetRuntimeConfigurationServerProcess(config.ServerProcesses) + + return []interface{}{m} +} + +func flattenGameliftFleetRuntimeConfigurationServerProcess(configs []*gamelift.ServerProcess) []interface{} { + result := make([]interface{}, 0, len(configs)) + + for _, config := range configs { + m := make(map[string]interface{}) + m["concurrent_executions"] = aws.Int64Value(config.ConcurrentExecutions) + m["launch_path"] = aws.StringValue(config.LaunchPath) + + if config.Parameters != nil { + m["parameters"] = aws.StringValue(config.LaunchPath) + } + + result = append(result, m) + } + + return result +} + +func expandGameliftFleetCertificateConfiguration(cfg []interface{}) *gamelift.CertificateConfiguration { + if len(cfg) < 1 { + return nil + } + out := gamelift.CertificateConfiguration{} + m := cfg[0].(map[string]interface{}) + + if v, ok := m["certificate_type"].(string); ok && v != "" { + out.CertificateType = aws.String(v) + } + + return &out +} + +func flattenGameliftFleetCertificateConfiguration(config *gamelift.CertificateConfiguration) []interface{} { + if config == nil { + return []interface{}{} + } + + m := make(map[string]interface{}) + m["certificate_type"] = aws.StringValue(config.CertificateType) + + return []interface{}{m} +} diff --git a/aws/resource_aws_gamelift_fleet_test.go b/aws/resource_aws_gamelift_fleet_test.go index a610038a157..f74158daaa9 100644 --- a/aws/resource_aws_gamelift_fleet_test.go +++ b/aws/resource_aws_gamelift_fleet_test.go @@ -274,7 +274,7 @@ func TestAccAWSGameliftFleet_basic(t *testing.T) { Config: testAccAWSGameliftFleetBasicConfig(fleetName, launchPath, params, buildName, bucketName, key, roleArn), Check: resource.ComposeTestCheckFunc( testAccCheckAWSGameliftFleetExists(resourceName, &conf), - resource.TestCheckResourceAttrSet(resourceName, "build_id"), + resource.TestCheckResourceAttrPair(resourceName, "build_id", "aws_gamelift_build.test", "id"), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "gamelift", regexp.MustCompile(`fleet/fleet-.+`)), resource.TestCheckResourceAttr(resourceName, "ec2_instance_type", "c4.large"), resource.TestCheckResourceAttr(resourceName, "log_paths.#", "0"), @@ -290,12 +290,18 @@ func TestAccAWSGameliftFleet_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccAWSGameliftFleetBasicUpdatedConfig(desc, uFleetName, launchPath, params, buildName, bucketName, key, roleArn), Check: resource.ComposeTestCheckFunc( testAccCheckAWSGameliftFleetExists(resourceName, &conf), - resource.TestCheckResourceAttrSet(resourceName, "build_id"), - testAccMatchResourceAttrRegionalARN(resourceName, "arn", "gamelift", regexp.MustCompile(`fleet/fleet-.+`)), resource.TestCheckResourceAttr(resourceName, "ec2_instance_type", "c4.large"), + resource.TestCheckResourceAttrPair(resourceName, "build_id", "aws_gamelift_build.test", "id"), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "gamelift", regexp.MustCompile(`fleet/fleet-.+`)), + resource.TestCheckResourceAttr(resourceName, "ec2_instance_type", "c4.large"), resource.TestCheckResourceAttr(resourceName, "log_paths.#", "0"), resource.TestCheckResourceAttr(resourceName, "name", uFleetName), resource.TestCheckResourceAttr(resourceName, "description", desc), @@ -360,6 +366,11 @@ func TestAccAWSGameliftFleet_tags(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccAWSGameliftFleetBasicConfigTags2(fleetName, launchPath, params, buildName, bucketName, key, roleArn, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( @@ -461,6 +472,11 @@ func TestAccAWSGameliftFleet_allFields(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "runtime_configuration.0.server_process.0.parameters", params[0]), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccAWSGameliftFleetAllFieldsUpdatedConfig(fleetName, desc, launchPath, params[1], buildName, bucketName, key, roleArn), Check: resource.ComposeTestCheckFunc( @@ -544,7 +560,7 @@ func TestAccAWSGameliftFleet_disappears(t *testing.T) { Config: testAccAWSGameliftFleetBasicConfig(fleetName, launchPath, params, buildName, bucketName, key, roleArn), Check: resource.ComposeTestCheckFunc( testAccCheckAWSGameliftFleetExists(resourceName, &conf), - testAccCheckAWSGameliftFleetDisappears(&conf), + testAccCheckResourceDisappears(testAccProvider, resourceAwsGameliftFleet(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -591,33 +607,6 @@ func testAccCheckAWSGameliftFleetExists(n string, res *gamelift.FleetAttributes) } } -func testAccCheckAWSGameliftFleetDisappears(res *gamelift.FleetAttributes) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).gameliftconn - - input := &gamelift.DeleteFleetInput{FleetId: res.FleetId} - err := resource.Retry(60*time.Minute, func() *resource.RetryError { - _, err := conn.DeleteFleet(input) - if err != nil { - msg := fmt.Sprintf("Cannot delete fleet %s that is in status of ", *res.FleetId) - if isAWSErr(err, gamelift.ErrCodeInvalidRequestException, msg) { - return resource.RetryableError(err) - } - return resource.NonRetryableError(err) - } - return nil - }) - if isResourceTimeoutError(err) { - _, err = conn.DeleteFleet(input) - } - if err != nil { - return fmt.Errorf("Error deleting Gamelift fleet: %s", err) - } - - return waitForGameliftFleetToBeDeleted(conn, *res.FleetId, 15*time.Minute) - } -} - func testAccCheckAWSGameliftFleetDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).gameliftconn @@ -860,6 +849,8 @@ resource "aws_gamelift_build" "test" { func testAccAWSGameLiftFleetIAMRole(rName string) string { return fmt.Sprintf(` +data "aws_partition" "current" {} + resource "aws_iam_role" "test" { name = "test-role-%[1]s" @@ -872,7 +863,7 @@ resource "aws_iam_role" "test" { "Effect": "Allow", "Principal": { "Service": [ - "gamelift.amazonaws.com" + "gamelift.${data.aws_partition.current.dns_suffix}" ] }, "Action": [ @@ -889,23 +880,21 @@ resource "aws_iam_policy" "test" { path = "/" description = "GameLift Fleet PassRole Policy" - policy = < Date: Mon, 7 Jun 2021 11:03:33 +0300 Subject: [PATCH 14/15] Revert "docs" This reverts commit 2ff8db8b63107f398dae08ac74fd7aea705b8aee. --- aws/resource_aws_gamelift_build.go | 3 +- aws/resource_aws_gamelift_fleet.go | 184 +++--------------- aws/resource_aws_gamelift_fleet_test.go | 85 ++++---- .../docs/r/ecs_task_definition.html.markdown | 4 +- 4 files changed, 80 insertions(+), 196 deletions(-) diff --git a/aws/resource_aws_gamelift_build.go b/aws/resource_aws_gamelift_build.go index 7aaa476774f..a78e2ec0fa0 100644 --- a/aws/resource_aws_gamelift_build.go +++ b/aws/resource_aws_gamelift_build.go @@ -11,7 +11,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" - iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" ) func resourceAwsGameliftBuild() *schema.Resource { @@ -94,7 +93,7 @@ func resourceAwsGameliftBuildCreate(d *schema.ResourceData, meta interface{}) er } log.Printf("[INFO] Creating Gamelift Build: %s", input) var out *gamelift.CreateBuildOutput - err := resource.Retry(iamwaiter.PropagationTimeout, func() *resource.RetryError { + err := resource.Retry(30*time.Second, func() *resource.RetryError { var err error out, err = conn.CreateBuild(&input) if err != nil { diff --git a/aws/resource_aws_gamelift_fleet.go b/aws/resource_aws_gamelift_fleet.go index fe104354a1f..38ae1164bc1 100644 --- a/aws/resource_aws_gamelift_fleet.go +++ b/aws/resource_aws_gamelift_fleet.go @@ -23,9 +23,7 @@ func resourceAwsGameliftFleet() *schema.Resource { Read: resourceAwsGameliftFleetRead, Update: resourceAwsGameliftFleetUpdate, Delete: resourceAwsGameliftFleetDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, + Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(70 * time.Minute), Delete: schema.DefaultTimeout(20 * time.Minute), @@ -41,41 +39,20 @@ func resourceAwsGameliftFleet() *schema.Resource { Required: true, ForceNew: true, }, - "certificate_configuration": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "certificate_type": { - Type: schema.TypeString, - Optional: true, - Default: gamelift.CertificateTypeDisabled, - ValidateFunc: validation.StringInSlice(gamelift.CertificateType_Values(), false), - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - if d.Get("certificate_configuration.0.certificate_type").(string) == "" { - return true - } - - return true - }, - }, - }, - }, - }, "ec2_instance_type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice(gamelift.EC2InstanceType_Values(), false), + Type: schema.TypeString, + Required: true, + ForceNew: true, }, "fleet_type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: gamelift.FleetTypeOnDemand, - ValidateFunc: validation.StringInSlice(gamelift.FleetType_Values(), false), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: gamelift.FleetTypeOnDemand, + ValidateFunc: validation.StringInSlice([]string{ + gamelift.FleetTypeOnDemand, + gamelift.FleetTypeSpot, + }, false), }, "name": { Type: schema.TypeString, @@ -110,9 +87,12 @@ func resourceAwsGameliftFleet() *schema.Resource { ValidateFunc: validateCIDRNetworkAddress, }, "protocol": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(gamelift.IpProtocol_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + gamelift.IpProtocolTcp, + gamelift.IpProtocolUdp, + }, false), }, "to_port": { Type: schema.TypeInt, @@ -127,20 +107,6 @@ func resourceAwsGameliftFleet() *schema.Resource { Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - // "location_configuration": { - // Type: schema.TypeList, - // Optional: true, - // Computed: true - // MaxItems: 100, - // Elem: &schema.Resource{ - // Schema: map[string]*schema.Schema{ - // "location": { - // Type: schema.TypeString, - // Optional: true, - // }, - // }, - // }, - // }, "metric_groups": { Type: schema.TypeList, Optional: true, @@ -151,10 +117,13 @@ func resourceAwsGameliftFleet() *schema.Resource { }, }, "new_game_session_protection_policy": { - Type: schema.TypeString, - Optional: true, - Default: gamelift.ProtectionPolicyNoProtection, - ValidateFunc: validation.StringInSlice(gamelift.ProtectionPolicy_Values(), false), + Type: schema.TypeString, + Optional: true, + Default: gamelift.ProtectionPolicyNoProtection, + ValidateFunc: validation.StringInSlice([]string{ + gamelift.ProtectionPolicyNoProtection, + gamelift.ProtectionPolicyFullProtection, + }, false), }, "operating_system": { Type: schema.TypeString, @@ -222,17 +191,6 @@ func resourceAwsGameliftFleet() *schema.Resource { }, }, }, - "peer_vpc_aws_account_id": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validateAwsAccountId, - }, - "peer_vpc_id": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - }, "tags": tagsSchema(), "tags_all": tagsSchemaComputed(), @@ -280,15 +238,6 @@ func resourceAwsGameliftFleetCreate(d *schema.ResourceData, meta interface{}) er if v, ok := d.GetOk("runtime_configuration"); ok { input.RuntimeConfiguration = expandGameliftRuntimeConfiguration(v.([]interface{})) } - if v, ok := d.GetOk("certificate_configuration"); ok { - input.CertificateConfiguration = expandGameliftFleetCertificateConfiguration(v.([]interface{})) - } - if v, ok := d.GetOk("peer_vpc_aws_account_id"); ok { - input.PeerVpcAwsAccountId = aws.String(v.(string)) - } - if v, ok := d.GetOk("peer_vpc_id"); ok { - input.PeerVpcId = aws.String(v.(string)) - } log.Printf("[INFO] Creating Gamelift Fleet: %s", input) var out *gamelift.CreateFleetOutput @@ -391,7 +340,6 @@ func resourceAwsGameliftFleetRead(d *schema.ResourceData, meta interface{}) erro arn := aws.StringValue(fleet.FleetArn) d.Set("build_id", fleet.BuildId) d.Set("description", fleet.Description) - d.Set("ec2_instance_type", fleet.InstanceType) d.Set("arn", arn) d.Set("log_paths", aws.StringValueSlice(fleet.LogPaths)) d.Set("metric_groups", flattenStringList(fleet.MetricGroups)) @@ -400,25 +348,7 @@ func resourceAwsGameliftFleetRead(d *schema.ResourceData, meta interface{}) erro d.Set("instance_role_arn", fleet.InstanceRoleArn) d.Set("new_game_session_protection_policy", fleet.NewGameSessionProtectionPolicy) d.Set("operating_system", fleet.OperatingSystem) - - if err := d.Set("resource_creation_limit_policy", flattenGameliftResourceCreationLimitPolicy(fleet.ResourceCreationLimitPolicy)); err != nil { - return fmt.Errorf("error setting resource_creation_limit_policy: %w", err) - } - - if err := d.Set("certificate_configuration", flattenGameliftFleetCertificateConfiguration(fleet.CertificateConfiguration)); err != nil { - return fmt.Errorf("error setting certificate_configuration: %w", err) - } - - runtimeOut, err := conn.DescribeRuntimeConfiguration(&gamelift.DescribeRuntimeConfigurationInput{ - FleetId: aws.String(d.Id()), - }) - if err != nil { - return fmt.Errorf("error describing Game Lift Fleet Runtime Configuration (%s): %w", arn, err) - } - if err := d.Set("runtime_configuration", flattenGameliftFleetRuntimeConfiguration(runtimeOut.RuntimeConfiguration)); err != nil { - return fmt.Errorf("error setting runtime_configuration: %w", err) - } - + d.Set("resource_creation_limit_policy", flattenGameliftResourceCreationLimitPolicy(fleet.ResourceCreationLimitPolicy)) tags, err := keyvaluetags.GameliftListTags(conn, arn) if isAWSErr(err, gamelift.ErrCodeInvalidRequestException, fmt.Sprintf("Resource %s is not in a taggable state", d.Id())) { @@ -426,7 +356,7 @@ func resourceAwsGameliftFleetRead(d *schema.ResourceData, meta interface{}) erro } if err != nil { - return fmt.Errorf("error listing tags for Game Lift Fleet (%s): %w", arn, err) + return fmt.Errorf("error listing tags for Game Lift Fleet (%s): %s", arn, err) } tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) @@ -617,8 +547,8 @@ func flattenGameliftResourceCreationLimitPolicy(policy *gamelift.ResourceCreatio } m := make(map[string]interface{}) - m["new_game_sessions_per_creator"] = aws.Int64Value(policy.NewGameSessionsPerCreator) - m["policy_period_in_minutes"] = aws.Int64Value(policy.PolicyPeriodInMinutes) + m["new_game_sessions_per_creator"] = *policy.NewGameSessionsPerCreator + m["policy_period_in_minutes"] = *policy.PolicyPeriodInMinutes return []interface{}{m} } @@ -749,59 +679,3 @@ OUTER: } return } - -func flattenGameliftFleetRuntimeConfiguration(config *gamelift.RuntimeConfiguration) []interface{} { - if config == nil { - return []interface{}{} - } - - m := make(map[string]interface{}) - m["game_session_activation_timeout_seconds"] = aws.Int64Value(config.GameSessionActivationTimeoutSeconds) - m["max_concurrent_game_session_activations"] = aws.Int64Value(config.MaxConcurrentGameSessionActivations) - m["server_process"] = flattenGameliftFleetRuntimeConfigurationServerProcess(config.ServerProcesses) - - return []interface{}{m} -} - -func flattenGameliftFleetRuntimeConfigurationServerProcess(configs []*gamelift.ServerProcess) []interface{} { - result := make([]interface{}, 0, len(configs)) - - for _, config := range configs { - m := make(map[string]interface{}) - m["concurrent_executions"] = aws.Int64Value(config.ConcurrentExecutions) - m["launch_path"] = aws.StringValue(config.LaunchPath) - - if config.Parameters != nil { - m["parameters"] = aws.StringValue(config.LaunchPath) - } - - result = append(result, m) - } - - return result -} - -func expandGameliftFleetCertificateConfiguration(cfg []interface{}) *gamelift.CertificateConfiguration { - if len(cfg) < 1 { - return nil - } - out := gamelift.CertificateConfiguration{} - m := cfg[0].(map[string]interface{}) - - if v, ok := m["certificate_type"].(string); ok && v != "" { - out.CertificateType = aws.String(v) - } - - return &out -} - -func flattenGameliftFleetCertificateConfiguration(config *gamelift.CertificateConfiguration) []interface{} { - if config == nil { - return []interface{}{} - } - - m := make(map[string]interface{}) - m["certificate_type"] = aws.StringValue(config.CertificateType) - - return []interface{}{m} -} diff --git a/aws/resource_aws_gamelift_fleet_test.go b/aws/resource_aws_gamelift_fleet_test.go index f74158daaa9..a610038a157 100644 --- a/aws/resource_aws_gamelift_fleet_test.go +++ b/aws/resource_aws_gamelift_fleet_test.go @@ -274,7 +274,7 @@ func TestAccAWSGameliftFleet_basic(t *testing.T) { Config: testAccAWSGameliftFleetBasicConfig(fleetName, launchPath, params, buildName, bucketName, key, roleArn), Check: resource.ComposeTestCheckFunc( testAccCheckAWSGameliftFleetExists(resourceName, &conf), - resource.TestCheckResourceAttrPair(resourceName, "build_id", "aws_gamelift_build.test", "id"), + resource.TestCheckResourceAttrSet(resourceName, "build_id"), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "gamelift", regexp.MustCompile(`fleet/fleet-.+`)), resource.TestCheckResourceAttr(resourceName, "ec2_instance_type", "c4.large"), resource.TestCheckResourceAttr(resourceName, "log_paths.#", "0"), @@ -290,18 +290,12 @@ func TestAccAWSGameliftFleet_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccAWSGameliftFleetBasicUpdatedConfig(desc, uFleetName, launchPath, params, buildName, bucketName, key, roleArn), Check: resource.ComposeTestCheckFunc( testAccCheckAWSGameliftFleetExists(resourceName, &conf), - resource.TestCheckResourceAttrPair(resourceName, "build_id", "aws_gamelift_build.test", "id"), - testAccMatchResourceAttrRegionalARN(resourceName, "arn", "gamelift", regexp.MustCompile(`fleet/fleet-.+`)), - resource.TestCheckResourceAttr(resourceName, "ec2_instance_type", "c4.large"), + resource.TestCheckResourceAttrSet(resourceName, "build_id"), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "gamelift", regexp.MustCompile(`fleet/fleet-.+`)), resource.TestCheckResourceAttr(resourceName, "ec2_instance_type", "c4.large"), resource.TestCheckResourceAttr(resourceName, "log_paths.#", "0"), resource.TestCheckResourceAttr(resourceName, "name", uFleetName), resource.TestCheckResourceAttr(resourceName, "description", desc), @@ -366,11 +360,6 @@ func TestAccAWSGameliftFleet_tags(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccAWSGameliftFleetBasicConfigTags2(fleetName, launchPath, params, buildName, bucketName, key, roleArn, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( @@ -472,11 +461,6 @@ func TestAccAWSGameliftFleet_allFields(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "runtime_configuration.0.server_process.0.parameters", params[0]), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccAWSGameliftFleetAllFieldsUpdatedConfig(fleetName, desc, launchPath, params[1], buildName, bucketName, key, roleArn), Check: resource.ComposeTestCheckFunc( @@ -560,7 +544,7 @@ func TestAccAWSGameliftFleet_disappears(t *testing.T) { Config: testAccAWSGameliftFleetBasicConfig(fleetName, launchPath, params, buildName, bucketName, key, roleArn), Check: resource.ComposeTestCheckFunc( testAccCheckAWSGameliftFleetExists(resourceName, &conf), - testAccCheckResourceDisappears(testAccProvider, resourceAwsGameliftFleet(), resourceName), + testAccCheckAWSGameliftFleetDisappears(&conf), ), ExpectNonEmptyPlan: true, }, @@ -607,6 +591,33 @@ func testAccCheckAWSGameliftFleetExists(n string, res *gamelift.FleetAttributes) } } +func testAccCheckAWSGameliftFleetDisappears(res *gamelift.FleetAttributes) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).gameliftconn + + input := &gamelift.DeleteFleetInput{FleetId: res.FleetId} + err := resource.Retry(60*time.Minute, func() *resource.RetryError { + _, err := conn.DeleteFleet(input) + if err != nil { + msg := fmt.Sprintf("Cannot delete fleet %s that is in status of ", *res.FleetId) + if isAWSErr(err, gamelift.ErrCodeInvalidRequestException, msg) { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + if isResourceTimeoutError(err) { + _, err = conn.DeleteFleet(input) + } + if err != nil { + return fmt.Errorf("Error deleting Gamelift fleet: %s", err) + } + + return waitForGameliftFleetToBeDeleted(conn, *res.FleetId, 15*time.Minute) + } +} + func testAccCheckAWSGameliftFleetDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).gameliftconn @@ -849,8 +860,6 @@ resource "aws_gamelift_build" "test" { func testAccAWSGameLiftFleetIAMRole(rName string) string { return fmt.Sprintf(` -data "aws_partition" "current" {} - resource "aws_iam_role" "test" { name = "test-role-%[1]s" @@ -863,7 +872,7 @@ resource "aws_iam_role" "test" { "Effect": "Allow", "Principal": { "Service": [ - "gamelift.${data.aws_partition.current.dns_suffix}" + "gamelift.amazonaws.com" ] }, "Action": [ @@ -880,21 +889,23 @@ resource "aws_iam_policy" "test" { path = "/" description = "GameLift Fleet PassRole Policy" - policy = jsonencode({ - "Version" : "2012-10-17", - "Statement" : [ - { - "Effect" : "Allow", - "Action" : [ - "iam:PassRole", - "sts:AssumeRole" - ], - "Resource" : [ - "*" - ] - }, + policy = < Date: Mon, 7 Jun 2021 11:06:05 +0300 Subject: [PATCH 15/15] docs --- website/docs/r/ecs_task_definition.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/ecs_task_definition.html.markdown b/website/docs/r/ecs_task_definition.html.markdown index f441a323d05..f1d885c5553 100644 --- a/website/docs/r/ecs_task_definition.html.markdown +++ b/website/docs/r/ecs_task_definition.html.markdown @@ -258,11 +258,11 @@ For more information, see [Specifying an EFS volume in your Task Definition Deve ### fsx_windows_file_server_volume_configuration -For more information, see [Specifying an FSX Windows File Server volume in your Task Definition Developer Guide](https://docs.amazonaws.cn/en_us/AmazonECS/latest/developerguide/tutorial-wfsx-volumes.html) +For more information, see [Specifying an FSX Windows File Server volume in your Task Definition Developer Guide](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/tutorial-wfsx-volumes.html) * `file_system_id` - (Required) The Amazon FSx for Windows File Server file system ID to use. * `root_directory` - (Required) The directory within the Amazon FSx for Windows File Server file system to mount as the root directory inside the host. -* `authorization_config` - (OptiRequiredonal) Configuration block for [authorization](#authorization_config) for the Amazon FSx for Windows File Server file system detailed below. +* `authorization_config` - (Required) Configuration block for [authorization](#authorization_config) for the Amazon FSx for Windows File Server file system detailed below. #### authorization_config