diff --git a/.gitignore b/.gitignore
index 6964514..edeabaf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,8 @@ aws-assumed-role/
*.iml
.direnv
.envrc
+.cache
+.atmos
# Compiled and auto-generated files
# Note that the leading "**/" appears necessary for Docker even if not for Git
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..8deadc1
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,35 @@
+# Repository Guidelines
+
+## Project Structure & Module Organization
+- `src/`: Terraform component (`main.tf`, `variables.tf`, `outputs.tf`, `providers.tf`, `versions.tf`, `context.tf`). This is the source of truth.
+- `test/`: Go Terratest suite using Atmos fixtures (`component_test.go`, `fixtures/`, `test_suite.yaml`). Tests deploy/destroy real AWS resources.
+- `README.yaml`: Source for the generated `README.md` (via atmos + terraform-docs).
+- `.github/`: CI/CD, Renovate/Dependabot, labels, and automerge settings.
+- `docs/`: Project docs (if any). Keep lightweight and current.
+
+## Build, Test, and Development Commands
+- To install atmos read this docs https://github.com/cloudposse/atmos
+- `atmos docs generate readme`: Regenerate `README.md` from `README.yaml` and terraform source.
+- `atmos docs generate readme-simple`: Regenerate `src/README.md` from `README.yaml` and terraform source.
+- `atmos test run`: Run Terratest suite in `test/` (uses Atmos fixtures; creates and destroys AWS resources).
+- Pre-commit locally: `pre-commit install && pre-commit run -a` (runs `terraform_fmt`, `terraform_docs`, `tflint`).
+- TFLint plugin setup: `tflint --init` (uses `.tflint.hcl`).
+
+## Coding Style & Naming Conventions
+- Indentation: Terraform 2 spaces; YAML/Markdown 2 spaces.
+- Terraform: prefer lower_snake_case for variables/locals; keep resources/data sources descriptive and aligned with Cloud Posse null-label patterns.
+- Lint/format: `terraform fmt -recursive`, TFLint rules per `.tflint.hcl`. Do not commit formatting or lint violations.
+
+## Testing Guidelines
+- Framework: Go Terratest with `github.com/cloudposse/test-helpers` and `atmos` fixtures.
+- Location/naming: put tests in `test/` and name files `*_test.go`. Add scenarios under `test/fixtures/stacks/catalog/usecase/`.
+- Run: `atmos test run`. Ensure AWS credentials are configured; tests may incur AWS costs and will clean up after themselves.
+
+## Commit & Pull Request Guidelines
+- Commits: follow Conventional Commits (e.g., `feat:`, `fix:`, `chore(deps):`, `docs:`). Keep messages concise and scoped.
+- PRs: include a clear description, linked issues, and any behavioral changes. Update `README.yaml` when inputs/outputs change and run `atmos docs generate readme`.
+- CI: ensure pre-commit, TFLint, and tests pass. Avoid unrelated changes in the same PR.
+
+## Security & Configuration Tips
+- Never commit secrets. Configure AWS credentials/role assumption externally; the provider setup in `src/providers.tf` supports role assumption via the `iam_roles` module.
+- Global quotas must be applied in `us-east-1`; place in the `gbl` stack and set `region: us-east-1` in `vars`.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 8a6d902..0000000
--- a/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
--include $(shell curl -sSL -o .build-harness "https://cloudposse.tools/build-harness"; echo .build-harness)
-
-all: init readme
-
-test::
- @echo "🚀 Starting tests..."
- ./test/run.sh
- @echo "✅ All tests passed."
diff --git a/README.md b/README.md
index 555e220..f62423a 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,11 @@

-

atmos to manage infrastructure from our Quick Start tutorial.
+> 
atmos to manage infrastructure from our Quick Start tutorial.
->
-
-
-
-
-
+
@@ -720,6 +728,38 @@ In general, PRs are welcome. We follow the typical "fork-and-pull" Git workflow.
**NOTE:** Be sure to merge the latest changes from "upstream" before making a pull request!
+
+## Running Terraform Tests
+
+We use [Atmos](https://atmos.tools) to streamline how Terraform tests are run. It centralizes configuration and wraps common test workflows with easy-to-use commands.
+
+All tests are located in the [`test/`](test) folder.
+
+Under the hood, tests are powered by Terratest together with our internal [Test Helpers](https://github.com/cloudposse/test-helpers) library, providing robust infrastructure validation.
+
+Setup dependencies:
+- Install Atmos ([installation guide](https://atmos.tools/install/))
+- Install Go [1.24+ or newer](https://go.dev/doc/install)
+- Install Terraform or OpenTofu
+
+To run tests:
+
+- Run all tests:
+ ```sh
+ atmos test run
+ ```
+- Clean up test artifacts:
+ ```sh
+ atmos test clean
+ ```
+- Explore additional test options:
+ ```sh
+ atmos test --help
+ ```
+The configuration for test commands is centrally managed. To review what's being imported, see the [`atmos.yaml`](https://raw.githubusercontent.com/cloudposse/.github/refs/heads/main/.github/atmos/terraform-module.yaml) file.
+
+Learn more about our [automated testing in our documentation](https://docs.cloudposse.com/community/contribute/automated-testing/) or implementing [custom commands](https://atmos.tools/core-concepts/custom-commands/) with atmos.
+
### 🌎 Slack Community
Join our [Open Source Community](https://cpco.io/slack?utm_source=github&utm_medium=readme&utm_campaign=cloudposse-terraform-components/aws-eks-actions-runner-controller&utm_content=slack) on Slack. It's **FREE** for everyone! Our "SweetOps" community is where you get to talk with others who share a similar vision for how to rollout and manage infrastructure. This is the best place to talk shop, ask questions, solicit feedback, and work together as a community to build totally *sweet* infrastructure.
diff --git a/README.yaml b/README.yaml
index 96a626c..a8f5b3b 100644
--- a/README.yaml
+++ b/README.yaml
@@ -6,8 +6,7 @@ description: |-
This component creates a Helm release for
[actions-runner-controller](https://github.com/actions-runner-controller/actions-runner-controller) on an EKS cluster.
- ## Usage
-
+usage: |-
**Stack Level**: Regional
Once the catalog file is created, the file can be imported as follows.
@@ -486,121 +485,24 @@ description: |-
documentation for further details.
-
- ## Requirements
-
- | Name | Version |
- |------|---------|
- | [terraform](#requirement\_terraform) | >= 1.3.0 |
- | [aws](#requirement\_aws) | >= 4.9.0 |
- | [helm](#requirement\_helm) | >= 2.0 |
- | [kubernetes](#requirement\_kubernetes) | >= 2.0, != 2.21.0 |
-
- ## Providers
-
- | Name | Version |
- |------|---------|
- | [aws](#provider\_aws) | >= 4.9.0 |
-
- ## Modules
-
- | Name | Source | Version |
- |------|--------|---------|
- | [actions\_runner](#module\_actions\_runner) | cloudposse/helm-release/aws | 0.10.1 |
- | [actions\_runner\_controller](#module\_actions\_runner\_controller) | cloudposse/helm-release/aws | 0.10.1 |
- | [eks](#module\_eks) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 |
- | [iam\_roles](#module\_iam\_roles) | ../../account-map/modules/iam-roles | n/a |
- | [this](#module\_this) | cloudposse/label/null | 0.25.0 |
-
- ## Resources
-
- | Name | Type |
- |------|------|
- | [aws_eks_cluster_auth.eks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/eks_cluster_auth) | data source |
- | [aws_ssm_parameter.docker_config_json](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
- | [aws_ssm_parameter.github_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
- | [aws_ssm_parameter.github_webhook_secret_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
-
- ## Inputs
-
- | Name | Description | Type | Default | Required |
- |------|-------------|------|---------|:--------:|
- | [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
} | no |
- | [context\_tags\_enabled](#input\_context\_tags\_enabled) | Whether or not to include all context tags as labels for each runner | `bool` | `false` | no |
- | [controller\_replica\_count](#input\_controller\_replica\_count) | The number of replicas of the runner-controller to run. | `number` | `2` | no |
- | [create\_namespace](#input\_create\_namespace) | Create the namespace if it does not yet exist. Defaults to `false`. | `bool` | `null` | no |
- | [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.[| no | - | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
"default"
]
object({
limits = object({
cpu = string
memory = string
})
requests = object({
cpu = string
memory = string
})
}) | n/a | yes |
- | [runners](#input\_runners) | Map of Action Runner configurations, with the key being the name of the runner. Please note that the name must be inhcl|
organization_runner = {
type = "organization" # can be either 'organization' or 'repository'
dind_enabled: true # A Docker daemon will be started in the runner Pod
image: summerwind/actions-runner-dind # If dind_enabled=false, set this to 'summerwind/actions-runner'
scope = "ACME" # org name for Organization runners, repo name for Repository runners
group = "core-automation" # Optional. Assigns the runners to a runner group, for access control.
scale_down_delay_seconds = 300
min_replicas = 1
max_replicas = 5
labels = [
"Ubuntu",
"core-automation",
]
}
map(object({
type = string
scope = string
group = optional(string, null)
image = optional(string, "summerwind/actions-runner-dind")
auto_update_enabled = optional(bool, true)
dind_enabled = optional(bool, true)
node_selector = optional(map(string), {})
pod_annotations = optional(map(string), {})
# running_pod_annotations are only applied to the pods once they start running a job
running_pod_annotations = optional(map(string), {})
# affinity is too complex to model. Whatever you assigned affinity will be copied
# to the runner Pod spec.
affinity = optional(any)
tolerations = optional(list(object({
key = string
operator = string
value = optional(string, null)
effect = string
})), [])
scale_down_delay_seconds = optional(number, 300)
min_replicas = number
max_replicas = number
# Scheduled overrides. See https://github.com/actions/actions-runner-controller/blob/master/docs/automatically-scaling-runners.md#scheduled-overrides
# Order is important. The earlier entry is prioritized higher than later entries. So you usually define
# one-time overrides at the top of your list, then yearly, monthly, weekly, and lastly daily overrides.
scheduled_overrides = optional(list(object({
start_time = string # ISO 8601 format, eg, "2021-06-01T00:00:00+09:00"
end_time = string # ISO 8601 format, eg, "2021-06-01T00:00:00+09:00"
min_replicas = optional(number)
max_replicas = optional(number)
recurrence_rule = optional(object({
frequency = string # One of Daily, Weekly, Monthly, Yearly
until_time = optional(string) # ISO 8601 format time after which the schedule will no longer apply
}))
})), [])
busy_metrics = optional(object({
scale_up_threshold = string
scale_down_threshold = string
scale_up_adjustment = optional(string)
scale_down_adjustment = optional(string)
scale_up_factor = optional(string)
scale_down_factor = optional(string)
}))
webhook_driven_scaling_enabled = optional(bool, true)
# max_duration is the duration after which a job will be considered completed,
# even if the webhook has not received a "job completed" event.
# This is to ensure that if an event is missed, it does not leave the runner running forever.
# Set it long enough to cover the longest job you expect to run and then some.
# See https://github.com/actions/actions-runner-controller/blob/9afd93065fa8b1f87296f0dcdf0c2753a0548cb7/docs/automatically-scaling-runners.md?plain=1#L264-L268
# Defaults to 1 hour programmatically (to be able to detect if both max_duration and webhook_startup_timeout are set).
max_duration = optional(string)
# The name `webhook_startup_timeout` was misleading and has been deprecated.
# It has been renamed `max_duration`.
webhook_startup_timeout = optional(string)
# Adjust the time (in seconds) to wait for the Docker in Docker daemon to become responsive.
wait_for_docker_seconds = optional(string, "")
pull_driven_scaling_enabled = optional(bool, false)
labels = optional(list(string), [])
# If not null, `docker_storage` specifies the size (as `go` string) of
# an ephemeral (default storage class) Persistent Volume to allocate for the Docker daemon.
# Takes precedence over `tmpfs_enabled` for the Docker daemon storage.
docker_storage = optional(string, null)
# storage is deprecated in favor of docker_storage, since it is only storage for the Docker daemon
storage = optional(string, null)
# If `pvc_enabled` is true, a Persistent Volume Claim will be created for the runner
# and mounted at /home/runner/work/shared. This is useful for sharing data between runners.
pvc_enabled = optional(bool, false)
# If `tmpfs_enabled` is `true`, both the runner and the docker daemon will use a tmpfs volume,
# meaning that all data will be stored in RAM rather than on disk, bypassing disk I/O limitations,
# but what would have been disk usage is now additional memory usage. You must specify memory
# requests and limits when using tmpfs or else the Pod will likely crash the Node.
tmpfs_enabled = optional(bool)
resources = optional(object({
limits = optional(object({
cpu = optional(string, "1")
memory = optional(string, "1Gi")
# ephemeral-storage is the Kubernetes name, but `ephemeral_storage` is the gomplate name,
# so allow either. If both are specified, `ephemeral-storage` takes precedence.
ephemeral-storage = optional(string)
ephemeral_storage = optional(string, "10Gi")
}), {})
requests = optional(object({
cpu = optional(string, "500m")
memory = optional(string, "256Mi")
# ephemeral-storage is the Kubernetes name, but `ephemeral_storage` is the gomplate name,
# so allow either. If both are specified, `ephemeral-storage` takes precedence.
ephemeral-storage = optional(string)
ephemeral_storage = optional(string, "1Gi")
}), {})
}), {})
})) | n/a | yes |
- | [s3\_bucket\_arns](#input\_s3\_bucket\_arns) | List of ARNs of S3 Buckets to which the runners will have read-write access to. | `list(string)` | `[]` | no |
- | [ssm\_docker\_config\_json\_path](#input\_ssm\_docker\_config\_json\_path) | SSM path to the Docker config JSON | `string` | `null` | no |
- | [ssm\_github\_secret\_path](#input\_ssm\_github\_secret\_path) | The path in SSM to the GitHub app private key file contents or GitHub PAT token. | `string` | `""` | no |
- | [ssm\_github\_webhook\_secret\_token\_path](#input\_ssm\_github\_webhook\_secret\_token\_path) | The path in SSM to the GitHub Webhook Secret token. | `string` | `""` | no |
- | [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no |
- | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).object({
enabled = bool
hostname_template = string
queue_limit = optional(number, 1000)
}) | {
"enabled": false,
"hostname_template": null,
"queue_limit": 1000
} | no |
-
- ## Outputs
-
- | Name | Description |
- |------|-------------|
- | [metadata](#output\_metadata) | Block status of the deployed release |
- | [metadata\_action\_runner\_releases](#output\_metadata\_action\_runner\_releases) | Block statuses of the deployed actions-runner chart releases |
- | [webhook\_payload\_url](#output\_webhook\_payload\_url) | Payload URL for GitHub webhook |
-
- ## References
-
- - [cloudposse/terraform-aws-components](https://github.com/cloudposse/terraform-aws-components/tree/main/modules/eks/actions-runner-controller) -
- Cloud Posse's upstream component
- - [alb-controller](https://artifacthub.io/packages/helm/aws/aws-load-balancer-controller) - Helm Chart
- - [alb-controller](https://github.com/kubernetes-sigs/aws-load-balancer-controller) - AWS Load Balancer Controller
- - [actions-runner-controller Webhook Driven Scaling](https://github.com/actions-runner-controller/actions-runner-controller/blob/master/docs/detailed-docs.md#webhook-driven-scaling)
- - [actions-runner-controller Chart Values](https://github.com/actions-runner-controller/actions-runner-controller/blob/master/charts/actions-runner-controller/values.yaml)
+references:
+ - name: cloudposse-terraform-components
+ url: https://github.com/orgs/cloudposse-terraform-components/repositories
+ description: Cloud Posse's upstream component
+ - name: alb-controller
+ url: https://artifacthub.io/packages/helm/aws/aws-load-balancer-controller
+ description: Helm Chart
+ - name: alb-controller
+ url: https://github.com/kubernetes-sigs/aws-load-balancer-controller
+ description: AWS Load Balancer Controller
+ - name: actions-runner-controller Webhook Driven Scaling
+ url: https://github.com/actions-runner-controller/actions-runner-controller/blob/master/docs/detailed-docs.md#webhook-driven-scaling
+ description:
+ - name: actions-runner-controller Chart Values
+ url: https://github.com/actions-runner-controller/actions-runner-controller/blob/master/charts/actions-runner-controller/values.yaml
+ description:
tags:
- component/eks/actions-runner-controller
- layer/github
diff --git a/atmos.yaml b/atmos.yaml
new file mode 100644
index 0000000..481c199
--- /dev/null
+++ b/atmos.yaml
@@ -0,0 +1,11 @@
+# Atmos Configuration — powered by https://atmos.tools
+#
+# This configuration enables centralized, DRY, and consistent project scaffolding using Atmos.
+#
+# Included features:
+# - Organizational custom commands: https://atmos.tools/core-concepts/custom-commands
+# - Automated README generation: https://atmos.tools/cli/commands/docs/generate
+#
+# Import shared configuration used by all modules
+import:
+ - https://raw.githubusercontent.com/cloudposse-terraform-components/.github/refs/heads/main/.github/atmos/terraform-component.yaml
diff --git a/src/README.md b/src/README.md
index 3901efb..7dac85f 100644
--- a/src/README.md
+++ b/src/README.md
@@ -491,7 +491,19 @@ Consult [actions-runner-controller](https://github.com/actions-runner-controller
documentation for further details.
-
+
+
+## References
+
+- [cloudposse/terraform-aws-components](https://github.com/cloudposse/terraform-aws-components/tree/main/modules/eks/actions-runner-controller) -
+ Cloud Posse's upstream component
+- [alb-controller](https://artifacthub.io/packages/helm/aws/aws-load-balancer-controller) - Helm Chart
+- [alb-controller](https://github.com/kubernetes-sigs/aws-load-balancer-controller) - AWS Load Balancer Controller
+- [actions-runner-controller Webhook Driven Scaling](https://github.com/actions-runner-controller/actions-runner-controller/blob/master/docs/detailed-docs.md#webhook-driven-scaling)
+- [actions-runner-controller Chart Values](https://github.com/actions-runner-controller/actions-runner-controller/blob/master/charts/actions-runner-controller/values.yaml)
+
+
+
## Requirements
| Name | Version |
@@ -595,16 +607,10 @@ documentation for further details.
| [metadata](#output\_metadata) | Block status of the deployed release |
| [metadata\_action\_runner\_releases](#output\_metadata\_action\_runner\_releases) | Block statuses of the deployed actions-runner chart releases |
| [webhook\_payload\_url](#output\_webhook\_payload\_url) | Payload URL for GitHub webhook |
-
-
+
+
-## References
-- [cloudposse/terraform-aws-components](https://github.com/cloudposse/terraform-aws-components/tree/main/modules/eks/actions-runner-controller) -
- Cloud Posse's upstream component
-- [alb-controller](https://artifacthub.io/packages/helm/aws/aws-load-balancer-controller) - Helm Chart
-- [alb-controller](https://github.com/kubernetes-sigs/aws-load-balancer-controller) - AWS Load Balancer Controller
-- [actions-runner-controller Webhook Driven Scaling](https://github.com/actions-runner-controller/actions-runner-controller/blob/master/docs/detailed-docs.md#webhook-driven-scaling)
-- [actions-runner-controller Chart Values](https://github.com/actions-runner-controller/actions-runner-controller/blob/master/charts/actions-runner-controller/values.yaml)
[