From 1f6cbbd5233a4818d03a65ebacb8fe51b99d6bc4 Mon Sep 17 00:00:00 2001 From: Bruno Lopes Date: Fri, 24 Jul 2026 13:41:46 -0300 Subject: [PATCH 1/2] Add ack-resource-adoption-from-iac custom transformation Generates ACK adoption manifests (adopt-or-create + deletion-policy retain) from CloudFormation, Terraform, and Pulumi code, translating IaC modules and nested stacks into kro ResourceGraphDefinitions. Includes BENCHMARKS.md with end-to-end test results, Troubleshooting, and full reference set. --- .../BENCHMARKS.md | 117 ++++++++ .../ack-resource-adoption-from-iac/README.md | 215 ++++++++++++++ .../ack-resource-adoption-from-iac/SKILL.md | 233 +++++++++++++++ .../references/adoption-fields-ref.md | 78 +++++ .../references/examples-iac-to-ack.md | 275 ++++++++++++++++++ .../references/iac-to-ack-mapping.md | 60 ++++ .../references/kro-patterns.md | 112 +++++++ 7 files changed, 1090 insertions(+) create mode 100644 community-sourced-transformations/ack-resource-adoption-from-iac/BENCHMARKS.md create mode 100644 community-sourced-transformations/ack-resource-adoption-from-iac/README.md create mode 100644 community-sourced-transformations/ack-resource-adoption-from-iac/SKILL.md create mode 100644 community-sourced-transformations/ack-resource-adoption-from-iac/references/adoption-fields-ref.md create mode 100644 community-sourced-transformations/ack-resource-adoption-from-iac/references/examples-iac-to-ack.md create mode 100644 community-sourced-transformations/ack-resource-adoption-from-iac/references/iac-to-ack-mapping.md create mode 100644 community-sourced-transformations/ack-resource-adoption-from-iac/references/kro-patterns.md diff --git a/community-sourced-transformations/ack-resource-adoption-from-iac/BENCHMARKS.md b/community-sourced-transformations/ack-resource-adoption-from-iac/BENCHMARKS.md new file mode 100644 index 0000000..d71a484 --- /dev/null +++ b/community-sourced-transformations/ack-resource-adoption-from-iac/BENCHMARKS.md @@ -0,0 +1,117 @@ +# Benchmark Results - ACK Resource Adoption from IaC + +## Executive Summary + +| Metric | Result | +|--------|--------| +| Repositories tested | 1 (mixed Terraform: flat resources + module + unsupported type) | +| Transformation success rate | 100% (1/1) | +| Resources inventoried | 7 Terraform resources + 1 module instantiation | +| ACK manifests generated | 3 flat (S3, DynamoDB, IAM Role) | +| kro outputs generated | 1 ResourceGraphDefinition + 1 instance CR (queue-with-dlq module) | +| Safety annotation audit | 5/5 ACK manifests (flat + inside RGD) carry `adopt-or-create` + `deletion-policy: retain` (100%) | +| IaC syntax remnants in output | 0 (`!Ref`, `${var.`, `${module.` - none found) | +| Source IaC modified | 0 files (byte-identical to baseline) | +| Unsupported resources flagged | 1/1 (CloudWatch Log Group - report-only with roadmap guidance) | +| Resource folding | 1/1 (`aws_iam_role_policy_attachment` folded into the Role spec `policies` field) | +| `ADOPTION_REPORT.md` generated | Yes (inventory, skips, prerequisites, apply order) | +| Agent minutes | ~42.4 | +| Estimated cost | ~$1.48 (at $0.035/agent-minute) | + +### Methodology + +The test repository was seeded with a **known mix of adoption scenarios**, committed to git as a baseline, then transformed via: + +```bash +atx custom def exec -n ack-resource-adoption-from-iac -p -x -t \ + --configuration 'additionalPlanContext=Adopt all resources.' +``` + +Results were verified against the git diff (what was generated), the exit criteria in [SKILL.md](SKILL.md), a programmatic annotation audit (YAML parse of every output document, including templates inside the RGD), and grep-based scans for IaC syntax remnants. + +### Pricing Note + +Agent minutes = active agent work (planning, reasoning, code generation). Client-side operations (file reads, validation) are not billed. Price: **$0.035 / agent minute**. + +--- + +## Test Repository Composition + +| Input (Terraform) | Scenario Exercised | Expected Output | +|---|---|---| +| `aws_s3_bucket.reports` | Flat resource, identifier in spec | Plain ACK `Bucket` manifest | +| `aws_dynamodb_table.orders` (key schema + tags) | Flat resource with structural spec mapping | Plain ACK `Table` manifest with keySchema/attributeDefinitions | +| `aws_iam_role.app` + `aws_iam_role_policy_attachment.app_s3` | Split-resource folding | ONE ACK `Role` manifest with the attachment folded into `spec.policies` | +| `module "order_queue"` (queue-with-dlq: 2 SQS queues wired via redrive policy) | Composition unit | kro `ResourceGraphDefinition` + instance CR, internal refs as CEL | +| `aws_cloudwatch_log_group.app` | Unsupported type (no GA ACK controller) | NO manifest - flagged in report with guidance | + +--- + +## Results Detail + +### Generated Files + +```text +ack-adoption/ +├── s3-bucket-reports.yaml # Bucket, adopt-or-create + retain +├── dynamodb-table-orders.yaml # Table, keySchema/attributes/tags mapped +├── iam-role-app.yaml # Role with folded policy attachment +├── rgd-queue-with-dlq.yaml # kro RGD: schema fields from module variables, +│ # 2 Queue templates with adoption annotations, +│ # DLQ wiring via CEL (${dlq.status...}) +└── instance-orders-queue.yaml # Instance CR with the module's argument values +ADOPTION_REPORT.md +``` + +### Pass/Fail Checks + +```text +✅ Annotation audit: 5/5 ACK resource documents (3 flat + 2 RGD templates) carry BOTH + services.k8s.aws/adoption-policy: "adopt-or-create" AND + services.k8s.aws/deletion-policy: "retain" +✅ Module inputs became RGD schema fields (queueName: string, + maxReceiveCount: integer | default=5) +✅ Terraform interpolation replaced by CEL: ${schema.spec.queueName}, + ${dlq.status.ackResourceMetadata.arn} - zero IaC syntax remnants in any output +✅ aws_iam_role_policy_attachment correctly folded into Role spec.policies + (no orphan manifest generated) +✅ aws_cloudwatch_log_group NOT converted - listed in ADOPTION_REPORT.md + "Skipped Resources" with keep-in-Terraform guidance and ACK roadmap link +✅ Source IaC byte-identical to the pre-run git baseline (exit criterion 7) +✅ All generated YAML parses cleanly (7 documents across 5 files) +✅ ADOPTION_REPORT.md contains: inventory table, skipped resources with reasons, + prerequisite checklist, dependency-sorted apply order +``` + +### Exit Criteria Compliance (per SKILL.md) + +| # | Exit Criterion | Result | +|---|---|---| +| 1 | One manifest/RGD slot per supported resource, no duplicates or silent drops | ✅ 6 mapped -> 3 flat + 2 in RGD + 1 folded | +| 2 | 100% of manifests carry adopt-or-create + retain | ✅ 5/5 | +| 3 | adoption-fields resolved or TODO(discovery) + report | ✅ (identifiers in spec for this set) | +| 4 | RGDs only for genuine composition units, with instance CRs, CEL refs | ✅ 1 RGD + 1 instance | +| 5 | No IaC syntax remnants in generated files | ✅ 0 found | +| 6 | Generated YAML parses cleanly | ✅ 7/7 documents | +| 7 | Source IaC byte-identical | ✅ | +| 8 | ADOPTION_REPORT.md complete | ✅ | + +--- + +## Validation Commands Used + +```bash +# Annotation audit (every document, including RGD-embedded templates) +python3 - <<'EOF' +import yaml, glob +for f in glob.glob('ack-adoption/*.yaml'): + for d in yaml.safe_load_all(open(f)): + ... # assert adoption-policy + deletion-policy on every ACK resource +EOF + +# IaC syntax remnant scan +grep -rE '\$\{var\.|\$\{module\.|!Ref|!GetAtt' ack-adoption/ + +# Source integrity +git diff HEAD -- main.tf modules/ +``` diff --git a/community-sourced-transformations/ack-resource-adoption-from-iac/README.md b/community-sourced-transformations/ack-resource-adoption-from-iac/README.md new file mode 100644 index 0000000..a1f770b --- /dev/null +++ b/community-sourced-transformations/ack-resource-adoption-from-iac/README.md @@ -0,0 +1,215 @@ +# ACK Resource Adoption from IaC + +Analyzes existing infrastructure-as-code (CloudFormation, Terraform, Pulumi) and generates ACK (AWS Controllers for Kubernetes) adoption manifests that bring the deployed AWS resources under Kubernetes/GitOps management - **without recreating, modifying, or deleting anything**. IaC modules and nested stacks are translated into kro `ResourceGraphDefinition`s, preserving reusable composition as first-class Kubernetes APIs. + +**Supports CloudFormation · Terraform · Pulumi → ACK manifests · kro ResourceGraphDefinitions** + +## Table of Contents + +- [Overview](#overview) +- [The Problem](#the-problem) +- [What This Transformation Does](#what-this-transformation-does) +- [Transformation Architecture](#transformation-architecture) +- [Why kro for Modules and Stacks](#why-kro-for-modules-and-stacks) +- [Getting Started](#getting-started) +- [Getting Started with AWS Transform Custom](#getting-started-with-aws-transform-custom) +- [Benchmarks](#benchmarks) +- [Troubleshooting](#troubleshooting) +- [Known Limitations](#known-limitations) +- [Documentation & References](#documentation--references) +- [Repository Structure](#repository-structure) + +## Overview + +Teams adopting GitOps on EKS often have years of AWS resources managed by CloudFormation, Terraform, or Pulumi. ACK's `ResourceAdoption` feature gate (with the `adopt-or-create` policy) makes it possible to bring those resources under Kubernetes control by reading their current state directly from AWS - no recreation, no downtime, no import ceremony. + +The hard part is generating correct adoption manifests at scale: mapping every IaC resource type to its ACK Kind, knowing which resources need the `adoption-fields` annotation (identifier in status vs spec), carrying the declared configuration into the spec with fidelity, and never, ever forgetting `deletion-policy: retain`. This transformation automates exactly that. + +## The Problem + +Migrating IaC-managed resources to ACK by hand means: + +- **Manifest-by-manifest translation**: each `aws_*` / `AWS::*` resource type has a different ACK apiVersion, Kind, and spec shape. +- **The adoption-fields trap**: resources whose identifier lives in `status` (VPC, SQS, SNS, KMS, CloudFront, and others) silently fail adoption without the `services.k8s.aws/adoption-fields` annotation. +- **The deletion-policy trap**: a manifest without `deletion-policy: retain` means deleting the Kubernetes CR **deletes the real AWS resource**. One missed annotation can destroy production infrastructure. +- **Spec drift risk**: `adopt-or-create` reconciles the declared spec against AWS after adoption - a spec that does not match the deployed resource triggers unintended AWS updates. +- **Composition is lost**: Terraform modules and CloudFormation nested stacks encode reusable architecture. Flattening them into loose manifests throws that structure away. + +## What This Transformation Does + +1. **Scan** the repository for CloudFormation templates, Terraform files (including `.tfstate` when present), and Pulumi programs. +2. **Inventory** every AWS resource declaration, resolving identifiers from literals, variable defaults, tfvars, and state files - and marking what can only be resolved against the live account. +3. **Map** each resource to its ACK apiVersion/Kind, determining per-Kind `adoption-fields` requirements. +4. **Generate**: + - One ACK adoption manifest per flat resource - always with `adoption-policy: adopt-or-create` + `deletion-policy: retain` + - One kro `ResourceGraphDefinition` + instance CRs per Terraform module / CloudFormation nested stack / Pulumi component + - `TODO(discovery)` comments with exact AWS CLI commands for identifiers that require runtime resolution +5. **Validate** every generated manifest (YAML parse + annotation audit + `kubectl apply --dry-run=client` when CRDs are available). +6. **Report** - generate `ADOPTION_REPORT.md` with the full inventory, skipped resources with reasons, prerequisite checklist, dependency-sorted apply order, and post-adoption IaC decommission guidance. + +The source IaC is never modified - it remains the system of record until the customer decides to decommission it, following the guidance in the report. + +## Transformation Architecture + +```text +Input: Customer repo (CFN / Terraform / Pulumi) + options (via additionalPlanContext) + | + +-- 0. Detect -> identify IaC flavor(s) present + +-- 1. Inventory -> parse resources, resolve identifiers, find composition units + +-- 2. Map -> IaC type -> ACK Kind + adoption-fields requirements + +-- 3. Generate -> ACK manifests (flat) + kro RGDs & instances (modules/stacks) + +-- 4. Validate -> YAML parse, annotation audit, dry-run where possible + +-- 5. Report -> ADOPTION_REPORT.md with apply order and decommission guidance +``` + +### Key Design Decisions + +1. **Read-only on both sides.** The IaC is never edited; no AWS API or cluster is ever touched. Output is manifests + report, applied by the customer in the documented order. +2. **retain is non-negotiable.** Every generated manifest carries `deletion-policy: retain`. The transformation treats a missing retain annotation as a critical defect, not a style issue. +3. **Never guess identifiers.** Unresolvable identifiers produce a `TODO(discovery)` comment with the exact AWS CLI command - a wrong `adoption-fields` value is worse than an explicit gap. +4. **kro only where composition exists.** Single flat resources get plain ACK manifests. RGDs are generated exclusively for genuine module/stack units, exposing module inputs as schema fields. +5. **Unsupported types are flagged, not dropped.** Resources without a GA ACK controller stay in IaC and appear in the report with guidance - silence is not an option. + +## Why kro for Modules and Stacks + +A Terraform module or CloudFormation nested stack is an architectural statement: "these resources deploy and evolve as a unit, parameterized by these inputs." [kro](https://kro.run) preserves that statement in Kubernetes: + +| IaC Concept | kro Equivalent | +|---|---| +| Module / nested stack definition | `ResourceGraphDefinition` (RGD) | +| Module inputs / stack Parameters | RGD `schema.spec` fields (typed, with defaults) | +| Module instantiation | Instance CR of the RGD-defined Kind | +| `!Ref` / `!GetAtt` / TF interpolation | CEL expressions (`${resource.status.field}`) | +| Module outputs | RGD `schema.status` fields | + +The result: platform teams keep their reusable building blocks, now served as Kubernetes-native APIs, and each ACK resource inside the graph still adopts (not recreates) its existing AWS counterpart. + +## Getting Started + +### Prerequisites + +| Tool | Purpose | +|---|---| +| AWS Transform CLI (`atx`) | Execute the transformation | +| `kubectl` (optional) | Manifest dry-run validation | +| ACK controllers + `ResourceAdoption` feature gate (on the target cluster, at apply time) | Actual adoption - documented in the report's prerequisite checklist | +| kro controller (on the target cluster, at apply time) | Only if RGDs were generated | + +> The transformation itself needs none of the cluster-side tools - they matter when the customer applies the generated manifests. The `ResourceAdoption` feature gate is enabled by default with EKS Capabilities installs and disabled by default on self-managed Helm installs. + +### Getting Started with AWS Transform Custom + +To set up the AWS Transform CLI, configure authentication, and run your first transformation, see the [AWS Transform Custom Getting Started Guide](https://docs.aws.amazon.com/transform/latest/userguide/custom-get-started.html). + +### Cloning the Repo and Publishing the Transformation + +```bash +git clone https://github.com/aws-samples/aws-transform-custom-samples +cd aws-transform-custom-samples/community-sourced-transformations + +atx custom def publish -n ack-resource-adoption-from-iac \ + --sd ack-resource-adoption-from-iac \ + --description "Generates ACK adoption manifests and kro ResourceGraphDefinitions from CloudFormation, Terraform, and Pulumi code" +``` + +### Running the Transformation + +```bash +# Full run: adopt everything, kro for modules/stacks +atx custom def exec \ + -n ack-resource-adoption-from-iac \ + -p /path/to/customer-iac-repo \ + -x -t \ + --configuration 'additionalPlanContext=Adopt all resources.' + +# Scope to a module, flat manifests only +atx custom def exec \ + -n ack-resource-adoption-from-iac \ + -p /path/to/customer-iac-repo \ + -x -t \ + --configuration 'additionalPlanContext=Adopt only the networking module. Skip kro, flat manifests only.' +``` + +### Expected Output + +```text +ack-adoption/ +├── --.yaml # one ACK adoption manifest per flat resource +├── rgd-.yaml # one kro RGD per module/nested stack +├── instance-.yaml # one instance CR per module instantiation +ADOPTION_REPORT.md # inventory, skips, discovery items, prerequisites, + # apply order, IaC decommission guidance +``` + +### Applying the Output (customer-side, after review) + +```bash +# 1. Resolve every TODO(discovery) item listed in ADOPTION_REPORT.md +# 2. Follow the report's dependency-sorted apply order, e.g.: +kubectl apply -f ack-adoption/rgd-messaging.yaml # RGDs first (if any) +kubectl apply -f ack-adoption/iam-role-app.yaml # then IAM/KMS +kubectl apply -f ack-adoption/vpc-app-vpc.yaml # then network +kubectl apply -f ack-adoption/dynamodb-table-orders.yaml # then data stores + +# 3. Validate adoption per resource +kubectl describe table orders # expect ACK.Adopted=True, ACK.ResourceSynced=True +``` + +## Benchmarks + +End-to-end test results - repositories tested via `atx custom def exec`, what was detected, what was generated, and what passed/failed - are documented in [BENCHMARKS.md](BENCHMARKS.md). + +## Troubleshooting + +| Symptom | Cause | Fix | +|---|---|---| +| `atx custom def publish` fails with authentication error | AWS Transform CLI not authenticated | Re-run the auth flow from the [Getting Started Guide](https://docs.aws.amazon.com/transform/latest/userguide/custom-get-started.html) | +| Generated manifest has `REPLACE_ME` in `adoption-fields` | Identifier only resolvable against the live account (computed value, runtime `!Ref`) | Run the AWS CLI command in the adjacent `TODO(discovery)` comment and replace the placeholder before applying | +| Applying a manifest creates a NEW resource instead of adopting | Spec identifier does not match the deployed resource (wrong name/ID), so `adopt-or-create` fell through to create | Delete the new CR (retain policy protects AWS), fix the identifier from the discovery command, re-apply | +| CR stuck with `ACK.Recoverable: True` | Controller lacks IAM permissions for the target service, or throttling | Check the IRSA/Pod Identity role against the report's prerequisite checklist | +| CR shows `ACK.Terminal: True` | A spec field is invalid or diverges incompatibly from the deployed resource | Compare the spec with the live resource (`aws describe-*`) and align the field | +| Adoption never happens (CR created, no `ACK.Adopted` condition) | `ResourceAdoption` feature gate disabled (default on self-managed Helm installs) | `helm upgrade ... --set featureGates.ResourceAdoption=true` - or use EKS Capabilities where it is on by default | +| RGD applied but instance CR rejected | kro serves the instance API only after the RGD reaches `state: Active` | `kubectl get rgd ` and wait for Active before applying instances | +| ACK attempts unexpected AWS updates right after adoption | Generated spec diverges from the deployed state (`adopt-or-create` reconciles post-adoption) | Review the diff in the controller logs; align the spec with reality, or switch that resource to `adoption-policy: adopt` + `read-only: "true"` for observation-only | + +## Known Limitations + +| Limitation | Notes | +|---|---| +| Resources without a GA ACK controller | Flagged in the report with guidance, never guessed (e.g., CloudWatch Log Groups, classic ASGs) | +| Computed/runtime identifiers | Cannot be resolved from code alone - emitted as `TODO(discovery)` with the exact CLI command, listed in the report | +| Pulumi dynamic providers and heavy runtime logic | Only statically analyzable `@pulumi/aws` / `pulumi_aws` resource declarations are mapped | +| IaC decommissioning | Documented as manual post-adoption guidance (e.g., `terraform state rm`, CFN `DeletionPolicy: Retain` + stack delete) - never automated | +| Cross-repo module sources | Terraform registry/git module sources are resolved only when vendored in the repo; otherwise the module boundary is reported for manual RGD design | +| kro API stability | kro is under active development (`v1alpha1`) - pin the kro version and validate RGDs against your installed release | + +## Documentation & References + +| File | Description | +|---|---| +| [SKILL.md](SKILL.md) | Complete transformation definition - objective, scope, workflow, and exit criteria | +| [references/iac-to-ack-mapping.md](references/iac-to-ack-mapping.md) | CFN/Terraform/Pulumi -> ACK Kind mapping table, fold rules, identifier resolution order | +| [references/adoption-fields-ref.md](references/adoption-fields-ref.md) | Per-Kind `adoption-fields` requirements with discovery commands | +| [references/kro-patterns.md](references/kro-patterns.md) | Module/nested stack -> ResourceGraphDefinition translation rules and anti-patterns | +| [references/examples-iac-to-ack.md](references/examples-iac-to-ack.md) | 6 worked before/after examples covering all major patterns | +| [BENCHMARKS.md](BENCHMARKS.md) | End-to-end test results with real repositories | + +**External documentation:** + +- [ACK ResourceAdoption feature gate](https://aws-controllers-k8s.github.io/community/docs/user-docs/features/) +- [ACK deletion policy](https://aws-controllers-k8s.github.io/community/docs/user-docs/deletion-policy/) +- [kro documentation](https://kro.run/docs/overview) + +## Repository Structure + +```text +ack-resource-adoption-from-iac/ +├── README.md # This file - overview, getting started, troubleshooting +├── SKILL.md # Transformation definition: objective, scope, workflow, exit criteria +├── BENCHMARKS.md # End-to-end test results with real repositories +└── references/ + ├── iac-to-ack-mapping.md # IaC type -> ACK Kind mapping + fold rules + ├── adoption-fields-ref.md # Per-Kind adoption-fields reference with discovery commands + ├── kro-patterns.md # Module/stack -> kro RGD translation rules + └── examples-iac-to-ack.md # 6 worked before/after examples +``` diff --git a/community-sourced-transformations/ack-resource-adoption-from-iac/SKILL.md b/community-sourced-transformations/ack-resource-adoption-from-iac/SKILL.md new file mode 100644 index 0000000..5184d23 --- /dev/null +++ b/community-sourced-transformations/ack-resource-adoption-from-iac/SKILL.md @@ -0,0 +1,233 @@ +--- +name: ack-resource-adoption-from-iac +description: >- + Analyzes CloudFormation, Terraform, and Pulumi code to generate ACK + (AWS Controllers for Kubernetes) adoption manifests that bring existing + AWS resources under Kubernetes/GitOps management without recreating them. + Uses the ACK ResourceAdoption feature gate with adopt-or-create policy, + and generates kro ResourceGraphDefinitions for reusable modules and + stacked/nested components. Trigger: ACK adoption, adopt-or-create, + migrate IaC to Kubernetes, CloudFormation to ACK, Terraform to ACK, + GitOps migration, kro ResourceGraphDefinition. +--- + +# ACK Resource Adoption from IaC + +## Objective + +Convert existing IaC-managed AWS resources (CloudFormation, Terraform, Pulumi) into ACK adoption manifests so the resources come under Kubernetes/GitOps control **without being recreated, modified, or deleted**. Where the IaC uses reusable modules or nested/stacked structures, generate kro `ResourceGraphDefinition`s that preserve that composition as a first-class Kubernetes API. + +## Scope + +Analyzes: +- CloudFormation templates (`.yaml`, `.yml`, `.json` with `AWSTemplateFormatVersion` or `Resources:` top-level key), including nested stacks (`AWS::CloudFormation::Stack`) +- Terraform files (`.tf`, `.tf.json`), including local and registry modules (`module` blocks) +- Pulumi programs (TypeScript/Python with `@pulumi/aws` or `pulumi_aws` imports) + +Generates: +- One ACK adoption manifest per supported AWS resource, with `adoption-policy: adopt-or-create`, `deletion-policy: retain`, and `adoption-fields` when the identifier lives in status +- kro `ResourceGraphDefinition`s (`kro.run/v1alpha1`) for IaC modules/nested stacks that instantiate more than one resource, exposing module inputs as schema fields +- `ADOPTION_REPORT.md` documenting every mapped resource, every skipped resource (with reason), discovery commands, and the recommended apply order + +Supported ACK services (initial mapping table in `references/iac-to-ack-mapping.md`): EKS, EC2 (VPC, Subnet, SecurityGroup, RouteTable, InternetGateway), S3, DynamoDB, SQS, SNS, RDS, ElastiCache, IAM (Role, Policy), KMS, Lambda, ECR, MSK, OpenSearch, Secrets Manager, SSM, EventBridge, CloudFront, Route53. + +**Non-Goals** (out of scope for this transformation): +1. Executing `kubectl apply` or touching any live cluster or AWS account - this transformation generates manifests and a report; the customer applies them following the documented order. +2. Removing or altering the source IaC - the CloudFormation/Terraform/Pulumi code is read-only input. Decommissioning the old IaC (e.g., `terraform state rm`, CloudFormation stack deletion with retain policies) is documented as a manual post-adoption step in the report, never automated. +3. Resources without a GA ACK controller - flagged in the report with the closest alternative (stay in IaC, or ACK controller roadmap link), never guessed. +4. Installing ACK controllers or kro - prerequisites are documented in the report; installation is the customer's platform decision (EKS Capabilities vs Helm). + +## Constraints + +### Safety (non-negotiable) +- EVERY generated manifest MUST carry `services.k8s.aws/deletion-policy: "retain"`. A missing retain policy can cause real AWS resource deletion when a CR is removed - this is the single most dangerous failure mode of this transformation. +- EVERY generated manifest MUST use `services.k8s.aws/adoption-policy: "adopt-or-create"` so applying it never fails on an existing resource and never duplicates it. +- Never generate a manifest with placeholder/guessed identifiers. If the resource identifier cannot be resolved from the IaC (e.g., a Terraform computed value or a CloudFormation `!Ref` chain that requires runtime state), emit the manifest with an explicit `# TODO(discovery):` comment containing the exact AWS CLI discovery command, and list it in the report's "Requires Discovery" section. +- The source IaC files are never modified. + +### Fidelity +- The generated `spec` must reflect the resource configuration declared in the IaC (adopt-or-create reconciles spec against AWS after adoption - a spec that diverges from reality causes unintended updates). When an IaC attribute has no ACK spec equivalent, document it in the report rather than dropping it silently. +- Preserve resource naming: the Kubernetes object name derives from the IaC logical name (kebab-cased), and the AWS-facing name field keeps the exact deployed value. +- Tags declared in IaC are carried into the ACK spec `tags` field when the controller supports it. + +### kro Generation Rules +- Generate a `ResourceGraphDefinition` ONLY when the IaC has a module/nested-stack instantiated as a unit (a Terraform `module` block, a CloudFormation nested stack, or a Pulumi ComponentResource). One-off flat resources get plain ACK manifests - do not wrap single resources in kro. +- Module input variables become kro `spec.schema.spec` fields with types and defaults mapped from the IaC variable definitions. +- Cross-resource references inside the module (e.g., subnet referencing VPC ID) become kro CEL expressions (`${resource.status.field}`), replacing the IaC-native references (`!Ref`, `!GetAtt`, Terraform interpolation). +- Each kro resource template inside the RGD still carries the adoption + retain annotations - adoption safety applies inside graphs exactly as it does for flat manifests. +- For module instances (multiple instantiations of the same module), generate ONE RGD plus one instance CR per instantiation, with the per-instance values. + +### Reporting +- `ADOPTION_REPORT.md` must contain: inventory table (IaC resource -> ACK Kind -> manifest file -> adoption-fields needed), skipped resources with reasons, discovery commands for unresolved identifiers, prerequisite checklist (controllers, feature gate, IRSA/Pod Identity permissions per service), recommended apply order (dependency-sorted: IAM/KMS -> network -> data stores -> compute), and post-adoption IaC decommission guidance. +- Every generated file must be listed with its source IaC file and line reference. + +## Workflow + +```text +Phase 0: Detect IaC flavor(s) + ├── CloudFormation: AWSTemplateFormatVersion / Resources: top-level key + ├── Terraform: .tf files, module blocks, terraform-provider-aws resources + └── Pulumi: @pulumi/aws (TS) or pulumi_aws (Python) imports + +Phase 1: Inventory + ├── Parse every resource declaration into (type, logical name, properties, references) + ├── Resolve identifiers: literal values, variable defaults, tfvars files + ├── Mark unresolvable identifiers (computed values, runtime refs) for discovery + └── Detect composition units: TF modules, CFN nested stacks, Pulumi components + +Phase 2: Map to ACK + ├── Map each IaC resource type to its ACK apiVersion/Kind + │ (references/iac-to-ack-mapping.md) + ├── Determine adoption-fields requirement per Kind + │ (references/adoption-fields-ref.md) + └── Flag unsupported types (no GA ACK controller) for the report + +Phase 3: Generate manifests + ├── Flat resources -> ACK manifests with adopt-or-create + retain + ├── Composition units -> kro ResourceGraphDefinition + instance CRs + │ (references/kro-patterns.md) + └── Unresolved identifiers -> TODO(discovery) comments + CLI commands + +Phase 4: Validate + ├── YAML syntax validation on every generated manifest + ├── kubectl apply --dry-run=client (if kubectl available; needs CRDs - skip gracefully) + └── Annotation audit: 100% of generated manifests carry retain + adopt-or-create + +Phase 5: Report + └── Generate ADOPTION_REPORT.md (inventory, skips, discovery, prerequisites, + apply order, post-adoption IaC decommission guidance) +``` + +### Configuration + +Options are provided via `additionalPlanContext`: +- `"Adopt all resources."` - full repository scan +- `"Adopt only the networking module."` - scope to a specific module/stack +- `"Skip kro, flat manifests only."` - disable RGD generation +- `"Output directory: ack-adoption/"` - override the default output location (`ack-adoption/` at repo root) + +## Worked Examples + +### Example 1: Terraform S3 bucket -> ACK adoption manifest + +**Input (`storage.tf`):** +```hcl +resource "aws_s3_bucket" "reports" { + bucket = "acme-reports-prod" + tags = { + Team = "analytics" + } +} +``` + +**Output (`ack-adoption/s3-bucket-reports.yaml`):** +```yaml +apiVersion: s3.services.k8s.aws/v1alpha1 +kind: Bucket +metadata: + name: reports + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" +spec: + name: acme-reports-prod + tagging: + tagSet: + - key: Team + value: analytics +``` + +### Example 2: Terraform module -> kro ResourceGraphDefinition + +**Input (`main.tf`):** +```hcl +module "queue_with_dlq" { + source = "./modules/queue-with-dlq" + queue_name = "orders" + max_receive_count = 5 +} +``` + +Where the module declares an `aws_sqs_queue` main queue plus an `aws_sqs_queue` DLQ wired via `redrive_policy`. + +**Output (`ack-adoption/rgd-queue-with-dlq.yaml`):** +```yaml +apiVersion: kro.run/v1alpha1 +kind: ResourceGraphDefinition +metadata: + name: queue-with-dlq +spec: + schema: + apiVersion: v1alpha1 + kind: QueueWithDLQ + spec: + queueName: string + maxReceiveCount: integer | default=5 + resources: + - id: dlq + template: + apiVersion: sqs.services.k8s.aws/v1alpha1 + kind: Queue + metadata: + name: ${schema.spec.queueName}-dlq + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" + spec: + queueName: ${schema.spec.queueName}-dlq + - id: queue + template: + apiVersion: sqs.services.k8s.aws/v1alpha1 + kind: Queue + metadata: + name: ${schema.spec.queueName} + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" + spec: + queueName: ${schema.spec.queueName} + redrivePolicy: '{"deadLetterTargetArn":"${dlq.status.ackResourceMetadata.arn}","maxReceiveCount":${schema.spec.maxReceiveCount}}' +``` + +**Plus the instance CR (`ack-adoption/instance-orders-queue.yaml`):** +```yaml +apiVersion: kro.run/v1alpha1 +kind: QueueWithDLQ +metadata: + name: orders +spec: + queueName: orders + maxReceiveCount: 5 +``` + +Full example set (CloudFormation nested stack -> RGD, VPC with adoption-fields, DynamoDB, IAM Role, unresolved-identifier TODO pattern) is in `references/examples-iac-to-ack.md`. + +## Reference Dispatch + +Load reference files on demand based on what the scan finds: + +| Signal | Reference File | +|---|---| +| Any supported AWS resource type in the IaC | `references/iac-to-ack-mapping.md` | +| A mapped Kind whose identifier lives in status (VPC, SQS, SNS, KMS, CloudFront, Route53, MSK, IAM Policy, EC2 network resources) | `references/adoption-fields-ref.md` | +| Terraform `module` block, CloudFormation `AWS::CloudFormation::Stack`, or Pulumi ComponentResource | `references/kro-patterns.md` | +| Any resource requiring a concrete before/after example | `references/examples-iac-to-ack.md` | + +## Validation / Exit Criteria + +1. Every supported IaC resource has exactly one generated ACK manifest (or one kro RGD slot) - no duplicates, no silent drops. +2. 100% of generated manifests (including templates inside RGDs) carry both `services.k8s.aws/adoption-policy: "adopt-or-create"` and `services.k8s.aws/deletion-policy: "retain"`. +3. Every Kind requiring `adoption-fields` has the annotation populated with a resolved identifier, OR carries a `TODO(discovery)` comment with the exact AWS CLI command and appears in the report's "Requires Discovery" section. +4. kro RGDs are generated only for genuine composition units; every RGD has at least one instance CR; internal references use CEL expressions, not IaC syntax remnants. +5. No generated file contains IaC-native syntax (`!Ref`, `!GetAtt`, `${var.`, `${module.`, Pulumi interpolations). +6. All generated YAML parses cleanly; `kubectl apply --dry-run=client` passes where CRDs are available (skipped gracefully otherwise). +7. Source IaC files are byte-identical to their pre-run state. +8. `ADOPTION_REPORT.md` exists and contains: inventory table, skipped resources with reasons, discovery commands, prerequisite checklist (controller + feature gate + IAM permissions per service), dependency-sorted apply order, and post-adoption IaC decommission guidance. + +## Tips + +- `ResourceAdoption` feature gate is enabled by default when ACK controllers are installed via EKS Capabilities, but disabled by default on self-managed Helm installs - the prerequisite checklist in the report must state both paths explicitly. +- adopt-or-create reconciles the declared spec against AWS after adoption. A spec field that differs from the deployed resource triggers a real AWS update - fidelity between IaC properties and generated spec is a correctness requirement, not cosmetics. +- Terraform state files (`.tfstate`) are the highest-fidelity identifier source when present in the repo - prefer state values over HCL interpolation resolution, but never require state to be present. +- CloudFormation `!Ref` on a resource usually resolves to the physical ID at runtime - when the template alone cannot resolve it, the discovery command in the TODO should query by stack: `aws cloudformation describe-stack-resources --stack-name `. +- An empty or partial spec is safer than a wrong spec for adopt-or-create: prefer the minimal find+create field set and let ACK populate the rest from AWS state after adoption. diff --git a/community-sourced-transformations/ack-resource-adoption-from-iac/references/adoption-fields-ref.md b/community-sourced-transformations/ack-resource-adoption-from-iac/references/adoption-fields-ref.md new file mode 100644 index 0000000..08ad684 --- /dev/null +++ b/community-sourced-transformations/ack-resource-adoption-from-iac/references/adoption-fields-ref.md @@ -0,0 +1,78 @@ +# ACK Adoption Fields - Reference by Service + +Reference table for the `services.k8s.aws/adoption-fields` annotation. + +**Rule:** `adoption-fields` is only needed when the resource identifier lives in **status** (not spec). +If the identifier is in spec (e.g., `spec.name`, `spec.tableName`), the annotation is not required. + +--- + +## Reference table + +| Service | Kind | adoption-fields key | How to discover | Example | +|---------|------|---------------------|-----------------|---------| +| EKS | `Cluster` | Not needed (`spec.name`) | `aws eks list-clusters` | - | +| EC2 | `VPC` | `vpcID` | `aws ec2 describe-vpcs --query 'Vpcs[*].VpcId'` | `{"vpcID": "vpc-0abc123"}` | +| EC2 | `Subnet` | `subnetID` | `aws ec2 describe-subnets --query 'Subnets[*].SubnetId'` | `{"subnetID": "subnet-0abc123"}` | +| EC2 | `SecurityGroup` | `id` | `aws ec2 describe-security-groups --query 'SecurityGroups[*].GroupId'` | `{"id": "sg-0abc123"}` | +| EC2 | `RouteTable` | `routeTableID` | `aws ec2 describe-route-tables --query 'RouteTables[*].RouteTableId'` | `{"routeTableID": "rtb-0abc123"}` | +| EC2 | `InternetGateway` | `internetGatewayID` | `aws ec2 describe-internet-gateways --query 'InternetGateways[*].InternetGatewayId'` | `{"internetGatewayID": "igw-0abc123"}` | +| S3 | `Bucket` | Not needed (`spec.name`) | `aws s3api list-buckets --query 'Buckets[*].Name'` | - | +| DynamoDB | `Table` | Not needed (`spec.tableName`) | `aws dynamodb list-tables` | - | +| SQS | `Queue` | `queueURL` | `aws sqs list-queues` | `{"queueURL": "https://sqs.us-east-1.amazonaws.com/123/my-queue"}` | +| SNS | `Topic` | `arn` | `aws sns list-topics --query 'Topics[*].TopicArn'` | `{"arn": "arn:aws:sns:us-east-1:123:my-topic"}` | +| RDS | `DBInstance` | Not needed (`spec.dbInstanceIdentifier`) | `aws rds describe-db-instances --query 'DBInstances[*].DBInstanceIdentifier'` | - | +| RDS | `DBCluster` | Not needed (`spec.dbClusterIdentifier`) | `aws rds describe-db-clusters --query 'DBClusters[*].DBClusterIdentifier'` | - | +| ElastiCache | `ReplicationGroup` | Not needed (`spec.replicationGroupID`) | `aws elasticache describe-replication-groups --query 'ReplicationGroups[*].ReplicationGroupId'` | - | +| IAM | `Role` | Not needed (`spec.name`) | `aws iam list-roles --query 'Roles[*].RoleName'` | - | +| IAM | `Policy` | `arn` | `aws iam list-policies --scope Local --query 'Policies[*].Arn'` | `{"arn": "arn:aws:iam::123:policy/my-policy"}` | +| KMS | `Key` | `keyID` | `aws kms list-keys --query 'Keys[*].KeyId'` | `{"keyID": "mrk-abc123"}` | +| Lambda | `Function` | Not needed (`spec.functionName`) | `aws lambda list-functions --query 'Functions[*].FunctionName'` | - | +| ECR | `Repository` | Not needed (`spec.repositoryName`) | `aws ecr describe-repositories --query 'repositories[*].repositoryName'` | - | +| MSK | `Cluster` | `clusterARN` | `aws kafka list-clusters --query 'ClusterInfoList[*].ClusterArn'` | `{"clusterARN": "arn:aws:kafka:..."}` | +| OpenSearch | `Domain` | Not needed (`spec.name`) | `aws opensearch list-domain-names --query 'DomainNames[*].DomainName'` | - | +| Secrets Manager | `Secret` | Not needed (`spec.name`) | `aws secretsmanager list-secrets --query 'SecretList[*].Name'` | - | +| SSM | `Parameter` | Not needed (`spec.name`) | `aws ssm describe-parameters --query 'Parameters[*].Name'` | - | +| EventBridge | `EventBus` | Not needed (`spec.name`) | `aws events list-event-buses --query 'EventBuses[*].Name'` | - | +| CloudFront | `Distribution` | `id` | `aws cloudfront list-distributions --query 'DistributionList.Items[*].Id'` | `{"id": "EDFDVBD6EXAMPLE"}` | +| Route53 | `HostedZone` | `id` | `aws route53 list-hosted-zones --query 'HostedZones[*].Id'` | `{"id": "/hostedzone/Z1D633PJN98FT9"}` | + +--- + +## Bulk discovery (multiple resources) + +```bash +# All EKS clusters +aws eks list-clusters --output json | jq -r '.clusters[]' + +# All VPCs with name tag +aws ec2 describe-vpcs \ + --query 'Vpcs[*].{ID:VpcId,CIDR:CidrBlock,Name:Tags[?Key==`Name`].Value|[0]}' \ + --output table + +# All SQS queues +aws sqs list-queues --output json | jq -r '.QueueUrls[]' + +# All S3 buckets +aws s3api list-buckets --query 'Buckets[*].Name' --output text + +# All DynamoDB tables +aws dynamodb list-tables --output json | jq -r '.TableNames[]' + +# All SNS topics +aws sns list-topics --query 'Topics[*].TopicArn' --output text + +# All Lambda functions +aws lambda list-functions \ + --query 'Functions[*].{Name:FunctionName,Runtime:Runtime}' \ + --output table +``` + +--- + +## Notes + +- Always use `--region` or `AWS_PROFILE` on discovery commands to target the correct region +- For multi-region resources, run discovery per region separately +- `adoption-fields` accepts inline JSON - use YAML block literal (`|`) to avoid quoting issues +- Official reference: https://aws-controllers-k8s.github.io/community/docs/user-docs/features/#resourceadoption diff --git a/community-sourced-transformations/ack-resource-adoption-from-iac/references/examples-iac-to-ack.md b/community-sourced-transformations/ack-resource-adoption-from-iac/references/examples-iac-to-ack.md new file mode 100644 index 0000000..c3edcec --- /dev/null +++ b/community-sourced-transformations/ack-resource-adoption-from-iac/references/examples-iac-to-ack.md @@ -0,0 +1,275 @@ +# Worked Examples - IaC to ACK Adoption + +Concrete before/after examples for each major transformation pattern. + +--- + +## Example 1: Terraform DynamoDB table (identifier in spec - no adoption-fields) + +**Before (`database.tf`):** +```hcl +resource "aws_dynamodb_table" "orders" { + name = "orders-prod" + billing_mode = "PAY_PER_REQUEST" + hash_key = "orderId" + + attribute { + name = "orderId" + type = "S" + } + + tags = { + Environment = "production" + } +} +``` + +**After (`ack-adoption/dynamodb-table-orders.yaml`):** +```yaml +# Source: database.tf (resource "aws_dynamodb_table" "orders") +apiVersion: dynamodb.services.k8s.aws/v1alpha1 +kind: Table +metadata: + name: orders + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" +spec: + tableName: orders-prod + billingMode: PAY_PER_REQUEST + keySchema: + - attributeName: orderId + keyType: HASH + attributeDefinitions: + - attributeName: orderId + attributeType: S + tags: + - key: Environment + value: production +``` + +--- + +## Example 2: CloudFormation VPC (identifier in status - adoption-fields REQUIRED, unresolvable from template) + +**Before (`network.template.yaml`):** +```yaml +Resources: + AppVPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: 10.42.0.0/16 + EnableDnsSupport: true + EnableDnsHostnames: true + Tags: + - Key: Name + Value: app-vpc +``` + +**After (`ack-adoption/vpc-app-vpc.yaml`):** +```yaml +# Source: network.template.yaml (AppVPC) +apiVersion: ec2.services.k8s.aws/v1alpha1 +kind: VPC +metadata: + name: app-vpc + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" + # TODO(discovery): the VPC ID is assigned at deploy time and cannot be read from the template. + # Resolve it with: + # aws cloudformation describe-stack-resources --stack-name \ + # --logical-resource-id AppVPC --query 'StackResources[0].PhysicalResourceId' --output text + # Then replace REPLACE_ME below before applying. + services.k8s.aws/adoption-fields: | + {"vpcID": "REPLACE_ME"} +spec: + cidrBlocks: + - "10.42.0.0/16" + enableDNSSupport: true + enableDNSHostnames: true + tags: + - key: Name + value: app-vpc +``` + +> This manifest is also listed in `ADOPTION_REPORT.md` under "Requires Discovery" - it must not be applied until the TODO is resolved. + +--- + +## Example 3: Terraform IAM Role with attachment folding + +**Before (`iam.tf`):** +```hcl +resource "aws_iam_role" "app" { + name = "app-runtime-role" + assume_role_policy = data.aws_iam_policy_document.app_assume.json +} + +resource "aws_iam_role_policy_attachment" "app_s3" { + role = aws_iam_role.app.name + policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" +} +``` + +**After (`ack-adoption/iam-role-app.yaml`)** - the attachment resource folds into the Role spec: +```yaml +# Source: iam.tf (aws_iam_role.app + aws_iam_role_policy_attachment.app_s3) +apiVersion: iam.services.k8s.aws/v1alpha1 +kind: Role +metadata: + name: app + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" +spec: + name: app-runtime-role + policies: + - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess + # TODO(discovery): assume_role_policy uses a data source (computed at plan time). + # Resolve the deployed trust policy with: + # aws iam get-role --role-name app-runtime-role --query 'Role.AssumeRolePolicyDocument' + # and paste it into assumeRolePolicyDocument before applying. +``` + +--- + +## Example 4: CloudFormation nested stack -> kro ResourceGraphDefinition + +**Before (parent `main.template.yaml`):** +```yaml +Resources: + MessagingStack: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: ./messaging.template.yaml + Parameters: + TopicName: order-events + QueueName: order-processor +``` + +**Child (`messaging.template.yaml`):** declares an SNS Topic, an SQS Queue, and a subscription wiring them. + +**After - one RGD (`ack-adoption/rgd-messaging.yaml`):** +```yaml +# Source: messaging.template.yaml (nested stack unit) +apiVersion: kro.run/v1alpha1 +kind: ResourceGraphDefinition +metadata: + name: messaging +spec: + schema: + apiVersion: v1alpha1 + kind: Messaging + spec: + topicName: string + queueName: string + status: + topicARN: ${topic.status.ackResourceMetadata.arn} + resources: + - id: topic + template: + apiVersion: sns.services.k8s.aws/v1alpha1 + kind: Topic + metadata: + name: ${schema.spec.topicName} + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" + # TODO(discovery): SNS adoption requires the topic ARN (status field). + # aws sns list-topics --query "Topics[?contains(TopicArn,'order-events')].TopicArn" + spec: + name: ${schema.spec.topicName} + - id: queue + template: + apiVersion: sqs.services.k8s.aws/v1alpha1 + kind: Queue + metadata: + name: ${schema.spec.queueName} + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" + # TODO(discovery): SQS adoption requires the queue URL (status field). + # aws sqs get-queue-url --queue-name order-processor + spec: + queueName: ${schema.spec.queueName} + - id: subscription + template: + apiVersion: sns.services.k8s.aws/v1alpha1 + kind: Subscription + metadata: + name: ${schema.spec.queueName}-sub + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" + spec: + topicARN: ${topic.status.ackResourceMetadata.arn} + protocol: sqs + endpoint: ${queue.status.ackResourceMetadata.arn} +``` + +**Plus the instance CR (`ack-adoption/instance-messaging.yaml`):** +```yaml +# Source: main.template.yaml (MessagingStack) +apiVersion: kro.run/v1alpha1 +kind: Messaging +metadata: + name: messaging +spec: + topicName: order-events + queueName: order-processor +``` + +Note how `!Ref`/`!GetAtt` wiring in the child template became CEL (`${topic.status.ackResourceMetadata.arn}`), and stack Parameters became schema fields. + +--- + +## Example 5: Terraform split resources folding (S3) + +**Before (`storage.tf`):** +```hcl +resource "aws_s3_bucket" "assets" { + bucket = "acme-assets-prod" +} + +resource "aws_s3_bucket_versioning" "assets" { + bucket = aws_s3_bucket.assets.id + versioning_configuration { + status = "Enabled" + } +} +``` + +**After (`ack-adoption/s3-bucket-assets.yaml`)** - two TF resources, ONE manifest: +```yaml +# Source: storage.tf (aws_s3_bucket.assets + aws_s3_bucket_versioning.assets) +apiVersion: s3.services.k8s.aws/v1alpha1 +kind: Bucket +metadata: + name: assets + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" +spec: + name: acme-assets-prod + versioning: + status: Enabled +``` + +--- + +## Example 6: Unsupported resource (report-only) + +**Before (`observability.tf`):** +```hcl +resource "aws_cloudwatch_log_group" "app" { + name = "/app/prod" + retention_in_days = 30 +} +``` + +**Output:** NO manifest generated. `ADOPTION_REPORT.md` gets: + +| Resource | Reason Skipped | Guidance | +|---|---|---| +| `aws_cloudwatch_log_group.app` (`/app/prod`) | No GA ACK controller for CloudWatch Logs | Keep under Terraform management; track the ACK controller roadmap at https://github.com/orgs/aws-controllers-k8s/projects/1 | diff --git a/community-sourced-transformations/ack-resource-adoption-from-iac/references/iac-to-ack-mapping.md b/community-sourced-transformations/ack-resource-adoption-from-iac/references/iac-to-ack-mapping.md new file mode 100644 index 0000000..b09b95d --- /dev/null +++ b/community-sourced-transformations/ack-resource-adoption-from-iac/references/iac-to-ack-mapping.md @@ -0,0 +1,60 @@ +# IaC Resource Type -> ACK Mapping + +Mapping table from CloudFormation, Terraform, and Pulumi resource types to ACK apiVersion/Kind. +Only resources with a GA or generally usable ACK controller are mapped - anything else is flagged in the report, never guessed. + +## Mapping Table + +| CloudFormation Type | Terraform Type | Pulumi Type (aws.) | ACK apiVersion | ACK Kind | adoption-fields | +|---|---|---|---|---|---| +| `AWS::EKS::Cluster` | `aws_eks_cluster` | `eks.Cluster` | `eks.services.k8s.aws/v1alpha1` | `Cluster` | Not needed (`spec.name`) | +| `AWS::EKS::Nodegroup` | `aws_eks_node_group` | `eks.NodeGroup` | `eks.services.k8s.aws/v1alpha1` | `Nodegroup` | Not needed (`spec.name` + `spec.clusterName`) | +| `AWS::EC2::VPC` | `aws_vpc` | `ec2.Vpc` | `ec2.services.k8s.aws/v1alpha1` | `VPC` | `{"vpcID": "vpc-..."}` | +| `AWS::EC2::Subnet` | `aws_subnet` | `ec2.Subnet` | `ec2.services.k8s.aws/v1alpha1` | `Subnet` | `{"subnetID": "subnet-..."}` | +| `AWS::EC2::SecurityGroup` | `aws_security_group` | `ec2.SecurityGroup` | `ec2.services.k8s.aws/v1alpha1` | `SecurityGroup` | `{"id": "sg-..."}` | +| `AWS::EC2::RouteTable` | `aws_route_table` | `ec2.RouteTable` | `ec2.services.k8s.aws/v1alpha1` | `RouteTable` | `{"routeTableID": "rtb-..."}` | +| `AWS::EC2::InternetGateway` | `aws_internet_gateway` | `ec2.InternetGateway` | `ec2.services.k8s.aws/v1alpha1` | `InternetGateway` | `{"internetGatewayID": "igw-..."}` | +| `AWS::S3::Bucket` | `aws_s3_bucket` | `s3.Bucket` / `s3.BucketV2` | `s3.services.k8s.aws/v1alpha1` | `Bucket` | Not needed (`spec.name`) | +| `AWS::DynamoDB::Table` | `aws_dynamodb_table` | `dynamodb.Table` | `dynamodb.services.k8s.aws/v1alpha1` | `Table` | Not needed (`spec.tableName`) | +| `AWS::SQS::Queue` | `aws_sqs_queue` | `sqs.Queue` | `sqs.services.k8s.aws/v1alpha1` | `Queue` | `{"queueURL": "https://sqs...."}` | +| `AWS::SNS::Topic` | `aws_sns_topic` | `sns.Topic` | `sns.services.k8s.aws/v1alpha1` | `Topic` | `{"arn": "arn:aws:sns:..."}` | +| `AWS::RDS::DBInstance` | `aws_db_instance` | `rds.Instance` | `rds.services.k8s.aws/v1alpha1` | `DBInstance` | Not needed (`spec.dbInstanceIdentifier`) | +| `AWS::RDS::DBCluster` | `aws_rds_cluster` | `rds.Cluster` | `rds.services.k8s.aws/v1alpha1` | `DBCluster` | Not needed (`spec.dbClusterIdentifier`) | +| `AWS::ElastiCache::ReplicationGroup` | `aws_elasticache_replication_group` | `elasticache.ReplicationGroup` | `elasticache.services.k8s.aws/v1alpha1` | `ReplicationGroup` | Not needed (`spec.replicationGroupID`) | +| `AWS::IAM::Role` | `aws_iam_role` | `iam.Role` | `iam.services.k8s.aws/v1alpha1` | `Role` | Not needed (`spec.name`) | +| `AWS::IAM::ManagedPolicy` | `aws_iam_policy` | `iam.Policy` | `iam.services.k8s.aws/v1alpha1` | `Policy` | `{"arn": "arn:aws:iam::..."}` | +| `AWS::KMS::Key` | `aws_kms_key` | `kms.Key` | `kms.services.k8s.aws/v1alpha1` | `Key` | `{"keyID": "..."}` | +| `AWS::Lambda::Function` | `aws_lambda_function` | `lambda.Function` | `lambda.services.k8s.aws/v1alpha1` | `Function` | Not needed (`spec.functionName`) | +| `AWS::ECR::Repository` | `aws_ecr_repository` | `ecr.Repository` | `ecr.services.k8s.aws/v1alpha1` | `Repository` | Not needed (`spec.repositoryName`) | +| `AWS::MSK::Cluster` | `aws_msk_cluster` | `msk.Cluster` | `kafka.services.k8s.aws/v1alpha1` | `Cluster` | `{"clusterARN": "arn:aws:kafka:..."}` | +| `AWS::OpenSearchService::Domain` | `aws_opensearch_domain` | `opensearch.Domain` | `opensearchservice.services.k8s.aws/v1alpha1` | `Domain` | Not needed (`spec.name`) | +| `AWS::SecretsManager::Secret` | `aws_secretsmanager_secret` | `secretsmanager.Secret` | `secretsmanager.services.k8s.aws/v1alpha1` | `Secret` | Not needed (`spec.name`) | +| `AWS::SSM::Parameter` | `aws_ssm_parameter` | `ssm.Parameter` | `ssm.services.k8s.aws/v1alpha1` | `Parameter` | Not needed (`spec.name`) | +| `AWS::Events::EventBus` | `aws_cloudwatch_event_bus` | `cloudwatch.EventBus` | `eventbridge.services.k8s.aws/v1alpha1` | `EventBus` | Not needed (`spec.name`) | +| `AWS::CloudFront::Distribution` | `aws_cloudfront_distribution` | `cloudfront.Distribution` | `cloudfront.services.k8s.aws/v1alpha1` | `Distribution` | `{"id": "E..."}` | +| `AWS::Route53::HostedZone` | `aws_route53_zone` | `route53.Zone` | `route53.services.k8s.aws/v1alpha1` | `HostedZone` | `{"id": "/hostedzone/Z..."}` | + +## Commonly Found but NOT Mapped (flag in report) + +| IaC Type | Why | Report Guidance | +|---|---|---| +| `aws_iam_role_policy_attachment` / `AWS::IAM::Policy` (inline) | Attachment is expressed inside the ACK Role spec (`policies` field), not a standalone Kind | Fold into the parent `Role` manifest's `spec.policies` | +| `aws_s3_bucket_policy`, `aws_s3_bucket_versioning`, etc. (TF split resources) | Terraform splits bucket sub-configs into separate resources; ACK `Bucket` holds them in one spec | Merge into the parent `Bucket` spec fields (`policy`, `versioning`) | +| `aws_security_group_rule` | Standalone rule resources fold into the parent SecurityGroup spec | Merge into parent `SecurityGroup` `spec.ingressRules`/`egressRules` | +| `AWS::CloudFormation::Stack` (nested stack) | Not a resource - a composition unit | Becomes a kro ResourceGraphDefinition (see `kro-patterns.md`) | +| `aws_autoscaling_group`, `aws_launch_template` | No GA ACK controller for classic ASG/LT standalone management | Keep in IaC; note EKS Nodegroup/Karpenter as the K8s-native alternative | +| `aws_cloudwatch_log_group` | CloudWatch Logs ACK controller not GA | Keep in IaC; monitor the ACK roadmap | +| Provider-specific/meta resources (`aws_caller_identity`, `random_*`, `null_resource`, data sources) | Not AWS-managed resources | Skip silently from manifests, list under "Not applicable" in report | + +## Terraform Identifier Resolution Order + +1. `.tfstate` file present in repo -> use `attributes` values (highest fidelity) +2. Literal values in HCL (`bucket = "acme-reports-prod"`) +3. Variable with `default` in `variables.tf` or value in `*.tfvars` +4. Anything else (computed, `depends_on` chains, remote state) -> `TODO(discovery)` + CLI command + +## CloudFormation Identifier Resolution Order + +1. Literal property values in the template +2. `Parameters` with `Default` +3. `!Ref`/`!GetAtt`/`!Sub` chains that cannot be statically resolved -> `TODO(discovery)` with `aws cloudformation describe-stack-resources --stack-name ` diff --git a/community-sourced-transformations/ack-resource-adoption-from-iac/references/kro-patterns.md b/community-sourced-transformations/ack-resource-adoption-from-iac/references/kro-patterns.md new file mode 100644 index 0000000..c4b1285 --- /dev/null +++ b/community-sourced-transformations/ack-resource-adoption-from-iac/references/kro-patterns.md @@ -0,0 +1,112 @@ +# kro Patterns for IaC Composition Units + +How to translate IaC modules and nested stacks into kro `ResourceGraphDefinition`s (RGDs). +kro API: `kro.run/v1alpha1`, kind `ResourceGraphDefinition` (short name `rgd`, cluster-scoped). + +> kro is an open-source project under active development (v1alpha1 API). Pin the kro version in the prerequisite checklist and validate RGDs against the installed version. + +## When to Generate an RGD + +| IaC Construct | kro Output | +|---|---| +| Terraform `module` block | 1 RGD (from the module source) + 1 instance CR per `module` block instantiation | +| CloudFormation `AWS::CloudFormation::Stack` (nested stack) | 1 RGD (from the child template) + 1 instance CR per nested stack reference | +| Pulumi `ComponentResource` subclass | 1 RGD (from the component class) + 1 instance CR per instantiation | +| Flat resources (no composition) | Plain ACK manifests - NEVER wrap single resources in kro | + +## Translation Rules + +### 1. Module inputs -> RGD schema + +| IaC | kro schema field | +|---|---| +| TF `variable "queue_name" { type = string }` | `queueName: string` | +| TF `variable "max_receive_count" { type = number, default = 5 }` | `maxReceiveCount: integer \| default=5` | +| TF `variable "enabled" { type = bool, default = true }` | `enabled: boolean \| default=true` | +| CFN `Parameters: QueueName: { Type: String }` | `queueName: string` | +| CFN `Parameters: MaxReceive: { Type: Number, Default: 5 }` | `maxReceive: integer \| default=5` | + +Naming: IaC snake_case variables become camelCase schema fields. + +### 2. Internal references -> CEL expressions + +| IaC Reference | kro CEL | +|---|---| +| TF `aws_vpc.main.id` | `${vpc.status.vpcID}` | +| TF `aws_sqs_queue.dlq.arn` | `${dlq.status.ackResourceMetadata.arn}` | +| TF `var.queue_name` | `${schema.spec.queueName}` | +| CFN `!Ref MyVPC` | `${vpc.status.vpcID}` (or the Kind's primary identifier) | +| CFN `!GetAtt MyQueue.Arn` | `${queue.status.ackResourceMetadata.arn}` | +| CFN `!Ref SomeParameter` | `${schema.spec.someParameter}` | + +Rule of thumb: ARNs come from `status.ackResourceMetadata.arn`; service-specific IDs come from their status field (see `adoption-fields-ref.md` for which field holds the identifier per Kind). + +### 3. Resource IDs + +Each `resources[].id` in the RGD uses the IaC logical name, kebab-case-free camelCase (`aws_sqs_queue.dead_letter` -> `id: deadLetter`). IDs are how CEL references resolve - keep them stable and descriptive. + +### 4. Adoption annotations INSIDE templates + +Every ACK resource template inside an RGD carries the same safety annotations as flat manifests: + +```yaml +metadata: + annotations: + services.k8s.aws/adoption-policy: "adopt-or-create" + services.k8s.aws/deletion-policy: "retain" +``` + +This matters because kro instances create/reconcile the underlying ACK CRs - without adopt-or-create on the templates, instantiating the RGD against existing AWS resources would fail or duplicate them. + +### 5. Instance CRs carry the per-instantiation values + +For each `module` block / nested stack reference, generate one instance CR using the RGD's schema kind, populated with that instantiation's argument values: + +```yaml +apiVersion: kro.run/v1alpha1 +kind: QueueWithDLQ # matches spec.schema.kind of the RGD +metadata: + name: orders # from the module instance label +spec: + queueName: orders # from the module arguments + maxReceiveCount: 5 +``` + +> Note: instance CRs use the API defined by the RGD schema (`kro.run/` + `schema.kind`). kro serves this API once the RGD is applied and ready. + +## Full RGD Skeleton + +```yaml +apiVersion: kro.run/v1alpha1 +kind: ResourceGraphDefinition +metadata: + name: +spec: + schema: + apiVersion: v1alpha1 + kind: + spec: + # module inputs with types and defaults + status: + # optional: surface useful child status fields + # queueARN: ${queue.status.ackResourceMetadata.arn} + resources: + - id: + template: + # full ACK manifest with adoption annotations +``` + +## Apply Order for kro Outputs + +1. kro controller installed and healthy (prerequisite, documented in report) +2. ACK controllers for every service used inside the RGD (prerequisite) +3. `kubectl apply` the RGD -> wait `state: Active` +4. `kubectl apply` the instance CRs -> ACK CRs are created, adoption happens per-resource +5. Validate: instance `Ready`, each child ACK CR shows `ACK.Adopted: True` / `ACK.ResourceSynced: True` + +## Anti-Patterns + +- Wrapping a single flat resource in an RGD (adds a CRD + controller hop for zero composition value) +- Leaking IaC syntax into templates (`${var.x}`, `!Ref`) - every reference must be CEL or a literal +- Omitting adoption annotations inside RGD templates (adoption safety is per-ACK-CR, not per-instance) +- Encoding per-instance values in the RGD instead of schema fields (breaks reusability - the whole point of the module translation) From a4db209bf8653d1886a57a1ab4c9785ba0f3eba1 Mon Sep 17 00:00:00 2001 From: Bruno Lopes Date: Fri, 24 Jul 2026 20:07:14 -0300 Subject: [PATCH 2/2] Extend BENCHMARKS.md to cover all 3 IaC flavors (Terraform, CloudFormation, Pulumi) Two additional end-to-end runs via atx custom def exec: - CloudFormation: nested stack -> kro RGD with CEL wiring, VPC discovery TODO, conservative flag on an unmapped type (never guessed) - Pulumi (Python): ComponentResource -> kro RGD, per-instance override (maxReceiveCount: 3) captured correctly 14/14 annotation audit across all runs, zero IaC syntax remnants, sources byte-identical. Also adds AWS::SNS::Subscription to the mapping table (gap surfaced by the CFN benchmark). --- .../BENCHMARKS.md | 151 ++++++++++-------- .../references/iac-to-ack-mapping.md | 1 + 2 files changed, 88 insertions(+), 64 deletions(-) diff --git a/community-sourced-transformations/ack-resource-adoption-from-iac/BENCHMARKS.md b/community-sourced-transformations/ack-resource-adoption-from-iac/BENCHMARKS.md index d71a484..7de31f7 100644 --- a/community-sourced-transformations/ack-resource-adoption-from-iac/BENCHMARKS.md +++ b/community-sourced-transformations/ack-resource-adoption-from-iac/BENCHMARKS.md @@ -4,30 +4,31 @@ | Metric | Result | |--------|--------| -| Repositories tested | 1 (mixed Terraform: flat resources + module + unsupported type) | -| Transformation success rate | 100% (1/1) | -| Resources inventoried | 7 Terraform resources + 1 module instantiation | -| ACK manifests generated | 3 flat (S3, DynamoDB, IAM Role) | -| kro outputs generated | 1 ResourceGraphDefinition + 1 instance CR (queue-with-dlq module) | -| Safety annotation audit | 5/5 ACK manifests (flat + inside RGD) carry `adopt-or-create` + `deletion-policy: retain` (100%) | -| IaC syntax remnants in output | 0 (`!Ref`, `${var.`, `${module.` - none found) | -| Source IaC modified | 0 files (byte-identical to baseline) | -| Unsupported resources flagged | 1/1 (CloudWatch Log Group - report-only with roadmap guidance) | -| Resource folding | 1/1 (`aws_iam_role_policy_attachment` folded into the Role spec `policies` field) | -| `ADOPTION_REPORT.md` generated | Yes (inventory, skips, prerequisites, apply order) | -| Agent minutes | ~42.4 | -| Estimated cost | ~$1.48 (at $0.035/agent-minute) | +| Repositories tested | 3 - one per supported IaC flavor (Terraform, CloudFormation, Pulumi) | +| Transformation success rate | 100% (3/3) | +| Resources inventoried | 18 IaC resources + 3 composition units (TF module, CFN nested stack, Pulumi ComponentResource) | +| ACK manifests generated | 8 flat (S3 x3, DynamoDB x3, IAM Role, VPC) | +| kro outputs generated | 3 ResourceGraphDefinitions + 3 instance CRs (one per composition unit) | +| Safety annotation audit | 14/14 ACK resource documents (flat + RGD-embedded templates) carry `adopt-or-create` + `deletion-policy: retain` (100%) | +| IaC syntax remnants in output | 0 across all runs (`!Ref`, `!GetAtt`, `${var.`, `.apply(` - none found) | +| Source IaC modified | 0 files in all runs (byte-identical to git baselines) | +| Unsupported resources flagged | 3/3 CloudWatch Log Groups report-only with guidance | +| adoption-fields discovery | VPC correctly emitted with `TODO(discovery)` + exact AWS CLI command (identifier only exists at deploy time) | +| Resource folding | `aws_iam_role_policy_attachment` folded into the Role spec (no orphan manifest) | +| `ADOPTION_REPORT.md` generated | 3/3 runs | +| Total agent minutes | ~116.4 | +| Total estimated cost | ~$4.07 (at $0.035/agent-minute) | ### Methodology -The test repository was seeded with a **known mix of adoption scenarios**, committed to git as a baseline, then transformed via: +Each test repository was seeded with a **known mix of adoption scenarios** (flat resources, a reusable composition unit, an unsupported type), committed to git as a baseline, then transformed via: ```bash atx custom def exec -n ack-resource-adoption-from-iac -p -x -t \ --configuration 'additionalPlanContext=Adopt all resources.' ``` -Results were verified against the git diff (what was generated), the exit criteria in [SKILL.md](SKILL.md), a programmatic annotation audit (YAML parse of every output document, including templates inside the RGD), and grep-based scans for IaC syntax remnants. +Results were verified against the git diff (what was generated), the exit criteria in [SKILL.md](SKILL.md), a programmatic annotation audit (YAML parse of every output document, including templates inside RGDs), and grep-based scans for IaC syntax remnants. ### Pricing Note @@ -35,66 +36,88 @@ Agent minutes = active agent work (planning, reasoning, code generation). Client --- -## Test Repository Composition +## At-a-Glance Results Table -| Input (Terraform) | Scenario Exercised | Expected Output | -|---|---|---| -| `aws_s3_bucket.reports` | Flat resource, identifier in spec | Plain ACK `Bucket` manifest | -| `aws_dynamodb_table.orders` (key schema + tags) | Flat resource with structural spec mapping | Plain ACK `Table` manifest with keySchema/attributeDefinitions | -| `aws_iam_role.app` + `aws_iam_role_policy_attachment.app_s3` | Split-resource folding | ONE ACK `Role` manifest with the attachment folded into `spec.policies` | -| `module "order_queue"` (queue-with-dlq: 2 SQS queues wired via redrive policy) | Composition unit | kro `ResourceGraphDefinition` + instance CR, internal refs as CEL | -| `aws_cloudwatch_log_group.app` | Unsupported type (no GA ACK controller) | NO manifest - flagged in report with guidance | +| # | Repository | IaC Flavor | Status | Flat Manifests | kro RGD + Instance | Unsupported Flagged | Annotation Audit | Agent Min | Cost | +|---|---|---|---|---|---|---|---|---|---| +| 1 | mixed Terraform (7 resources + 1 module) | Terraform | ✅ SUCCESS | 3 (S3, DynamoDB, IAM Role w/ folded attachment) | 1 + 1 (queue-with-dlq) | 1 (Log Group) | 5/5 | 42.4 | $1.48 | +| 2 | platform stack (5 resources + 1 nested stack) | CloudFormation | ✅ SUCCESS | 3 (S3, DynamoDB, VPC w/ discovery TODO) | 1 + 1 (messaging: SNS+SQS) | 2 (Log Group, SNS Subscription¹) | 5/5 | 30.0 | $1.05 | +| 3 | platform program (4 resources + 1 ComponentResource) | Pulumi (Python) | ✅ SUCCESS | 2 (S3 BucketV2, DynamoDB) | 1 + 1 (QueueWithDlq) | 1 (Log Group) | 4/4 | 44.1 | $1.54 | +| | **TOTALS** | | **3/3** | **8** | **3 + 3** | **4** | **14/14** | **~116.4** | **~$4.07** | + +> ¹ The SNS Subscription was conservatively flagged as unsupported because it was missing from the mapping reference at test time - the agent correctly followed the "never guess" constraint instead of inventing a Kind. The mapping table has since been extended with `AWS::SNS::Subscription` -> `sns.services.k8s.aws/v1alpha1 Subscription`. This is exactly the failure mode the design intends: gaps degrade to report-only guidance, never to wrong manifests. --- -## Results Detail +## Detailed Per-Repository Results + +### 1. Terraform - mixed repo (flat + module + unsupported) -### Generated Files +**Input:** `main.tf` with S3 bucket, DynamoDB table, IAM role + policy attachment, a `module "order_queue"` instantiation (queue-with-dlq: 2 SQS queues wired via redrive policy), and a CloudWatch Log Group. + +**Pass/Fail checks:** ```text -ack-adoption/ -├── s3-bucket-reports.yaml # Bucket, adopt-or-create + retain -├── dynamodb-table-orders.yaml # Table, keySchema/attributes/tags mapped -├── iam-role-app.yaml # Role with folded policy attachment -├── rgd-queue-with-dlq.yaml # kro RGD: schema fields from module variables, -│ # 2 Queue templates with adoption annotations, -│ # DLQ wiring via CEL (${dlq.status...}) -└── instance-orders-queue.yaml # Instance CR with the module's argument values -ADOPTION_REPORT.md +✅ 3 flat ACK manifests generated (Bucket, Table, Role) +✅ aws_iam_role_policy_attachment folded into Role spec.policies (no orphan manifest) +✅ Module -> kro RGD: inputs became schema fields (queueName: string, + maxReceiveCount: integer | default=5), DLQ wiring via CEL + (${dlq.status.ackResourceMetadata.arn}), + instance CR with the module arguments +✅ Agent added TODO(discovery) for SQS queueURL adoption-fields on its own, + following the constraints (not present in the worked example) +✅ aws_cloudwatch_log_group flagged report-only with ACK roadmap guidance +✅ Source IaC byte-identical; 0 IaC syntax remnants; 5/5 annotation audit ``` -### Pass/Fail Checks +### 2. CloudFormation - platform stack (flat + nested stack + adoption-fields case) + +**Input:** `main.template.yaml` with S3 bucket (versioning), DynamoDB table, a VPC (identifier only exists at deploy time), a nested stack `MessagingStack` (child template: SNS Topic + SQS Queue + Subscription wired via `!Ref`/`!GetAtt`), and a Log Group. + +**Pass/Fail checks:** ```text -✅ Annotation audit: 5/5 ACK resource documents (3 flat + 2 RGD templates) carry BOTH - services.k8s.aws/adoption-policy: "adopt-or-create" AND - services.k8s.aws/deletion-policy: "retain" -✅ Module inputs became RGD schema fields (queueName: string, - maxReceiveCount: integer | default=5) -✅ Terraform interpolation replaced by CEL: ${schema.spec.queueName}, - ${dlq.status.ackResourceMetadata.arn} - zero IaC syntax remnants in any output -✅ aws_iam_role_policy_attachment correctly folded into Role spec.policies - (no orphan manifest generated) -✅ aws_cloudwatch_log_group NOT converted - listed in ADOPTION_REPORT.md - "Skipped Resources" with keep-in-Terraform guidance and ACK roadmap link -✅ Source IaC byte-identical to the pre-run git baseline (exit criterion 7) -✅ All generated YAML parses cleanly (7 documents across 5 files) -✅ ADOPTION_REPORT.md contains: inventory table, skipped resources with reasons, - prerequisite checklist, dependency-sorted apply order +✅ 3 flat ACK manifests generated (Bucket with versioning mapped, Table, VPC) +✅ VPC emitted with TODO(discovery) + exact CLI command + (aws ec2 describe-vpcs --filters cidr-block) and placeholder adoption-fields - + listed in the report's "Requires Discovery" section, per exit criterion 3 +✅ Nested stack -> kro RGD "messaging": stack Parameters became schema fields, + !Ref/!GetAtt wiring became CEL (${eventsTopic.status.ackResourceMetadata.arn}), + RGD status surfaces topicARN, + instance CR with the parent stack's parameter values +✅ AWS::Logs::LogGroup flagged report-only +✅ SNS Subscription conservatively flagged (see footnote ¹) - never guessed +✅ Source templates byte-identical; 0 CFN syntax remnants in outputs; 5/5 annotation audit ``` -### Exit Criteria Compliance (per SKILL.md) +### 3. Pulumi (Python) - platform program (flat + ComponentResource) + +**Input:** `__main__.py` with `pulumi_aws`: S3 BucketV2, DynamoDB table, a `QueueWithDlq(pulumi.ComponentResource)` class instantiated as `billing` with `max_receive_count=3`, and a CloudWatch Log Group. + +**Pass/Fail checks:** + +```text +✅ 2 flat ACK manifests generated (Bucket from BucketV2, Table with keySchema mapped) +✅ ComponentResource -> kro RGD "queue-with-dlq": class constructor params became + schema fields, the .apply() redrive wiring became CEL, + instance CR correctly + capturing the per-instance override maxReceiveCount: 3 (not the default 5) +✅ aws.cloudwatch.LogGroup flagged report-only +✅ Source program byte-identical; 0 Pulumi syntax remnants (.apply(), pulumi.*) + in outputs; 4/4 annotation audit +``` + +--- + +## Exit Criteria Compliance (per SKILL.md) -| # | Exit Criterion | Result | -|---|---|---| -| 1 | One manifest/RGD slot per supported resource, no duplicates or silent drops | ✅ 6 mapped -> 3 flat + 2 in RGD + 1 folded | -| 2 | 100% of manifests carry adopt-or-create + retain | ✅ 5/5 | -| 3 | adoption-fields resolved or TODO(discovery) + report | ✅ (identifiers in spec for this set) | -| 4 | RGDs only for genuine composition units, with instance CRs, CEL refs | ✅ 1 RGD + 1 instance | -| 5 | No IaC syntax remnants in generated files | ✅ 0 found | -| 6 | Generated YAML parses cleanly | ✅ 7/7 documents | -| 7 | Source IaC byte-identical | ✅ | -| 8 | ADOPTION_REPORT.md complete | ✅ | +| # | Exit Criterion | Run 1 (TF) | Run 2 (CFN) | Run 3 (Pulumi) | +|---|---|---|---|---| +| 1 | One manifest/RGD slot per supported resource, no duplicates or silent drops | ✅ | ✅ | ✅ | +| 2 | 100% of manifests carry adopt-or-create + retain | ✅ 5/5 | ✅ 5/5 | ✅ 4/4 | +| 3 | adoption-fields resolved or TODO(discovery) + report | ✅ | ✅ (VPC) | ✅ | +| 4 | RGDs only for genuine composition units, with instance CRs, CEL refs | ✅ | ✅ | ✅ | +| 5 | No IaC syntax remnants in generated files | ✅ | ✅ | ✅ | +| 6 | Generated YAML parses cleanly | ✅ | ✅ | ✅ | +| 7 | Source IaC byte-identical | ✅ | ✅ | ✅ | +| 8 | ADOPTION_REPORT.md complete | ✅ | ✅ | ✅ | --- @@ -109,9 +132,9 @@ for f in glob.glob('ack-adoption/*.yaml'): ... # assert adoption-policy + deletion-policy on every ACK resource EOF -# IaC syntax remnant scan -grep -rE '\$\{var\.|\$\{module\.|!Ref|!GetAtt' ack-adoption/ +# IaC syntax remnant scan (per flavor) +grep -rE '\$\{var\.|\$\{module\.|!Ref|!GetAtt|\.apply\(|pulumi\.' ack-adoption/ # Source integrity -git diff HEAD -- main.tf modules/ +git diff HEAD -- ``` diff --git a/community-sourced-transformations/ack-resource-adoption-from-iac/references/iac-to-ack-mapping.md b/community-sourced-transformations/ack-resource-adoption-from-iac/references/iac-to-ack-mapping.md index b09b95d..7014673 100644 --- a/community-sourced-transformations/ack-resource-adoption-from-iac/references/iac-to-ack-mapping.md +++ b/community-sourced-transformations/ack-resource-adoption-from-iac/references/iac-to-ack-mapping.md @@ -18,6 +18,7 @@ Only resources with a GA or generally usable ACK controller are mapped - anythin | `AWS::DynamoDB::Table` | `aws_dynamodb_table` | `dynamodb.Table` | `dynamodb.services.k8s.aws/v1alpha1` | `Table` | Not needed (`spec.tableName`) | | `AWS::SQS::Queue` | `aws_sqs_queue` | `sqs.Queue` | `sqs.services.k8s.aws/v1alpha1` | `Queue` | `{"queueURL": "https://sqs...."}` | | `AWS::SNS::Topic` | `aws_sns_topic` | `sns.Topic` | `sns.services.k8s.aws/v1alpha1` | `Topic` | `{"arn": "arn:aws:sns:..."}` | +| `AWS::SNS::Subscription` | `aws_sns_topic_subscription` | `sns.TopicSubscription` | `sns.services.k8s.aws/v1alpha1` | `Subscription` | `{"arn": "arn:aws:sns:...:"}` | | `AWS::RDS::DBInstance` | `aws_db_instance` | `rds.Instance` | `rds.services.k8s.aws/v1alpha1` | `DBInstance` | Not needed (`spec.dbInstanceIdentifier`) | | `AWS::RDS::DBCluster` | `aws_rds_cluster` | `rds.Cluster` | `rds.services.k8s.aws/v1alpha1` | `DBCluster` | Not needed (`spec.dbClusterIdentifier`) | | `AWS::ElastiCache::ReplicationGroup` | `aws_elasticache_replication_group` | `elasticache.ReplicationGroup` | `elasticache.services.k8s.aws/v1alpha1` | `ReplicationGroup` | Not needed (`spec.replicationGroupID`) |