floci-io/floci · error · AwsException

ValidationError

ValidationError

Error message

Updating Name requires resource replacement, which is not supported.

What it means

ValidationError from the AWS::ECS::CapacityProvider provisioner when an update changes the provider's Name. Name is create-only in this implementation (replacement is not modeled), and the guard exists because a name change would otherwise mint a new provider and orphan the old one with nothing referencing it.

Solutions

  1. Keep the Name property identical across updates, or omit it in both the original and updated template.
  2. If a rename is genuinely needed, delete and recreate the stack (or remove and re-add the resource) — replacement semantics.
  3. Beware the asymmetry: a resource first created unnamed keeps its generated name forever; adding a Name later will always trip this guard.

Example fix

# before (v1 omitted Name, v2 adds it)
Type: AWS::ECS::CapacityProvider
Properties:
  Name: prod-cp   # conflicts with generated name from v1

# after (v2 keeps it unnamed, matching v1)
Type: AWS::ECS::CapacityProvider
Properties:
  AutoScalingGroupProvider: ...
Defensive patterns

Strategy: validation

Validate before calling

// Before UpdateStack: declared Name must equal the recorded physical id
String declared = template.stringAt("Resources/Cap/Properties/Name");
String recorded = describeStackResource(stack, "Cap").physicalResourceId();
if (declared != null && !declared.equals(recorded)) {
    throw new IllegalStateException("Name change requires stack recreation");
}

Try / catch

catch ValidationError "Updating Name requires resource replacement" during UpdateStack: either revert the Name in the template or delete/recreate the stack (or move the resource to a new logical id); retrying the same template always fails.

Prevention

When it happens

Trigger: UpdateStack on a stack containing AWS::ECS::CapacityProvider where the template's Name property differs from the physical id recorded at create time — including switching from an unnamed provider to a named one.

Common situations: Renaming a capacity provider in a template between deploys, or adding a Name to a resource that was originally created without one (generated name) so the declared name never equals the stored one.

Understand the failure class

Background: ValidationError explained: why open-source libraries reject your input — file uploads, YAML manifests, unique fields, and query permissions — this error's family across 13 libraries.

Related errors


AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14). Data as JSON: /api/errors/c6d73b31e8a26957. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/io/github/hectorvent/floci/services/cloudformation/provisioners/EcsCapacityCfnProvisioner.java:88

            // providers attached, which is what AWS does: the associations are the resource.
            case ASSOCIATIONS -> clearAssociations(physicalId, region);
            default -> { }
        }
    }

    private void provisionCapacityProvider(StackResource r, JsonNode props, ProvisionContext ctx) {
        String existingName = r.getPhysicalId();
        String declaredName = ctx.resolveOptional(props, "Name");
        // An unnamed provider keeps the name its first execution generated. Minting a fresh one on
        // every pass left the previous provider behind with nothing referencing it.
        String name = declaredName != null && !declaredName.isBlank()
                ? declaredName
                : (existingName != null && !existingName.isBlank()
                        ? existingName
                        : ctx.generatePhysicalName(r.getLogicalId(), 255, false));

        if (existingName != null && !existingName.isBlank() && !existingName.equals(name)) {
            throw new AwsException("ValidationError",
                    "Updating Name requires resource replacement, which is not supported.", 400);
        }

        Map<String, Object> asgProvider = asgProvider(props, ctx);
        Map<String, String> tags = tags(props, ctx);

        // UpdateStack re-executes every resource with the physical id it got at create time, and
        // createCapacityProvider rejects a name that already exists, so an unchanged provider used
        // to fail the whole update. An empty result also covers a provider removed out of band,
        // which is recreated rather than reported as a failure.
        CapacityProvider existing = existingName == null || existingName.isBlank()
                ? null
                : ecsService.describeCapacityProviders(List.of(existingName)).stream()
                        .findFirst().orElse(null);

        if (existing == null) {
            ecsService.createCapacityProvider(name, asgProvider, tags, ctx.region());
        } else {

View on GitHub (pinned to 62ff490619)