Repository: floci-io/floci (2026-07-28T17:34:46Z)

Issue:
[BUG] CloudFormation: AWS::EC2::Subnet drops MapPublicIpOnLaunch
## Summary

An `AWS::EC2::Subnet` with `MapPublicIpOnLaunch: true` deploys with the attribute **false**: the CFN provisioner calls `Ec2Service.createSubnet(region, vpcId, cidr, az)`, which has no MapPublicIpOnLaunch parameter, and never applies the template value (the only setter is `ModifySubnetAttribute`).

Repro: a CDK/CFN VPC with a public subnet (`MapPublicIpOnLaunch: true`). `DescribeSubnets` reports `false`; a default-VPC subnet correctly reports `true`.

## Why this matters

Public-vs-private subnet is a core topology fact. With it always false on custom subnets, 'which instances are in public subnets' has no reliable signal — every consumer must fall back to route-table→IGW inference.

Found running the [aws-bench](https://github.com/aws-bench/aws-bench) benchmark scenarios against Floci (series: lex00/floci#17): the public-subnet-membership task is unanswerable via the attribute because of this.

## Expected

The provisioned subnet reflects the template's `MapPublicIpOnLaunch`.

## Are you willing to contribute a PR?

Yes.


Discussion:

Patch:
diff --git a/src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationResourceProvisioner.java b/src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationResourceProvisioner.java
index 75b34a830b..e6e6e60deb 100644
--- a/src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationResourceProvisioner.java
+++ b/src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationResourceProvisioner.java
@@ -587,7 +587,11 @@ private void provisionSubnet(StackResource r, JsonNode props, CloudFormationTemp
         String vpcId = resolveOptional(props, "VpcId", engine);
         String cidr = resolveOptional(props, "CidrBlock", engine);
         String az = resolveOptional(props, "AvailabilityZone", engine);
+        String mapPublicIpOnLaunch = resolveOptional(props, "MapPublicIpOnLaunch", engine);
         var subnet = ec2Service.createSubnet(region, vpcId, cidr, az);
+        if (mapPublicIpOnLaunch != null) {
+            ec2Service.modifySubnetAttribute(region, subnet.getSubnetId(), "mapPublicIpOnLaunch", mapPublicIpOnLaunch);
+        }
         r.setPhysicalId(subnet.getSubnetId());
         r.getAttributes().put("SubnetId", subnet.getSubnetId());
         r.getAttributes().put("VpcId", subnet.getVpcId());


Test patch:
diff --git a/src/test/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationEc2IntegrationTest.java b/src/test/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationEc2IntegrationTest.java
index dbf1479775..265a47db59 100644
--- a/src/test/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationEc2IntegrationTest.java
+++ b/src/test/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationEc2IntegrationTest.java
@@ -10,6 +10,7 @@
 
 import static io.restassured.RestAssured.given;
 import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -48,7 +49,8 @@ void vpcStackSubnetsAreUsableByDescribeSubnetsAndElbv2() {
                       "Properties": {
                         "VpcId": {"Ref": "Vpc"},
                         "CidrBlock": "10.0.1.0/24",
-                        "AvailabilityZone": "ap-southeast-2a"
+                        "AvailabilityZone": "ap-southeast-2a",
+                        "MapPublicIpOnLaunch": true
                       }
                     },
                     "Subnet2": {
@@ -107,6 +109,8 @@ void vpcStackSubnetsAreUsableByDescribeSubnetsAndElbv2() {
             .post("/")
         .then()
             .statusCode(200)
+            .body("DescribeSubnetsResponse.subnetSet.item.find { it.availabilityZone == 'ap-southeast-2a' }.mapPublicIpOnLaunch",
+                    equalTo("true"))
             .extract().asString();
 
         for (String subnetId : exportedSubnetIds) {