{"record":{"id":"45db4bce046c8205","repo":"floci-io/floci","slug":"streamingdistributionnotdisabled","errorCode":"StreamingDistributionNotDisabled","errorMessage":"The streaming distribution you are trying to delete has not been disabled.","messagePattern":"The streaming distribution you are trying to delete has not been disabled\\.","errorType":"exception","errorClass":"AwsException","httpStatus":409,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/cloudfront/CloudFrontService.java","lineNumber":1339,"sourceCode":"        }\n        updated.setId(id);\n        updated.setArn(existing.getArn());\n        updated.setDomainName(existing.getDomainName());\n        updated.setStatus(\"Deployed\");\n        updated.setLastModifiedTime(Instant.now());\n        updated.setEtag(UUID.randomUUID().toString());\n        streamingDistStore.put(id, updated);\n        return updated;\n    }\n\n    public synchronized void deleteStreamingDistribution(String id, String ifMatch) {\n        StreamingDistribution existing = getStreamingDistribution(id);\n        if (!existing.getEtag().equals(ifMatch)) {\n            throw new AwsException(\"InvalidIfMatchVersion\",\n                    \"The If-Match version is missing or not valid for the resource.\", 400);\n        }\n        if (existing.isEnabled()) {\n            throw new AwsException(\"StreamingDistributionNotDisabled\",\n                    \"The streaming distribution you are trying to delete has not been disabled.\", 409);\n        }\n        streamingDistStore.delete(id);\n    }\n\n    // ── Field-Level Encryption Configs ────────────────────────────────────────\n\n    public synchronized FieldLevelEncryptionConfig createFieldLevelEncryptionConfig(\n            FieldLevelEncryptionConfig cfg) {\n        cfg.setId(UUID.randomUUID().toString());\n        cfg.setLastModifiedTime(Instant.now());\n        cfg.setEtag(UUID.randomUUID().toString());\n        fleConfigStore.put(cfg.getId(), cfg);\n        return cfg;\n    }\n\n    public FieldLevelEncryptionConfig getFieldLevelEncryptionConfig(String id) {\n        return fleConfigStore.get(id).orElseThrow(() ->","sourceCodeStart":1321,"sourceCodeEnd":1357,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/cloudfront/CloudFrontService.java#L1321-L1357","documentation":"A streaming distribution must be disabled before it can be deleted; Floci checks existing.isEnabled() in deleteStreamingDistribution and throws StreamingDistributionNotDisabled (HTTP 409 Conflict) when the distribution is still enabled. The ETag check runs first, so a valid If-Match is still required. AWS behaves identically, and disabling is itself an update that requires the current ETag and takes the distribution out of deployment.","triggerScenarios":"DeleteStreamingDistribution on a distribution whose StreamingDistributionConfig.Enabled is true. Typical in teardown scripts that skip the disable step, or that disable and delete so fast the disable update was never actually sent.","commonSituations":"CI/CD teardown pipelines deleting distributions in one step; test cleanup between runs; forgetting that disable is a separate UpdateStreamingDistribution call with its own If-Match.","solutions":["First disable: GetStreamingDistributionConfig, then UpdateStreamingDistribution with Enabled=false and the current ETag","Then GetStreamingDistribution again for the new ETag and call DeleteStreamingDistribution with it","In cleanup code, catch StreamingDistributionNotDisabled and run the disable-then-delete sequence"],"exampleFix":"// before\ncloudFrontClient.deleteStreamingDistribution(r -> r.id(distId).ifMatch(etag));\n\n// after\nGetStreamingDistributionConfigResponse cfg =\n    cloudFrontClient.getStreamingDistributionConfig(r -> r.id(distId));\ncloudFrontClient.updateStreamingDistribution(r -> r\n    .id(distId)\n    .ifMatch(cfg.eTag())\n    .streamingDistributionConfig(cfg.streamingDistributionConfig().toBuilder()\n        .enabled(false)\n        .build()));\nString newEtag = cloudFrontClient.getStreamingDistributionConfig(r -> r.id(distId)).eTag();\ncloudFrontClient.deleteStreamingDistribution(r -> r.id(distId).ifMatch(newEtag));","handlingStrategy":"try-catch","validationCode":"GetStreamingDistributionResponse dist = cloudFrontClient.getStreamingDistribution(r -> r.id(distId));\nif (dist.streamingDistribution().enabled()) {\n    // must disable before delete\n}","typeGuard":null,"tryCatchPattern":"try {\n    cloudFrontClient.deleteStreamingDistribution(r -> r.id(distId).ifMatch(etag));\n} catch (CloudFrontException e) {\n    if (\"StreamingDistributionNotDisabled\".equals(e.awsErrorDetails().errorCode())) {\n        var cfg = cloudFrontClient.getStreamingDistributionConfig(r -> r.id(distId));\n        cloudFrontClient.updateStreamingDistribution(r -> r.id(distId)\n            .ifMatch(cfg.eTag())\n            .streamingDistributionConfig(cfg.streamingDistributionConfig().toBuilder()\n                .enabled(false).build()));\n        String newEtag = cloudFrontClient.getStreamingDistributionConfig(r -> r.id(distId)).eTag();\n        cloudFrontClient.deleteStreamingDistribution(r -> r.id(distId).ifMatch(newEtag));\n    } else { throw e; }\n}","preventionTips":["Standard teardown order: disable (update) → re-read ETag → delete","Remember each update rotates the ETag, so re-read after disabling"],"tags":["cloudfront","streaming-distribution","delete","lifecycle"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}