Skip to content

New Resource: aws_ecs_daemon, aws_ecs_daemon_task_definition #47562

Open
dloeafoe wants to merge 9 commits into
hashicorp:mainfrom
dloeafoe:f-aws_ecs_daemon_support
Open

New Resource: aws_ecs_daemon, aws_ecs_daemon_task_definition #47562
dloeafoe wants to merge 9 commits into
hashicorp:mainfrom
dloeafoe:f-aws_ecs_daemon_support

Conversation

@dloeafoe
Copy link
Copy Markdown

@dloeafoe dloeafoe commented Apr 21, 2026

Rollback Plan

If a change needs to be reverted, we will publish an updated version of the library.

Changes to Security Controls

Are there any changes to security controls (access controls, encryption, logging) in this pull request? If so, explain.

Description

Adding support for the daemon and daemon task definition resources in Terraform. Users will be able to Create, Update, Delete, Read and List daemons and daemon task definitions.

Commit 2

Change Log added

Commit 3

Responded to comments left by reviewer. Specific actions taken are addressed in the comments.

Separate list resource structs (2 files):

  1. daemon_list.go — Extracted List Resource implementation from daemon.go into a separate struct, per the public clone's pattern for @FrameworkListResource
  2. daemon_task_definition_list.go — Same for daemon task definition

Include resource list test configs (4 files):
3. testdata/Daemon/list_include_resource/main.tf — Terraform config for the _IncludeResource list test
4. testdata/Daemon/list_include_resource/main.tfquery.hcl — Query config with include_resource = true
5. testdata/DaemonTaskDefinition/list_include_resource/main.tf — Same for daemon task definition
6. testdata/DaemonTaskDefinition/list_include_resource/main.tfquery.hcl — Same for daemon task definition

Template renames (2 files):
7. daemon_basic.gtpl (renamed from daemon_tags.gtpl) — Generator expects _basic.gtpl naming
8. daemon_task_definition_basic.gtpl (renamed from daemon_task_definition_tags.gtpl) — Same

2 new tests:

  • TestAccECSDaemon_List_IncludeResource
  • TestAccECSDaemonTaskDefinition_List_IncludeResource

Commit 4

Made changes based on the second round of reviewer feedback and failed CI checks. The change that addresses each comment is outlined in my response to the corresponding comment. Please let me know if you have any questions.

Commit 5

-Merged main into feature branch to fix failing CI checks.

Commit 6

-Made changes based on reviewer comments. The details of each change is documented in a corresponding reply for each reviewer comment.

Commit 7

-Made changes based on output of failed CI checks and reviewer comments. Each code change is documented in the response to the corresponding reviewer comment.

Commit 8

-Made changes to Daemon Task Definition files in response to full audit and the latest reviewer comments.
-The Data Source files have been removed since they are not required for new resources. They will be added in a future update.
-Expanded test coverage for Daemon Task Definition.

Commit 9

-Made changes to Daemon Resource files based on reviewer comments. The details of the changes are documented in the comments.
-Defensive code added to both resource files
-Expanded test coverage for Daemon resource.

Relations

Closes #47200

References

Output from Acceptance Testing

--- PASS: TestAccECSDaemonTaskDefinition_noCPUMemory (37.98s)
--- PASS: TestAccECSDaemonTaskDefinition_disappears (40.58s)
--- PASS: TestAccECSDaemonTaskDefinition_basic (45.87s)
--- PASS: TestAccECSDaemonTaskDefinition_taskRole (46.47s)
--- PASS: TestAccECSDaemonTaskDefinition_executionRole (46.64s)
--- PASS: TestAccECSDaemonTaskDefinition_tags (76.71s)
--- PASS: TestAccECSDaemonTaskDefinition_multipleContainers (80.68s)

--- PASS: TestAccECSDaemonTaskDefinition_containerDefinitionHealthCheck (42.03s)
--- PASS: TestAccECSDaemonTaskDefinition_containerDefinitionEnvironment (42.09s)
--- PASS: TestAccECSDaemonTaskDefinition_containerDefinitionMountPoint (42.49s)
--- PASS: TestAccECSDaemonTaskDefinition_containerDefinitionOptionalFields (42.60s)
--- PASS: TestAccECSDaemonTaskDefinition_containerDefinitionLogConfiguration (42.86s)
--- PASS: TestAccECSDaemonTaskDefinition_containerDefinitionLogConfigurationSecretOption (43.26s)
--- PASS: TestAccECSDaemonTaskDefinition_containerDefinitionAllNestedBlocks (48.89s)
--- PASS: TestAccECSDaemonTaskDefinition_containerDefinitionsUpdate (58.75s)

--- PASS: TestAccECSDaemonTaskDefinition_volumeWithoutHostPath (21.08s)
--- PASS: TestAccECSDaemonTaskDefinition_volume (58.33s)

--- PASS: TestAccECSDaemonTaskDefinition_List_regionOverride (31.62s)
--- PASS: TestAccECSDaemonTaskDefinition_List_includeResource (33.58s)
--- PASS: TestAccECSDaemonTaskDefinition_List_basic (33.61s)
--- PASS: TestAccECSDaemonTaskDefinition_Identity_basic (53.39s)
--- PASS: TestAccECSDaemonTaskDefinition_Identity_regionOverride (61.04s)

--- PASS: TestAccECSDaemon_disappears (46.95s)
--- PASS: TestAccECSDaemon_basic (51.76s)
--- PASS: TestAccECSDaemon_updateTaskDefinition (65.10s)
--- PASS: TestAccECSDaemon_tags (87.75s)

--- PASS: TestAccECSDaemon_minimumValues (55.66s)
--- PASS: TestAccECSDaemon_boundaryValues (57.81s)
--- PASS: TestAccECSDaemon_propagateTags (78.12s)
--- PASS: TestAccECSDaemon_enableManagedTags (78.37s)
--- PASS: TestAccECSDaemon_enableExecuteCommand (78.90s)
--- PASS: TestAccECSDaemon_alarms (79.65s)
--- PASS: TestAccECSDaemon_deploymentConfiguration (86.04s)

--- PASS: TestAccECSDaemon_List_regionOverride (47.22s)
--- PASS: TestAccECSDaemon_List_includeResource (50.52s)
--- PASS: TestAccECSDaemon_List_basic (51.18s)
--- PASS: TestAccECSDaemon_Identity_basic (74.50s)
--- PASS: TestAccECSDaemon_Identity_regionOverride (79.94s)

@dloeafoe dloeafoe requested a review from a team as a code owner April 21, 2026 18:29
@dosubot dosubot Bot added new-data-source Introduces a new data source. new-list-resource Introduces list resource support. new-resource Introduces a new resource. labels Apr 21, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Community Guidelines

This comment is added to every new Pull Request to provide quick reference to how the Terraform AWS Provider is maintained. Please review the information below, and thank you for contributing to the community that keeps the provider thriving! 🚀

Voting for Prioritization

  • Please vote on this Pull Request by adding a 👍 reaction to the original post to help the community and maintainers prioritize it.
  • Please see our prioritization guide for additional information on how the maintainers handle prioritization.
  • Please do not leave +1 or other comments that do not add relevant new information or questions; they generate extra noise for others following the Pull Request and do not help prioritize the request.

Pull Request Authors

  • Review the contribution guide relating to the type of change you are making to ensure all of the necessary steps have been taken.
  • Whether or not the branch has been rebased will not impact prioritization, but doing so is always a welcome surprise.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

✅ Thank you for correcting the previously detected issues! The maintainers appreciate your efforts to make the review process as smooth as possible.

@github-actions github-actions Bot added needs-triage Waiting for first response or review from a maintainer. documentation Introduces or discusses updates to documentation. tests PRs: expanded test coverage. Issues: expanded coverage, enhancements to test infrastructure. service/ecs Issues and PRs that pertain to the ecs service. generators Relates to code generators. size/XL Managed by automation to categorize the size of a PR. labels Apr 21, 2026
@wellsiau-aws
Copy link
Copy Markdown
Contributor

tagging @jar-b

@jar-b jar-b added partner Contribution from a partner. and removed needs-triage Waiting for first response or review from a maintainer. labels Apr 21, 2026
Comment thread internal/service/ecs/testdata/tmpl/daemon_tags.gtpl Outdated
Comment thread internal/service/ecs/testdata/tmpl/daemon_tags.gtpl Outdated
Comment thread internal/service/ecs/daemon.go Outdated
"enable_ecs_managed_tags": schema.BoolAttribute{
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where possible, avoid provider-side default values in favor of optional/computed with a UseStateForUnknown plan modifier. More context here:
https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#default-values

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These fields (enable_ecs_managed_tags and enable_execute_command) are write-only — they're accepted by CreateDaemon and UpdateDaemon but not returned by DescribeDaemon (DaemonDetail doesn't include them). With UseStateForUnknown(), the values remain unknown after Create when the user doesn't set them, causing Terraform to error with "Provider returned invalid result object after apply — still indicated an unknown value." The provider-side default is necessary here because there's no API response to populate these fields from. This matches the pattern used by wait_for_steady_state in express_gateway_service.go for the same reason.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps these should instead be marked as WriteOnly then?
https://developer.hashicorp.com/terraform/plugin/framework/resources/write-only-arguments

Comment thread internal/service/ecs/daemon.go Outdated
Comment thread internal/service/ecs/daemon.go Outdated
Comment on lines +184 to +186
if len(plan.DeploymentConfiguration) > 0 {
input.DeploymentConfiguration = expandDaemonDeploymentConfigurationFromModel(plan.DeploymentConfiguration[0])
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is a fairly straightforward structure, AutoFlex should be able to handle this. Can you describe the issues you're observing that warrant the custom expand function?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated and confirmed AutoFlex can handle the full conversion. Updated model types to be AutoFlex-compatible: changed DeploymentConfiguration from []deploymentConfigurationModel with autoflex:"-" to fwtypes.ListNestedObjectValueOf[deploymentConfigurationModel], changed Alarms from []alarmConfigurationModel to fwtypes.ListNestedObjectValueOf[alarmConfigurationModel], and changed AlarmNames from types.Set to fwtypes.SetOfString. Removed the manual expand calls from both Create and Update in daemon.go and deleted the expandDaemonDeploymentConfigurationFromModel function entirely. AutoFlex now handles the full DeploymentConfigurationDaemonDeploymentConfiguration conversion automatically.

Comment thread .changelog/47562.txt
Comment thread website/docs/d/ecs_daemon_task_definitions.html.markdown Outdated
Comment thread website/docs/r/ecs_daemon.html.markdown
Comment thread website/docs/r/ecs_daemon.html.markdown Outdated
Comment thread website/docs/r/ecs_daemon_task_definition.html.markdown Outdated
@dloeafoe
Copy link
Copy Markdown
Author

Hi @jar-b. Thank you for the feedback. I've been working my way through the list of changes. I have completed most of the changes. I will finish up the rest of the changes and run the acceptance tests today. You can expect to see a new commit before the end of the day. I will put a summary of each change I made in the corresponding comment.

@dloeafoe dloeafoe requested a review from jar-b April 24, 2026 15:54
@dloeafoe
Copy link
Copy Markdown
Author

Hi @jar-b. I've made the changes that you've requested. I've also detailed the changes I've made in each corresponding comment. Please let me know if you have any other feedback for me.

Comment thread internal/service/ecs/daemon.go Outdated
"enable_ecs_managed_tags": schema.BoolAttribute{
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps these should instead be marked as WriteOnly then?
https://developer.hashicorp.com/terraform/plugin/framework/resources/write-only-arguments

Comment thread internal/service/ecs/daemon.go Outdated
Comment thread internal/service/ecs/daemon.go Outdated
Comment thread internal/service/ecs/daemon.go Outdated
Comment thread internal/service/ecs/daemon_data_source.go Outdated
Comment thread internal/service/ecs/daemon_task_definitions_data_source.go Outdated
Comment thread internal/service/ecs/daemon_task_definitions_data_source.go Outdated
Comment thread internal/service/ecs/daemon_task_definitions_data_source.go Outdated
Comment thread internal/service/ecs/daemons_data_source.go Outdated
Comment thread internal/service/ecs/daemons_data_source.go Outdated
@jar-b
Copy link
Copy Markdown
Member

jar-b commented Apr 28, 2026

Thanks @dloeafoe, I've left another round of comments and there are still some CI checks failing which will need to be resolved. Let me know if there is anything I can assist with.

@dloeafoe
Copy link
Copy Markdown
Author

Thanks again for the feedback @jar-b. I've gone ahead and addressed the issues you raised along with the issues from the failed CI checks. What I did in response to each issue is detailed in my response for each corresponding comment. Let me know if you have any questions about what I did.

I did have one thing with which I needed some assistance. There were 3 CI checks that said I have a type mismatch error in service_package_gen.go unique.Handle. I will list the 3 affected CI check below. I wanted to know if you could provide some insight as to why these CI checks failed. I'm not sure what to do about this since service_package_gen.go is a generated file.

  1. Modern Go Check (modernize)
  2. Provider Checks / go-build
  3. ProviderLint Checks / providerlint

@dloeafoe dloeafoe requested a review from jar-b April 29, 2026 08:49
@jar-b
Copy link
Copy Markdown
Member

jar-b commented Apr 29, 2026

There were 3 CI checks that said I have a type mismatch error in service_package_gen.go unique.Handle.

You will likely need to merge main back into your branch and re-run the ecs generators (go generate internal/service/ecs). Within the last ~2 months there was a small change to the service package generator template, so my suspicion is you're generating from an outdated template.

Comment thread internal/service/ecs/daemon.go Outdated
}
}

func newNullObject(typ attr.Type) (obj basetypes.ObjectValue, diags diag.Diagnostics) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be unused. Can it be removed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has been removed.

Comment thread internal/service/ecs/daemon.go Outdated
Comment on lines +365 to +368
case err == nil:
// no-op, continue on to waiter
case errs.IsAErrorMessageContains[*awstypes.InvalidParameterException](err, "deployment deletion is ongoing"):
// no-op, continue on to waiter
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slight simplification.

Suggested change
case err == nil:
// no-op, continue on to waiter
case errs.IsAErrorMessageContains[*awstypes.InvalidParameterException](err, "deployment deletion is ongoing"):
// no-op, continue on to waiter
case err == nil, errs.IsAErrorMessageContains[*awstypes.InvalidParameterException](err, "deployment deletion is ongoing"):
// no-op, continue on to waiter

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combined two case clauses with identical // no-op, continue on to waiter bodies into one comma-separated case: case err == nil, errs.IsAErrorMessageContains[...]("deployment deletion is ongoing"):.

Comment thread internal/service/ecs/daemon.go Outdated
return obj, diags
}

func daemonNameFromARN(arn string) types.String {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to have unit tests for this. Also, should if len(arnParts) match on exactly 3? Is there a valid form of the daemon ARN with more than 3 /-deliminted parts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed len(arnParts) >= 3 to len(arnParts) == 3 and arnParts[len(arnParts)-1] to arnParts[2] in daemonNameFromARN in daemon.go. Added DaemonNameFromARN = daemonNameFromARN export to exports_test.go following the existing pattern for exposing unexported functions to the test package. Added TestDaemonNameFromARN table-driven unit test to daemon_test.go . Test covers 5 cases: valid daemon ARN (returns name), too few parts (returns null), too many parts (returns null), empty string (returns null), no slashes (returns null).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use arn.Parse() from aws-sdk instead

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced manual strings.Split(arn, "/") with arn.Parse() from github.com/aws/aws-sdk-go-v2/aws/arn. Now validates full ARN structure before extracting the daemon name from parsed.Resource. Matches pattern in task_definition.go which also uses arn.Parse(). Added "github.com/aws/aws-sdk-go-v2/aws/arn" import.

Comment thread internal/service/ecs/daemon_data_source.go Outdated
Comment thread internal/service/ecs/daemon_list.go Outdated
Comment on lines +60 to +62
if !query.ClusterArn.IsNull() {
input.ClusterArn = query.ClusterArn.ValueStringPointer()
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a required argument, cluster_arn should never be null.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed if !query.ClusterArn.IsNull() guard and inlined ClusterArn into the ListDaemonsInput struct initialization in daemon_list.go.

Comment on lines +76 to +83
var summaries []daemonTaskDefinitionSummaryModel
for _, summary := range results {
var s daemonTaskDefinitionSummaryModel
response.Diagnostics.Append(fwflex.Flatten(ctx, summary, &s)...)
summaries = append(summaries, s)
}

data.DaemonTaskDefinitions = fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, summaries)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AutoFlex should be able to handle this without manually iterating over each summary. If needed you can pass just the DaemonTaskDefinitions argument as the "target" argument (e.g. fwflex.Flatten(ctx, results, data.DaemonTaskDefinitions))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced 7-line manual loop with fwflex.Flatten(ctx, results, &data.DaemonTaskDefinitions) in daemon_task_definitions_data_source.go. Same pattern in daemons_data_source.go — replaced with fwflex.Flatten(ctx, summaries, &data.Daemons).

Revision fwtypes.StringEnum[awstypes.DaemonTaskDefinitionRevisionFilter] `tfsdk:"revision"`
Sort fwtypes.StringEnum[awstypes.SortOrder] `tfsdk:"sort"`
Status fwtypes.StringEnum[awstypes.DaemonTaskDefinitionStatusFilter] `tfsdk:"status"`
DaemonTaskDefinitions fwtypes.ListNestedObjectValueOf[daemonTaskDefinitionSummaryModel] `tfsdk:"daemon_task_definitions"`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit- alphabetize

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is now alphabetized.

Comment on lines +69 to +87
var results []daemonSummaryModel
for _, summary := range summaries {
s := daemonSummaryModel{
DaemonArn: types.StringPointerValue(summary.DaemonArn),
Status: types.StringValue(string(summary.Status)),
}

if summary.CreatedAt != nil {
s.CreatedAt = timetypes.NewRFC3339TimePointerValue(summary.CreatedAt)
}

if summary.UpdatedAt != nil {
s.UpdatedAt = timetypes.NewRFC3339TimePointerValue(summary.UpdatedAt)
}

results = append(results, s)
}

data.Daemons = fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, results)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch to AutoFlex here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced 14 lines of manual field-by-field mapping in daemons_data_source.go (types.StringPointerValue, types.StringValue(string(...)), timetypes.NewRFC3339TimePointerValue with nil checks) with a 4-line fwflex.Flatten loop.

Comment thread internal/service/ecs/daemon.go Outdated
Comment on lines +492 to +495
err := listDaemonsPages(ctx, conn, input, func(page *ecs.ListDaemonsOutput, lastPage bool) bool {
result = append(result, page.DaemonSummariesList...)
return !lastPage
})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a check to prevent potential nil pointer deferences when page is nil.

Suggested change
err := listDaemonsPages(ctx, conn, input, func(page *ecs.ListDaemonsOutput, lastPage bool) bool {
result = append(result, page.DaemonSummariesList...)
return !lastPage
})
err := listDaemonsPages(ctx, conn, input, func(page *ecs.ListDaemonsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}
result = append(result, page.DaemonSummariesList...)
return !lastPage
})

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added if page == nil { return !lastPage } guard to paginator callbacks in 4 files: daemon.go (findDaemons), daemon_list.go (listDaemonSummaries), daemon_task_definition.go (findDaemonTaskDefinitions), and daemon_task_definition_list.go (listDaemonTaskDefinitionSummaries).

Comment on lines +795 to +798
err := listDaemonTaskDefinitionsPages(ctx, conn, input, func(page *ecs.ListDaemonTaskDefinitionsOutput, lastPage bool) bool {
result = append(result, page.DaemonTaskDefinitions...)
return !lastPage
})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
err := listDaemonTaskDefinitionsPages(ctx, conn, input, func(page *ecs.ListDaemonTaskDefinitionsOutput, lastPage bool) bool {
result = append(result, page.DaemonTaskDefinitions...)
return !lastPage
})
err := listDaemonTaskDefinitionsPages(ctx, conn, input, func(page *ecs.ListDaemonTaskDefinitionsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}
result = append(result, page.DaemonTaskDefinitions...)
return !lastPage
})

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in previous comment.

func (r *daemonResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) {
response.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
names.AttrARN: schema.StringAttribute{
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added CustomType: fwtypes.ARNType to the schema, changed model field from types.String to fwtypes.ARN, and changed the Create assignment from types.StringValue(...) to fwtypes.ARNValue(...).

names.AttrName: schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added stringvalidator.LengthBetween(1, 255) and stringvalidator.RegexMatches(regexache.MustCompile("^[0-9A-Za-z_-]+$"), ...) to the name attribute. API docs state: "Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed." Same pattern as family in daemon_task_definition.go. Added regexache and stringvalidator imports.

https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_CreateDaemon.html#API_CreateDaemon_ResponseElements

},
"daemon_task_definition": schema.StringAttribute{
Required: true,
},
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added deployment_arn as a Computed attribute with CustomType: fwtypes.ARNType. This field is the ARN of the most recent daemon deployment, returned by DaemonDetail.DeploymentArn in the Describe response. Added to documentation Attribute Reference and basic acceptance test (TestCheckResourceAttrSet).

},
},
names.AttrPropagateTags: schema.StringAttribute{
Optional: true,
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field is in CreateDaemonInput and UpdateDaemonInput but absent from DaemonDetail (Describe response). API never returns this value, making it write-only.

Comment thread internal/service/ecs/daemon.go Outdated
names.AttrStatus: schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UseStateForUnknown was removed since it was incorrect, it would hide drift. Added CustomType: fwtypes.StringEnumType[awstypes.DaemonStatus]() and changed model from types.String to fwtypes.StringEnum[awstypes.DaemonStatus] to match the SDK enum type.

Comment thread internal/service/ecs/daemon.go Outdated
"bake_time_in_minutes": schema.Int64Attribute{
Optional: true,
Validators: []validator.Int64{
int64validator.Between(0, 1440),
Copy link
Copy Markdown
Author

@dloeafoe dloeafoe May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed validator. Not in documentation.

Public API docs state "The default value is 0" for bakeTimeInMinutes. Added Computed: true and Default: int64default.StaticInt64(0) so Terraform fills in 0 when user omits the attribute. Computed is required alongside Default to allow Terraform to populate the value in state. Added int64default import.

https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DaemonDeploymentConfiguration.html

},
},
"firelens_configuration": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[firelensConfigurationModel](ctx),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added listvalidator.SizeAtMost(1) — SDK field is FirelensConfiguration *FirelensConfiguration (pointer = single object). Prevents silent data loss from multiple blocks.

},
},
names.AttrHealthCheck: schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[healthCheckModel](ctx),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added listvalidator.SizeAtMost(1) — SDK field is HealthCheck *HealthCheck (pointer = single object). Prevents silent data loss from multiple blocks.

},
},
"linux_parameters": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[daemonLinuxParametersModel](ctx),
Copy link
Copy Markdown
Author

@dloeafoe dloeafoe May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added listvalidator.SizeAtMost(1) — SDK field is LinuxParameters *DaemonLinuxParameters (pointer = single object). Prevents silent data loss from multiple blocks.

},
Blocks: map[string]schema.Block{
"capabilities": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[kernelCapabilitiesModel](ctx),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added listvalidator.SizeAtMost(1) — SDK field is Capabilities *KernelCapabilities (pointer = single object). Prevents silent data loss from multiple blocks.

},
},
"log_configuration": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[logConfigurationModel](ctx),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added listvalidator.SizeAtMost(1) — SDK field is LogConfiguration *LogConfiguration (pointer = single object). Prevents silent data loss from multiple blocks.

},
},
"repository_credentials": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[repositoryCredentialsModel](ctx),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added listvalidator.SizeAtMost(1) — SDK field is RepositoryCredentials RepositoryCredentials` (pointer = single object). Prevents silent data loss from multiple blocks.

},
},
"restart_policy": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[containerRestartPolicyModel](ctx),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added listvalidator.SizeAtMost(1) — SDK field is RestartPolicy *ContainerRestartPolicy (pointer = single object). Prevents silent data loss from multiple blocks.

Comment thread internal/service/ecs/daemon.go Outdated
input.EnableExecuteCommand = execCmd.ValueBool()
}

output, err := conn.CreateDaemon(ctx, &input)
Copy link
Copy Markdown
Author

@dloeafoe dloeafoe May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added defensive code to handle partitions that don't support tag-on-create. If CreateDaemon fails with tags and the error is a partition unsupported operation, retries without tags then applies tags separately via createTags. Pattern matches all taggable ECS resources have this.

Added retryDaemonCreate that wraps conn.CreateDaemon with tfresource.RetryWhen. Retries on ClusterNotFoundException (cluster just created in same apply) and InvalidParameterException containing "Unable to assume the service linked role" (IAM propagation delay). Total retry window: 4 minutes (propagationTimeout + 2min). Matches pattern in service.go (retryServiceCreate) and express_gateway_service.go (retryExpressGatewayServiceCreate). Replaced direct conn.CreateDaemon calls in Create function with retryDaemonCreate.

return
}

daemon, err := findDaemonByARN(ctx, conn, plan.DaemonArn.ValueString())
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After findDaemonByARN in Create, added retry.NotFound(err) check that gracefully removes the resource from state if the daemon was deleted between creation and read-back. Matches the pattern in service.go which calls resourceServiceRead (which has not-found handling). Defends against the scenario of external deletion during the Create flow.

response.Diagnostics.AddError(fmt.Sprintf("creating ECS Daemon (%s)", plan.DaemonName.ValueString()), err.Error())
return
}

Copy link
Copy Markdown
Author

@dloeafoe dloeafoe May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added defensive check if output == nil || output.DaemonArn == nil after the CreateDaemon API call. Prevents confusing downstream errors if the API returns a successful response with empty/nil fields. Matches express_gateway_service.go pattern which checks out == nil || out.Service == nil.

Added response.State.SetAttribute(ctx, path.Root("arn"), output.DaemonArn) immediately after creation, before waitDaemonActive. Also added github.com/hashicorp/terraform-plugin-framework/path import. Without this, if the waiter times out, the Create function returns an error and nothing is saved to state. The daemon exists in AWS but Terraform has no record of it and it is now an orphaned resource. On the next terraform apply, Terraform tries to create it again, fails with "already exists", and the user must manually terraform import to recover. With partial state save, even if the waiter times out, Terraform knows the resource exists (has its ARN) and can read/update/delete it on the next run without manual intervention. Matches express_gateway_service.go which does resp.State.SetAttribute(ctx, path.Root("service_arn"), out.Service.ServiceArn) before its waiter, and service.go which does d.SetId(arn) before its waiter.

Comment thread internal/service/ecs/daemon.go Outdated
input.EnableExecuteCommand = execCmd.ValueBool()
}

_, err := conn.UpdateDaemon(ctx, &input)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added retryDaemonUpdate that wraps conn.UpdateDaemon with tfresource.RetryWhen. Retries on InvalidParameterException containing "Unable to assume the service linked role" (IAM propagation delay when task definition references a newly created role). Total retry window: 4 minutes. Replaced direct conn.UpdateDaemon call in Update function. Matches pattern in service.go (inline RetryWhen in Update) and express_gateway_service.go (retryExpressGatewayServiceUpdate).

input.EnableECSManagedTags = managedTags.ValueBool()
}
if !execCmd.IsNull() {
input.EnableExecuteCommand = execCmd.ValueBool()
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated Create function to set input.PropagateTags = awstypes.DaemonPropagateTags(propagateTags.ValueString()) when not null. Previously, users setting propagate_tags = "DAEMON" had the value silently ignored.

input.EnableECSManagedTags = managedTags.ValueBool()
}
if !execCmd.IsNull() {
input.EnableExecuteCommand = execCmd.ValueBool()
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated Update function to set input.PropagateTags = awstypes.DaemonPropagateTags(propagateTags.ValueString()) when not null. Previously, users setting propagate_tags = "DAEMON" had the value silently ignored.

Optional: true,
},
names.AttrName: schema.StringAttribute{
Optional: true,
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In daemon_task_definition.go: Added LengthAtMost(255) and regex ^[0-9A-Za-z_-]+$ validators to name inside container_definition, per public API docs.

https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DaemonContainerDefinition.html

Required: true,
},
names.AttrValue: schema.StringAttribute{
Required: true,
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed name and value in the environment block of container_definition from Required to Optional in daemon_task_definition.go.

"retries": schema.Int64Attribute{
Optional: true,
Validators: []validator.Int64{
int64validator.Between(1, 10),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added Computed: true and Default: int64default.StaticInt64(3) to retries in the health_check block of daemon_task_definition.go. Per public docs and SDK comments, the API server defaults retries to 3 when omitted.

names.AttrTimeout: schema.Int64Attribute{
Optional: true,
Validators: []validator.Int64{
int64validator.Between(2, 60),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added Computed: true and Default: int64default.StaticInt64(5) to timeout in the health_check block of daemon_task_definition.go.

input.Volumes = expandDaemonVolumesFromModel(volumeSlice)
}

output, err := conn.RegisterDaemonTaskDefinition(ctx, &input)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added retry logic that clears tags and retries RegisterDaemonTaskDefinition if the partition doesn't support tag-on-create (errs.IsUnsupportedOperationInPartitionError). Matches the pattern in `task_definition.go.

return
}

plan.DaemonTaskDefinitionArn = fwtypes.ARNValue(aws.ToString(output.DaemonTaskDefinitionArn))
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added createTags call after registration when input.Tags == nil && len(tags) > 0. This handles partitions that don't support tag-on-create by applying tags separately. Matches task_definition.go.

response.Diagnostics.AddError(fmt.Sprintf("creating ECS Daemon Task Definition (%s)", plan.Family.ValueString()), err.Error())
return
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added if output == nil || output.DaemonTaskDefinitionArn == nil check that returns "empty output from API" error. Prevents nil pointer panic if the API returns an unexpected empty response.

DaemonTaskDefinition: state.DaemonTaskDefinitionArn.ValueStringPointer(),
})

if errs.IsA[*awstypes.ClientException](err) {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed errs.IsA[*awstypes.ClientException](err) (swallows ALL ClientExceptions) to specific checks: DaemonTaskDefinitionNotFoundException, ClientException containing "not found", or ClientException containing "being deleted". Prevents silently swallowing real errors like permissions issues. Matches task_definition.go pattern.

Add defensive code patterns, fix schema issues, and improve test
assertions for ECS Daemon and Daemon Task Definition resources.

Daemon changes:
- Add tag-on-create retry and tag-after-create for ISO partitions
- Add nil output check after CreateDaemon
- Add retryDaemonCreate/retryDaemonUpdate with IAM propagation retry
- Add partial state save before waiter to prevent orphaned resources
- Add WithIgnoredField for write-only fields in fwflex.Diff
- Fix propagate_tags never being sent to API
- Change capacity_provider_arns from List to Set
- Refactor daemonNameFromARN to use arn.Parse
- Strengthen test assertions (minimumValues, boundaryValues, deployment_arn)
- Remove write-only field assertions from propagateTags test

Daemon Task Definition changes:
- Add tag-on-create retry and tag-after-create for ISO partitions
- Add nil output check after RegisterDaemonTaskDefinition
- Narrow Delete error handling to specific not-found conditions
- Add validators to container_definition name and volume name
- Add default values for health_check retries (3) and timeout (5)
- Restore Computed on cpu and user (API returns values when not set)
- Change environment block attributes from Required to Optional
- Reorder functions (helpers before models)

Shared:
- Format testdata files with terraform fmt
- Format Go files with gofmt
@dloeafoe dloeafoe requested a review from jar-b May 22, 2026 13:41
@dloeafoe
Copy link
Copy Markdown
Author

Hi @jar-b. I wanted to bring up an issue I came across in daemon_task_definition.go while I was testing in preparation for my next commit and review. I wanted to get your feedback on how I went about this. It's similar to the issue I brought up last time.

The API returns a value for cpu even when not set in config. cpu returns 0 (since the SDK field is int32, not *int32). Neither the SDK nor the public docs formally document these as computed/defaulted, but the API behavior requires Computed: true to avoid the inconsistency error.

I added Computed: true without UseStateForUnknown since this is an immutable resource and each replacement creates a new revision.

I wanted to know if you agree with this approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Introduces or discusses updates to documentation. generators Relates to code generators. new-data-source Introduces a new data source. new-list-resource Introduces list resource support. new-resource Introduces a new resource. partner Contribution from a partner. service/ecs Issues and PRs that pertain to the ecs service. size/XL Managed by automation to categorize the size of a PR. tests PRs: expanded test coverage. Issues: expanded coverage, enhancements to test infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Managed Daemons for ECS Managed Instances

7 participants