{"record":{"id":"69cebfecfa9ddc1d","repo":"floci-io/floci","slug":"validationerror-69cebf","errorCode":"ValidationError","errorMessage":"Property VpcId is required for AWS::EC2::VPCGatewayAttachment","messagePattern":"Property VpcId is required for AWS::EC2::VPCGatewayAttachment","errorType":"validation","errorClass":"AwsException","httpStatus":400,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/cloudformation/provisioners/Ec2VpcGatewayAttachmentCfnProvisioner.java","lineNumber":47,"sourceCode":"    private final Ec2Service ec2Service;\n\n    @Inject\n    public Ec2VpcGatewayAttachmentCfnProvisioner(Ec2Service ec2Service) {\n        this.ec2Service = ec2Service;\n    }\n\n    @Override\n    public Set<String> resourceTypes() {\n        return Set.of(\"AWS::EC2::VPCGatewayAttachment\");\n    }\n\n    @Override\n    public void provision(StackResource r, JsonNode props, ProvisionContext ctx) {\n        String vpcId = ctx.resolveOptional(props, \"VpcId\");\n        if (vpcId == null || vpcId.isBlank()) {\n            // Without this the attachment lands with a null VpcId, which both shows up in\n            // DescribeInternetGateways and makes every later detach on that gateway throw.\n            throw new AwsException(\"ValidationError\",\n                    \"Property VpcId is required for AWS::EC2::VPCGatewayAttachment\", 400);\n        }\n\n        String igwId = ctx.resolveOptional(props, \"InternetGatewayId\");\n        String vgwId = ctx.resolveOptional(props, \"VpnGatewayId\");\n        boolean hasIgw = igwId != null && !igwId.isBlank();\n        boolean hasVgw = vgwId != null && !vgwId.isBlank();\n        if (hasIgw == hasVgw) {\n            throw new AwsException(\"ValidationError\",\n                    \"AWS::EC2::VPCGatewayAttachment requires exactly one of \"\n                    + \"InternetGatewayId or VpnGatewayId\", 400);\n        }\n        if (hasVgw) {\n            // There is no VPN gateway model to attach to and nothing DescribeVpnGateways\n            // could report afterwards, so recording a physical id would report\n            // CREATE_COMPLETE for an attachment that does not exist. Fail instead.\n            throw new AwsException(\"ValidationError\",\n                    \"AWS::EC2::VPCGatewayAttachment with VpnGatewayId is not supported: \"","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/cloudformation/provisioners/Ec2VpcGatewayAttachmentCfnProvisioner.java#L29-L65","documentation":"ValidationError from the AWS::EC2::VPCGatewayAttachment provisioner when VpcId resolves to null/blank. Without the guard the attachment would be recorded with a null VpcId, which leaks into DescribeInternetGateways output and makes every later DetachInternetGateway on that gateway throw.","triggerScenarios":"A template declares AWS::EC2::VPCGatewayAttachment without a VpcId property, or with a VpcId that resolves to empty — e.g. !Ref on a parameter that was not passed, or !Sub on an unresolved import.","commonSituations":"Templates copied from AWS samples where VpcId comes from a parameter with no default and the emulator deploy omitted it; Fn::ImportValue of an export that does not exist (which resolves empty here rather than failing at resolution).","solutions":["Add an explicit VpcId to the resource: VpcId: !Ref Vpc (or the actual vpc-... id).","If it references a parameter, ensure the deploy passes it: --parameters ParameterKey=VpcId,ParameterValue=vpc-....","If it uses Fn::ImportValue, verify the export exists (ListExports) before deploying."],"exampleFix":"# before\nResources:\n  Attach:\n    Type: AWS::EC2::VPCGatewayAttachment\n    Properties:\n      InternetGatewayId: !Ref Igw   # VpcId missing\n\n# after\nResources:\n  Attach:\n    Type: AWS::EC2::VPCGatewayAttachment\n    Properties:\n      VpcId: !Ref Vpc\n      InternetGatewayId: !Ref Igw","handlingStrategy":"validation","validationCode":"// Client-side template check before deploy\nObject attach = templateAt(\"Resources/AttachGw/Properties\");\nif (attach == null || isBlank(((Map<?,?>) attach).get(\"VpcId\"))) {\n    throw new IllegalArgumentException(\"VPCGatewayAttachment requires VpcId\");\n}","typeGuard":null,"tryCatchPattern":"catch AwsException/ValidationError from CreateStack; the stack event for the attachment resource carries the reason — fix the template, do not retry.","preventionTips":["Run cfn-lint before deploying; required-property gaps are caught statically","Pass every parameter a template declares, or give parameters defaults","Prefer !Ref to a real AWS::EC2::VPC resource over string interpolation"],"tags":["cloudformation","ec2","vpc","validation"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}