Skip to content

Commit

Permalink
Merge pull request #22529 from hashicorp/f-iso-tagging-ecs
Browse files Browse the repository at this point in the history
ecs: ISO-friendly tagging
  • Loading branch information
YakDriver committed Jan 12, 2022
2 parents 78f5133 + 343e656 commit 203b2f8
Show file tree
Hide file tree
Showing 11 changed files with 436 additions and 200 deletions.
19 changes: 19 additions & 0 deletions .changelog/22529.txt
@@ -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
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
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
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
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

0 comments on commit 203b2f8

Please sign in to comment.