Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ecs: ISO-friendly tagging #22529

Merged
merged 14 commits into from
Jan 12, 2022
19 changes: 19 additions & 0 deletions .changelog/22529.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
```release-note:enhancement
resource/aws_ecs_capacity_provider: Attempt `tags`-on-create, fallback to tag after create, and allow some `tags` errors to be non-fatal to support non-standard AWS partitions (i.e., ISO)
```

```release-note:enhancement
resource/aws_ecs_cluster: Attempt `tags`-on-create, fallback to tag after create, and allow some `tags` errors to be non-fatal to support non-standard AWS partitions (i.e., ISO)
```

```release-note:enhancement
resource/aws_ecs_service: Attempt `tags`-on-create, fallback to tag after create, and allow some `tags` errors to be non-fatal to support non-standard AWS partitions (i.e., ISO)
```

```release-note:enhancement
resource/aws_ecs_task_definition: Attempt `tags`-on-create, fallback to tag after create, and allow some `tags` errors to be non-fatal to support non-standard AWS partitions (i.e., ISO)
```

```release-note:enhancement
resource/aws_ecs_task_set: Attempt `tags`-on-create, fallback to tag after create, and allow some `tags` errors to be non-fatal to support non-standard AWS partitions (i.e., ISO)
```
39 changes: 38 additions & 1 deletion internal/service/ecs/capacity_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,32 @@ func resourceCapacityProviderCreate(d *schema.ResourceData, meta interface{}) er
log.Printf("[DEBUG] Creating ECS Capacity Provider: %s", input)
output, err := conn.CreateCapacityProvider(&input)

// Some partitions (i.e., ISO) may not support tag-on-create
if input.Tags != nil && (tfawserr.ErrCodeContains(err, ecs.ErrCodeAccessDeniedException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeInvalidParameterException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeUnsupportedFeatureException)) {
log.Printf("[WARN] ECS Capacity Provider (%s) create failed (%s) with tags. Trying create without tags.", d.Id(), err)
input.Tags = nil
output, err = conn.CreateCapacityProvider(&input)
}

if err != nil {
return fmt.Errorf("error creating ECS Capacity Provider (%s): %w", name, err)
}

// Some partitions (i.e., ISO) may not support tag-on-create, attempt tag after create
if input.Tags == nil && len(tags) > 0 {
err := UpdateTags(conn, d.Id(), nil, tags)

if v, ok := d.GetOk("tags"); (!ok || len(v.(map[string]interface{})) == 0) && (tfawserr.ErrCodeContains(err, ecs.ErrCodeAccessDeniedException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeInvalidParameterException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeUnsupportedFeatureException)) {
// If default tags only, log and continue. Otherwise, error.
log.Printf("[WARN] error adding tags after create for ECS Capacity Provider (%s): %s", d.Id(), err)
return resourceCapacityProviderRead(d, meta)
}

if err != nil {
return fmt.Errorf("error creating ECS Capacity Provider (%s) tags: %w", name, err)
}
}

d.SetId(aws.StringValue(output.CapacityProvider.CapacityProviderArn))

return resourceCapacityProviderRead(d, meta)
Expand Down Expand Up @@ -161,6 +183,12 @@ func resourceCapacityProviderRead(d *schema.ResourceData, meta interface{}) erro

tags := KeyValueTags(output.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

// Some partitions (i.e., ISO) may not support tagging, giving error
if tfawserr.ErrCodeContains(err, ecs.ErrCodeAccessDeniedException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeInvalidParameterException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeUnsupportedFeatureException) {
log.Printf("[WARN] Unable to list tags for ECS Capacity Provider %s: %s", d.Id(), err)
return nil
}

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
Expand Down Expand Up @@ -212,7 +240,16 @@ func resourceCapacityProviderUpdate(d *schema.ResourceData, meta interface{}) er

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")
if err := UpdateTags(conn, d.Id(), o, n); err != nil {

err := UpdateTags(conn, d.Id(), o, n)

// Some partitions (i.e., ISO) may not support tagging, giving error
if tfawserr.ErrCodeContains(err, ecs.ErrCodeAccessDeniedException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeInvalidParameterException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeUnsupportedFeatureException) {
log.Printf("[WARN] Unable to update tags for ECS Capacity Provider %s: %s", d.Id(), err)
return resourceCapacityProviderRead(d, meta)
}

if err != nil {
return fmt.Errorf("error updating ECS Capacity Provider (%s) tags: %w", d.Id(), err)
}
}
Expand Down
138 changes: 92 additions & 46 deletions internal/service/ecs/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,42 +181,35 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error {

input := &ecs.CreateClusterInput{
ClusterName: aws.String(clusterName),
DefaultCapacityProviderStrategy: expandEcsCapacityProviderStrategy(d.Get("default_capacity_provider_strategy").(*schema.Set)),
Tags: Tags(tags.IgnoreAWS()),
DefaultCapacityProviderStrategy: expandCapacityProviderStrategy(d.Get("default_capacity_provider_strategy").(*schema.Set)),
}

if v, ok := d.GetOk("capacity_providers"); ok {
input.CapacityProviders = flex.ExpandStringSet(v.(*schema.Set))
}

if v, ok := d.GetOk("setting"); ok {
input.Settings = expandEcsSettings(v.(*schema.Set))
input.Settings = expandClusterSettings(v.(*schema.Set))
}

if v, ok := d.GetOk("configuration"); ok && len(v.([]interface{})) > 0 {
input.Configuration = expandECSClusterConfiguration(v.([]interface{}))
input.Configuration = expandClusterConfiguration(v.([]interface{}))
}

if len(tags) > 0 {
input.Tags = Tags(tags.IgnoreAWS())
}

// CreateCluster will create the ECS IAM Service Linked Role on first ECS provision
// This process does not complete before the initial API call finishes.
var out *ecs.CreateClusterOutput
err := resource.Retry(tfiam.PropagationTimeout, func() *resource.RetryError {
var err error
out, err = conn.CreateCluster(input)

if tfawserr.ErrMessageContains(err, ecs.ErrCodeInvalidParameterException, "Unable to assume the service linked role") {
return resource.RetryableError(err)
}
out, err := retryClusterCreate(conn, input)

if err != nil {
return resource.NonRetryableError(err)
}

return nil
})
// Some partitions (i.e., ISO) may not support tag-on-create
if input.Tags != nil && (tfawserr.ErrCodeContains(err, ecs.ErrCodeAccessDeniedException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeInvalidParameterException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeUnsupportedFeatureException)) {
log.Printf("[WARN] ECS Cluster (%s) create failed (%s) with tags. Trying create without tags.", d.Id(), err)
input.Tags = nil

if tfresource.TimedOut(err) {
out, err = conn.CreateCluster(input)
out, err = retryClusterCreate(conn, input)
}

if err != nil {
Expand All @@ -231,6 +224,21 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("error waiting for ECS Cluster (%s) to become Available: %w", d.Id(), err)
}

// Some partitions (i.e., ISO) may not support tag-on-create, attempt tag after create
if input.Tags == nil && len(tags) > 0 {
err := UpdateTags(conn, d.Id(), nil, tags)

if v, ok := d.GetOk("tags"); (!ok || len(v.(map[string]interface{})) == 0) && (tfawserr.ErrCodeContains(err, ecs.ErrCodeAccessDeniedException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeInvalidParameterException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeUnsupportedFeatureException)) {
// If default tags only, log and continue. Otherwise, error.
log.Printf("[WARN] error adding tags after create for ECS Cluster (%s): %s", d.Id(), err)
return resourceClusterRead(d, meta)
}

if err != nil {
return fmt.Errorf("error creating ECS Cluster (%s) tags: %w", clusterName, err)
}
}

return resourceClusterRead(d, meta)
}

Expand Down Expand Up @@ -298,22 +306,28 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error {
if err := d.Set("capacity_providers", aws.StringValueSlice(cluster.CapacityProviders)); err != nil {
return fmt.Errorf("error setting capacity_providers: %w", err)
}
if err := d.Set("default_capacity_provider_strategy", flattenEcsCapacityProviderStrategy(cluster.DefaultCapacityProviderStrategy)); err != nil {
if err := d.Set("default_capacity_provider_strategy", flattenCapacityProviderStrategy(cluster.DefaultCapacityProviderStrategy)); err != nil {
return fmt.Errorf("error setting default_capacity_provider_strategy: %w", err)
}

if err := d.Set("setting", flattenEcsSettings(cluster.Settings)); err != nil {
if err := d.Set("setting", flattenClusterSettings(cluster.Settings)); err != nil {
return fmt.Errorf("error setting setting: %w", err)
}

if cluster.Configuration != nil {
if err := d.Set("configuration", flattenECSClusterConfiguration(cluster.Configuration)); err != nil {
if err := d.Set("configuration", flattenClusterConfiguration(cluster.Configuration)); err != nil {
return fmt.Errorf("error setting configuration: %w", err)
}
}

tags := KeyValueTags(cluster.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

// Some partitions (i.e., ISO) may not support tagging, giving error
if tfawserr.ErrCodeContains(err, ecs.ErrCodeAccessDeniedException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeInvalidParameterException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeUnsupportedFeatureException) {
log.Printf("[WARN] Unable to list tags for ECS Cluster %s: %s", d.Id(), err)
return nil
}

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
Expand All @@ -335,11 +349,11 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error {
}

if v, ok := d.GetOk("setting"); ok {
input.Settings = expandEcsSettings(v.(*schema.Set))
input.Settings = expandClusterSettings(v.(*schema.Set))
}

if v, ok := d.GetOk("configuration"); ok && len(v.([]interface{})) > 0 {
input.Configuration = expandECSClusterConfiguration(v.([]interface{}))
input.Configuration = expandClusterConfiguration(v.([]interface{}))
}

_, err := conn.UpdateCluster(&input)
Expand All @@ -352,19 +366,11 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")

if err := UpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating ECS Cluster (%s) tags: %w", d.Id(), err)
}
}

if d.HasChanges("capacity_providers", "default_capacity_provider_strategy") {
input := ecs.PutClusterCapacityProvidersInput{
Cluster: aws.String(d.Id()),
CapacityProviders: flex.ExpandStringSet(d.Get("capacity_providers").(*schema.Set)),
DefaultCapacityProviderStrategy: expandEcsCapacityProviderStrategy(d.Get("default_capacity_provider_strategy").(*schema.Set)),
DefaultCapacityProviderStrategy: expandCapacityProviderStrategy(d.Get("default_capacity_provider_strategy").(*schema.Set)),
}

err := resource.Retry(ecsClusterTimeoutUpdate, func() *resource.RetryError {
Expand Down Expand Up @@ -395,6 +401,22 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")

err := UpdateTags(conn, d.Id(), o, n)

// Some partitions (i.e., ISO) may not support tagging, giving error
if tfawserr.ErrCodeContains(err, ecs.ErrCodeAccessDeniedException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeInvalidParameterException) || tfawserr.ErrCodeContains(err, ecs.ErrCodeUnsupportedFeatureException) {
log.Printf("[WARN] Unable to update tags for ECS Cluster %s: %s", d.Id(), err)
return nil
}

if err != nil {
return fmt.Errorf("error updating ECS Cluster (%s) tags: %w", d.Id(), err)
}
}

return nil
}

Expand Down Expand Up @@ -446,7 +468,31 @@ func resourceClusterDelete(d *schema.ResourceData, meta interface{}) error {
return nil
}

func expandEcsSettings(configured *schema.Set) []*ecs.ClusterSetting {
func retryClusterCreate(conn *ecs.ECS, input *ecs.CreateClusterInput) (*ecs.CreateClusterOutput, error) {
var output *ecs.CreateClusterOutput
err := resource.Retry(tfiam.PropagationTimeout, func() *resource.RetryError {
var err error
output, err = conn.CreateCluster(input)

if tfawserr.ErrMessageContains(err, ecs.ErrCodeInvalidParameterException, "Unable to assume the service linked role") {
return resource.RetryableError(err)
}

if err != nil {
return resource.NonRetryableError(err)
}

return nil
})

if tfresource.TimedOut(err) {
output, err = conn.CreateCluster(input)
}

return output, err
}

func expandClusterSettings(configured *schema.Set) []*ecs.ClusterSetting {
list := configured.List()
if len(list) == 0 {
return nil
Expand All @@ -468,7 +514,7 @@ func expandEcsSettings(configured *schema.Set) []*ecs.ClusterSetting {
return settings
}

func flattenEcsSettings(list []*ecs.ClusterSetting) []map[string]interface{} {
func flattenClusterSettings(list []*ecs.ClusterSetting) []map[string]interface{} {
if len(list) == 0 {
return nil
}
Expand All @@ -485,20 +531,20 @@ func flattenEcsSettings(list []*ecs.ClusterSetting) []map[string]interface{} {
return result
}

func flattenECSClusterConfiguration(apiObject *ecs.ClusterConfiguration) []interface{} {
func flattenClusterConfiguration(apiObject *ecs.ClusterConfiguration) []interface{} {
if apiObject == nil {
return nil
}

tfMap := map[string]interface{}{}

if apiObject.ExecuteCommandConfiguration != nil {
tfMap["execute_command_configuration"] = flattenECSClusterConfigurationExecuteCommandConfiguration(apiObject.ExecuteCommandConfiguration)
tfMap["execute_command_configuration"] = flattenClusterConfigurationExecuteCommandConfiguration(apiObject.ExecuteCommandConfiguration)
}
return []interface{}{tfMap}
}

func flattenECSClusterConfigurationExecuteCommandConfiguration(apiObject *ecs.ExecuteCommandConfiguration) []interface{} {
func flattenClusterConfigurationExecuteCommandConfiguration(apiObject *ecs.ExecuteCommandConfiguration) []interface{} {
if apiObject == nil {
return nil
}
Expand All @@ -510,7 +556,7 @@ func flattenECSClusterConfigurationExecuteCommandConfiguration(apiObject *ecs.Ex
}

if apiObject.LogConfiguration != nil {
tfMap["log_configuration"] = flattenECSClusterConfigurationExecuteCommandConfigurationLogConfiguration(apiObject.LogConfiguration)
tfMap["log_configuration"] = flattenClusterConfigurationExecuteCommandConfigurationLogConfiguration(apiObject.LogConfiguration)
}

if apiObject.Logging != nil {
Expand All @@ -520,7 +566,7 @@ func flattenECSClusterConfigurationExecuteCommandConfiguration(apiObject *ecs.Ex
return []interface{}{tfMap}
}

func flattenECSClusterConfigurationExecuteCommandConfigurationLogConfiguration(apiObject *ecs.ExecuteCommandLogConfiguration) []interface{} {
func flattenClusterConfigurationExecuteCommandConfigurationLogConfiguration(apiObject *ecs.ExecuteCommandLogConfiguration) []interface{} {
if apiObject == nil {
return nil
}
Expand All @@ -545,29 +591,29 @@ func flattenECSClusterConfigurationExecuteCommandConfigurationLogConfiguration(a
return []interface{}{tfMap}
}

func expandECSClusterConfiguration(nc []interface{}) *ecs.ClusterConfiguration {
func expandClusterConfiguration(nc []interface{}) *ecs.ClusterConfiguration {
if len(nc) == 0 {
return &ecs.ClusterConfiguration{}
}
raw := nc[0].(map[string]interface{})

config := &ecs.ClusterConfiguration{}
if v, ok := raw["execute_command_configuration"].([]interface{}); ok && len(v) > 0 {
config.ExecuteCommandConfiguration = expandECSClusterConfigurationExecuteCommandConfiguration(v)
config.ExecuteCommandConfiguration = expandClusterConfigurationExecuteCommandConfiguration(v)
}

return config
}

func expandECSClusterConfigurationExecuteCommandConfiguration(nc []interface{}) *ecs.ExecuteCommandConfiguration {
func expandClusterConfigurationExecuteCommandConfiguration(nc []interface{}) *ecs.ExecuteCommandConfiguration {
if len(nc) == 0 {
return &ecs.ExecuteCommandConfiguration{}
}
raw := nc[0].(map[string]interface{})

config := &ecs.ExecuteCommandConfiguration{}
if v, ok := raw["log_configuration"].([]interface{}); ok && len(v) > 0 {
config.LogConfiguration = expandECSClusterConfigurationExecuteCommandLogConfiguration(v)
config.LogConfiguration = expandClusterConfigurationExecuteCommandLogConfiguration(v)
}

if v, ok := raw["kms_key_id"].(string); ok && v != "" {
Expand All @@ -581,7 +627,7 @@ func expandECSClusterConfigurationExecuteCommandConfiguration(nc []interface{})
return config
}

func expandECSClusterConfigurationExecuteCommandLogConfiguration(nc []interface{}) *ecs.ExecuteCommandLogConfiguration {
func expandClusterConfigurationExecuteCommandLogConfiguration(nc []interface{}) *ecs.ExecuteCommandLogConfiguration {
if len(nc) == 0 {
return &ecs.ExecuteCommandLogConfiguration{}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/service/ecs/cluster_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func dataSourceClusterRead(d *schema.ResourceData, meta interface{}) error {
d.Set("running_tasks_count", cluster.RunningTasksCount)
d.Set("registered_container_instances_count", cluster.RegisteredContainerInstancesCount)

if err := d.Set("setting", flattenEcsSettings(cluster.Settings)); err != nil {
if err := d.Set("setting", flattenClusterSettings(cluster.Settings)); err != nil {
return fmt.Errorf("error setting setting: %w", err)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/service/ecs/flex.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func expandLoadBalancers(configured []interface{}) []*ecs.LoadBalancer {
}

// Flattens an array of ECS LoadBalancers into a []map[string]interface{}
func flattenECSLoadBalancers(list []*ecs.LoadBalancer) []map[string]interface{} {
func flattenLoadBalancers(list []*ecs.LoadBalancer) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, loadBalancer := range list {
l := map[string]interface{}{
Expand Down
Loading