diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..522506c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,34 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu +{ + "name": "Ubuntu", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/base:jammy", + "features": { + "ghcr.io/devcontainers/features/terraform:1": {}, + "ghcr.io/devcontainers-contrib/features/terraform-docs:1": {}, + "ghcr.io/devcontainers/features/azure-cli:1": {}, + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers-contrib/features/kind:1": {}, + "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {}, + "ghcr.io/dhoeric/features/stern:1": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "github.vscode-github-actions", + "redhat.vscode-yaml" + ] + } + } + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "uname -a", + // Configure tool-specific properties. + // "customizations": {}, + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3c92f18 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,28 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" + reviewers: + - infinite-automations/admins + - package-ecosystem: "terraform" + directory: "/" + schedule: + # Check for updates to terraform every week + interval: "weekly" + reviewers: + - infinite-automations/admins + - package-ecosystem: "terraform" + directory: "examples/full" + schedule: + # Check for updates to terraform every week + interval: "weekly" + reviewers: + - infinite-automations/admins diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml new file mode 100644 index 0000000..1214cd4 --- /dev/null +++ b/.github/workflows/test-and-release.yml @@ -0,0 +1,158 @@ +name: Test & Release + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +permissions: + actions: read + contents: write + statuses: read + +env: + TF_VAR_github_app_id: ${{ secrets.GH_APP_ID }} + TF_VAR_github_app_install_id: ${{ secrets.GH_APP_INSTALL_ID }} + TF_VAR_github_app_private_key: ${{ secrets.GH_APP_KEY }} + TF_VAR_labels: '["test", "${{ github.run_id }}"]' + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + strategy: + matrix: + directory: + - "." + - "examples/full" + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Lint Example + uses: "infinite-automations/tflint-all-in-one@v1.0.0" + with: + directory: ${{ matrix.directory }} + + update-docs: + name: Update Docs + needs: + - lint + runs-on: ubuntu-latest + concurrency: + group: ${{ github.ref }}-docs + cancel-in-progress: false + outputs: + changed: ${{ steps.terraform-docs.outputs.num_changed > 0 }} + steps: + - name: Checkout + uses: actions/checkout@v4 + if: ${{ github.event_name == 'push' }} + - name: Checkout + uses: actions/checkout@v4 + if: ${{ github.event_name == 'pull_request' }} + with: + ref: ${{ github.event.pull_request.head.ref }} + - name: Render terraform docs inside the README.md + id: terraform-docs + uses: terraform-docs/gh-actions@v1.0.0 + with: + working-dir: .,examples/full + output-file: README.md + output-method: inject + git-push: ${{ github.event_name == 'pull_request' }} + git-commit-message: "docs(terraform): update README.md" + - name: Cache Docs + if: ${{ github.event_name == 'push' }} + uses: actions/cache@v3 + with: + path: | + README.md + examples/full/README.md + key: ${{ runner.os }}-docs-${{ github.sha }} + + setup-runner: + name: Setup Runner + needs: + - lint + - update-docs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Create k8s Kind Cluster + uses: helm/kind-action@v1.5.0 + with: + config: "~/.kube/config" + wait: "120s" + - name: Create Runners + uses: infinite-automations/terraform-all-in-one@v1.2.0 + id: apply-example + with: + directory: "examples/full" + apply: true + destroy: false + - name: Wait For Test + uses: NathanFirmo/wait-for-other-job@v1.1.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + job: "test-runner" + - name: Destroy Runners + uses: infinite-automations/terraform-all-in-one@v1.2.0 + with: + directory: "examples/full" + apply: false + destroy: true + + test-runner: + name: Run Test Job + needs: + - lint + - update-docs + runs-on: + - self-hosted + - test + - ${{ github.run_id }} + strategy: + matrix: + image: + - alpine:latest + - ubuntu:latest + - debian:latest + container: + image: ${{ matrix.image }} + steps: + - name: Print Environment Information + run: | + echo "Operating System: $OSTYPE" + echo "Hostname: $(hostname)" + echo "CPU Architecture: $(uname -m)" + echo "Kernel Version: $(uname -r)" + echo + cat /etc/os-release + + release: + name: Release + needs: + - update-docs + - test-runner + concurrency: + group: ${{ github.ref }}-release + cancel-in-progress: false + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Restore Cached Docs + if: ${{ github.event_name == 'push' }} + uses: actions/cache@v3 + with: + path: README.md + key: ${{ runner.os }}-docs-${{ github.sha }} + - name: Semantic Release + uses: cycjimmy/semantic-release-action@v4 + with: + dry_run: ${{ github.event_name == 'pull_request' }} + ci: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 9b8a46e..0a2bdc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# local dev +/dev + # Local .terraform directories **/.terraform/* diff --git a/.terraform-docs.yml b/.terraform-docs.yml new file mode 100644 index 0000000..1d9b0ff --- /dev/null +++ b/.terraform-docs.yml @@ -0,0 +1,45 @@ +# .terraform-docs.yml +formatter: "markdown table" + +sections: + show: + - header + - requirements + - providers + - resources + - inputs + - outputs + - footer + +content: |- + {{ .Header }} + + ## Module Usage + + ```hcl + {{ include "examples/full/main.tf" }} + ``` + + {{ .Requirements }} + + {{ .Providers }} + + {{ .Resources }} + + {{ .Inputs }} + + {{ .Outputs }} + + {{ .Footer }} + +sort: + enabled: true + by: required + +settings: + anchor: true + default: true + required: true + type: true + hide-empty: true + indent: 2 diff --git a/README.md b/README.md index d038116..5342a73 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,80 @@ # terraform-helm-github-actions-runner-controller Setup the GitHub Actions Runner Controller (ARC) in an existing kubernetes cluster + + + + +## Module Usage + +```hcl +# setup actions-runner-controller +module "actions-runner-controller" { + source = "../.." + namespace = "github-actions-runner-controller" + create_namespace = true + allow_granting_container_mode_permissions = false + github_app_id = var.github_app_id + github_app_install_id = var.github_app_install_id + github_app_private_key = var.github_app_private_key + kubernetes_secret_name = "github-auth-secret" + helm_deployment_name = "actions-runner-controller" + helm_chart_version = "0.23.5" + replicas = 1 + atomic = true + timeout = 600 + depends_on = [ + helm_release.cert_manager + ] +} +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13 | +| [helm](#requirement\_helm) | >= 2.11.0 | +| [kubernetes](#requirement\_kubernetes) | >= 2.23.0 | + +## Providers + +| Name | Version | +|------|---------| +| [helm](#provider\_helm) | >= 2.11.0 | +| [kubernetes](#provider\_kubernetes) | >= 2.23.0 | + +## Resources + +| Name | Type | +|------|------| +| [helm_release.this](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [kubernetes_namespace.this](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | +| [kubernetes_secret.this](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [github\_app\_id](#input\_github\_app\_id) | GitHub App ID | `string` | n/a | yes | +| [github\_app\_install\_id](#input\_github\_app\_install\_id) | GitHub App Install ID | `string` | n/a | yes | +| [github\_app\_private\_key](#input\_github\_app\_private\_key) | GitHub App Private Key | `string` | n/a | yes | +| [allow\_granting\_container\_mode\_permissions](#input\_allow\_granting\_container\_mode\_permissions) | If true, the runner controller will be allowed to grant container mode permissions | `bool` | `false` | no | +| [atomic](#input\_atomic) | If true, installation process purges chart on fail. If false, installation process deletes resources created by chart, but not purge them | `bool` | `true` | no | +| [create\_namespace](#input\_create\_namespace) | If true, the namespace will be created | `bool` | `true` | no | +| [helm\_chart\_version](#input\_helm\_chart\_version) | The version of the helm chart to deploy | `string` | `"0.23.5"` | no | +| [helm\_deployment\_name](#input\_helm\_deployment\_name) | The name of the helm deployment | `string` | `"actions-runner-controller"` | no | +| [kubernetes\_secret\_name](#input\_kubernetes\_secret\_name) | The name of the secret to create | `string` | `"github-auth-secret"` | no | +| [namespace](#input\_namespace) | The namespace to deploy the runner controller into | `string` | `"github-actions-runner-controller"` | no | +| [replicas](#input\_replicas) | The number of replicas for the runner controller | `number` | `3` | no | +| [timeout](#input\_timeout) | Time in seconds to wait for helm deployment operation (like Jobs for hooks) | `number` | `600` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [helm\_deployment\_name](#output\_helm\_deployment\_name) | The name of the helm deployment | +| [namespace](#output\_namespace) | The namespace runner controller was deployed into | +| [secret\_name](#output\_secret\_name) | The name of the secret created | + + + \ No newline at end of file diff --git a/examples/full/.terraform-docs.yml b/examples/full/.terraform-docs.yml new file mode 100644 index 0000000..441c370 --- /dev/null +++ b/examples/full/.terraform-docs.yml @@ -0,0 +1,57 @@ +# .terraform-docs.yml +formatter: "markdown table" + +sections: + show: + - header + - requirements + - providers + - resources + - inputs + - outputs + - footer + +content: |- + {{ .Header }} + + ## 1. Setup Cert Manager + + ```hcl + {{ include "cert-manager.tf" }} + ``` + + ## 2. Setup Agents Runner Controller + + ```hcl + {{ include "main.tf" }} + ``` + + ## 3. Setup Runner + + ```hcl + {{ include "runner.tf" }} + ``` + + {{ .Requirements }} + + {{ .Providers }} + + {{ .Resources }} + + {{ .Inputs }} + + {{ .Outputs }} + + {{ .Footer }} + +sort: + enabled: true + by: required + +settings: + anchor: true + default: true + required: true + type: true + hide-empty: true + indent: 2 diff --git a/examples/full/.terraform.lock.hcl b/examples/full/.terraform.lock.hcl new file mode 100644 index 0000000..9bb41bf --- /dev/null +++ b/examples/full/.terraform.lock.hcl @@ -0,0 +1,59 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/gavinbunney/kubectl" { + version = "1.14.0" + constraints = ">= 1.14.0" + hashes = [ + "h1:gLFn+RvP37sVzp9qnFCwngRjjFV649r6apjxvJ1E/SE=", + "zh:0350f3122ff711984bbc36f6093c1fe19043173fad5a904bce27f86afe3cc858", + "zh:07ca36c7aa7533e8325b38232c77c04d6ef1081cb0bac9d56e8ccd51f12f2030", + "zh:0c351afd91d9e994a71fe64bbd1662d0024006b3493bb61d46c23ea3e42a7cf5", + "zh:39f1a0aa1d589a7e815b62b5aa11041040903b061672c4cfc7de38622866cbc4", + "zh:428d3a321043b78e23c91a8d641f2d08d6b97f74c195c654f04d2c455e017de5", + "zh:4baf5b1de2dfe9968cc0f57fd4be5a741deb5b34ee0989519267697af5f3eee5", + "zh:6131a927f9dffa014ab5ca5364ac965fe9b19830d2bbf916a5b2865b956fdfcf", + "zh:c62e0c9fd052cbf68c5c2612af4f6408c61c7e37b615dc347918d2442dd05e93", + "zh:f0beffd7ce78f49ead612e4b1aefb7cb6a461d040428f514f4f9cc4e5698ac65", + ] +} + +provider "registry.terraform.io/hashicorp/helm" { + version = "2.12.1" + constraints = ">= 2.11.0" + hashes = [ + "h1:sgYI7lwGqJqPopY3NGmhb1eQ0YbH8PIXaAZAmnJrAvw=", + "zh:1d623fb1662703f2feb7860e3c795d849c77640eecbc5a776784d08807b15004", + "zh:253a5bc62ba2c4314875139e3fbd2feaad5ef6b0fb420302a474ab49e8e51a38", + "zh:282358f4ad4f20d0ccaab670b8645228bfad1c03ac0d0df5889f0aea8aeac01a", + "zh:4fd06af3091a382b3f0d8f0a60880f59640d2b6d9d6a31f9a873c6f1bde1ec50", + "zh:6816976b1830f5629ae279569175e88b497abbbac30ee809948a1f923c67a80d", + "zh:7d82c4150cdbf48cfeec867be94c7b9bd7682474d4df0ebb7e24e148f964844f", + "zh:83f062049eea2513118a4c6054fb06c8600bac96196f25aed2cc21898ec86e93", + "zh:a79eec0cf4c08fca79e44033ec6e470f25ff23c3e2c7f9bc707ed7771c1072c0", + "zh:b2b2d904b2821a6e579910320605bc478bbef063579a23fbfdd6fcb5871b81f8", + "zh:e91177ca06a15487fc570cb81ecef6359aa399459ea2aa7c4f7367ba86f6fcad", + "zh:e976bcb82996fc4968f8382bbcb6673efb1f586bf92074058a232028d97825b1", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "2.24.0" + constraints = ">= 2.23.0" + hashes = [ + "h1:u9lRMCdNXcB5/WQTZVMvGhNliW2pKOzj3SOVbu9yPpg=", + "zh:0ed83ec390a7e75c4990ebce698f14234de2b6204ed9a01cd042bb7ea5f26564", + "zh:195150e4fdab259c70088528006f4604557a051e037ebe8de64e92840f27e40a", + "zh:1a334af55f7a74adf033eb871c9fe7e9e648b41ab84321114ef4ca0e7a34fba6", + "zh:1ef68c3832691de21a61bf1a4e268123f3e08850712eda0b893cac908a0d1bc1", + "zh:44a1c58e5a6646e62b0bad653319c245f3b635dd03554dea2707a38f553e4a52", + "zh:54b5b374c4386f7f05b3fe986f9cb57bde4beab3bdf6ee33444f2b9a81b8af64", + "zh:aa8c2687ab784b72f8cdad8d3c3673dea83b33561e7b3f2d287ef0d06ff2a9e5", + "zh:e6ecba0503052ef3ad49ad56e17b2a73d9b55e30fcb82b040189d281e25e1a3b", + "zh:f105393f6487d3eb1f1636ba42d10c82950ddfef852244c1bca8d526fa23a9a3", + "zh:f17a8f1914ec66d80ccacecd40123362cf093abee3d3aa1ff9f8f687d8736f85", + "zh:f394b12ef01fa0bdf666a43ad152eb3890134f35e635ea056b18771c292de46e", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/examples/full/README.md b/examples/full/README.md new file mode 100644 index 0000000..644b76b --- /dev/null +++ b/examples/full/README.md @@ -0,0 +1,207 @@ + + + +## 1. Setup Cert Manager + +```hcl +# create namespace for cert mananger +resource "kubernetes_namespace" "cert_manager" { + metadata { + labels = { + "name" = "cert-manager" + } + name = "cert-manager" + } +} + +# install cert-manager helm chart using terraform +resource "helm_release" "cert_manager" { + name = "cert-manager" + repository = "https://charts.jetstack.io" + chart = "cert-manager" + version = "v1.12.3" + namespace = kubernetes_namespace.cert_manager.metadata.0.name + atomic = true + timeout = 600 + set { + name = "installCRDs" + value = "true" + } +} +``` + +## 2. Setup Agents Runner Controller + +```hcl +# setup actions-runner-controller +module "actions-runner-controller" { + source = "../.." + namespace = "github-actions-runner-controller" + create_namespace = true + allow_granting_container_mode_permissions = false + github_app_id = var.github_app_id + github_app_install_id = var.github_app_install_id + github_app_private_key = var.github_app_private_key + kubernetes_secret_name = "github-auth-secret" + helm_deployment_name = "actions-runner-controller" + helm_chart_version = "0.23.5" + replicas = 1 + atomic = true + timeout = 600 + depends_on = [ + helm_release.cert_manager + ] +} +``` + +## 3. Setup Runner + +```hcl +# create runner +resource "kubernetes_service_account" "runner" { + metadata { + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } + secret { + name = kubernetes_secret.runner.metadata.0.name + } +} + +resource "kubernetes_secret" "runner" { + metadata { + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } +} + +resource "kubernetes_role" "runner" { + metadata { + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } + + rule { + api_groups = [""] + resources = ["pods"] + verbs = ["get", "list", "create", "delete"] + } + rule { + api_groups = [""] + resources = ["pods/exec"] + verbs = ["get", "create"] + } + rule { + api_groups = [""] + resources = ["pods/log"] + verbs = ["get", "list", "watch"] + } + rule { + api_groups = ["batch"] + resources = ["jobs"] + verbs = ["get", "list", "create", "delete"] + } + rule { + api_groups = [""] + resources = ["secrets"] + verbs = ["create", "delete", "get", "list"] + } +} + +resource "kubernetes_role_binding" "runner" { + metadata { + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "Role" + name = "test-runner" + } + subject { + kind = "ServiceAccount" + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } +} + +resource "kubectl_manifest" "runner" { + yaml_body = yamlencode({ + apiVersion = "actions.summerwind.dev/v1alpha1" + kind = "RunnerDeployment" + metadata = { + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } + spec = { + replicas = 2 + template = { + spec = { + repository = "infinite-automations/terraform-helm-github-actions-runner-controller" + labels = var.labels + serviceAccountName = kubernetes_service_account.runner.metadata.0.name + containerMode = "kubernetes" + workVolumeClaimTemplate = { + storageClassName = "standard" + accessModes = [ + "ReadWriteOnce" + ] + resources = { + requests = { + storage = "100Mi" + } + } + } + } + } + } + }) + depends_on = [ + module.actions-runner-controller, + kubernetes_service_account.runner + ] +} +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13 | +| [helm](#requirement\_helm) | >= 2.11.0 | +| [kubectl](#requirement\_kubectl) | >= 1.14.0 | +| [kubernetes](#requirement\_kubernetes) | >= 2.23.0 | + +## Providers + +| Name | Version | +|------|---------| +| [helm](#provider\_helm) | 2.12.1 | +| [kubectl](#provider\_kubectl) | 1.14.0 | +| [kubernetes](#provider\_kubernetes) | 2.24.0 | + +## Resources + +| Name | Type | +|------|------| +| [helm_release.cert_manager](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [kubectl_manifest.runner](https://registry.terraform.io/providers/gavinbunney/kubectl/latest/docs/resources/manifest) | resource | +| [kubernetes_namespace.cert_manager](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | +| [kubernetes_role.runner](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/role) | resource | +| [kubernetes_role_binding.runner](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/role_binding) | resource | +| [kubernetes_secret.runner](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | +| [kubernetes_service_account.runner](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service_account) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [github\_app\_id](#input\_github\_app\_id) | GitHub App ID | `string` | n/a | yes | +| [github\_app\_install\_id](#input\_github\_app\_install\_id) | GitHub App Install ID | `string` | n/a | yes | +| [github\_app\_private\_key](#input\_github\_app\_private\_key) | GitHub App Private Key | `string` | n/a | yes | +| [labels](#input\_labels) | The labels for the runner | `list(string)` | n/a | yes | + + + + + \ No newline at end of file diff --git a/examples/full/backend.tf b/examples/full/backend.tf new file mode 100644 index 0000000..e6a0e2e --- /dev/null +++ b/examples/full/backend.tf @@ -0,0 +1,5 @@ + +terraform { + backend "local" { + } +} diff --git a/examples/full/cert-manager.tf b/examples/full/cert-manager.tf new file mode 100644 index 0000000..6d6c182 --- /dev/null +++ b/examples/full/cert-manager.tf @@ -0,0 +1,24 @@ +# create namespace for cert mananger +resource "kubernetes_namespace" "cert_manager" { + metadata { + labels = { + "name" = "cert-manager" + } + name = "cert-manager" + } +} + +# install cert-manager helm chart using terraform +resource "helm_release" "cert_manager" { + name = "cert-manager" + repository = "https://charts.jetstack.io" + chart = "cert-manager" + version = "v1.12.3" + namespace = kubernetes_namespace.cert_manager.metadata[0].name + atomic = true + timeout = 600 + set { + name = "installCRDs" + value = "true" + } +} diff --git a/examples/full/main.tf b/examples/full/main.tf new file mode 100644 index 0000000..e016d5e --- /dev/null +++ b/examples/full/main.tf @@ -0,0 +1,19 @@ +# setup actions-runner-controller +module "actions-runner-controller" { + source = "../.." + namespace = "github-actions-runner-controller" + create_namespace = true + allow_granting_container_mode_permissions = false + github_app_id = var.github_app_id + github_app_install_id = var.github_app_install_id + github_app_private_key = var.github_app_private_key + kubernetes_secret_name = "github-auth-secret" + helm_deployment_name = "actions-runner-controller" + helm_chart_version = "0.23.5" + replicas = 1 + atomic = true + timeout = 600 + depends_on = [ + helm_release.cert_manager + ] +} diff --git a/examples/full/providers.tf b/examples/full/providers.tf new file mode 100644 index 0000000..7b2baac --- /dev/null +++ b/examples/full/providers.tf @@ -0,0 +1,15 @@ +provider "kubernetes" { + config_path = "~/.kube/config" +} + +provider "helm" { + kubernetes { + config_path = "~/.kube/config" + } +} + +provider "kubectl" { + config_path = "~/.kube/config" + load_config_file = true + apply_retry_count = 3 +} diff --git a/examples/full/runner.tf b/examples/full/runner.tf new file mode 100644 index 0000000..4464a1e --- /dev/null +++ b/examples/full/runner.tf @@ -0,0 +1,104 @@ +# create runner +resource "kubernetes_service_account" "runner" { + metadata { + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } + secret { + name = kubernetes_secret.runner.metadata[0].name + } +} + +resource "kubernetes_secret" "runner" { + metadata { + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } +} + +resource "kubernetes_role" "runner" { + metadata { + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } + + rule { + api_groups = [""] + resources = ["pods"] + verbs = ["get", "list", "create", "delete"] + } + rule { + api_groups = [""] + resources = ["pods/exec"] + verbs = ["get", "create"] + } + rule { + api_groups = [""] + resources = ["pods/log"] + verbs = ["get", "list", "watch"] + } + rule { + api_groups = ["batch"] + resources = ["jobs"] + verbs = ["get", "list", "create", "delete"] + } + rule { + api_groups = [""] + resources = ["secrets"] + verbs = ["create", "delete", "get", "list"] + } +} + +resource "kubernetes_role_binding" "runner" { + metadata { + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "Role" + name = "test-runner" + } + subject { + kind = "ServiceAccount" + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } +} + +resource "kubectl_manifest" "runner" { + yaml_body = yamlencode({ + apiVersion = "actions.summerwind.dev/v1alpha1" + kind = "RunnerDeployment" + metadata = { + name = "test-runner" + namespace = module.actions-runner-controller.namespace + } + spec = { + replicas = 2 + template = { + spec = { + repository = "infinite-automations/terraform-helm-github-actions-runner-controller" + labels = var.labels + serviceAccountName = kubernetes_service_account.runner.metadata[0].name + containerMode = "kubernetes" + workVolumeClaimTemplate = { + storageClassName = "standard" + accessModes = [ + "ReadWriteOnce" + ] + resources = { + requests = { + storage = "100Mi" + } + } + } + } + } + } + }) + depends_on = [ + module.actions-runner-controller, + kubernetes_service_account.runner + ] +} diff --git a/examples/full/variables.tf b/examples/full/variables.tf new file mode 100644 index 0000000..6811cf3 --- /dev/null +++ b/examples/full/variables.tf @@ -0,0 +1,22 @@ +variable "github_app_id" { + type = string + description = "GitHub App ID" + sensitive = true +} + +variable "github_app_install_id" { + type = string + description = "GitHub App Install ID" + sensitive = true +} + +variable "github_app_private_key" { + type = string + description = "GitHub App Private Key" + sensitive = true +} + +variable "labels" { + type = list(string) + description = "The labels for the runner" +} diff --git a/examples/full/versions.tf b/examples/full/versions.tf new file mode 100644 index 0000000..8b3c938 --- /dev/null +++ b/examples/full/versions.tf @@ -0,0 +1,17 @@ +terraform { + required_version = ">= 0.13" + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.23.0" + } + helm = { + source = "hashicorp/helm" + version = ">= 2.11.0" + } + kubectl = { + source = "gavinbunney/kubectl" + version = ">= 1.14.0" + } + } +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..2a90475 --- /dev/null +++ b/main.tf @@ -0,0 +1,52 @@ +locals { + namespace = var.create_namespace ? kubernetes_namespace.this[0].metadata[0].name : data.kubernetes_namespace.this[0].metadata[0].name +} + +data "kubernetes_namespace" "this" { + count = var.create_namespace ? 0 : 1 + metadata { + name = var.namespace + } +} + +resource "kubernetes_namespace" "this" { + count = var.create_namespace ? 1 : 0 + metadata { + name = var.namespace + } +} + +resource "kubernetes_secret" "this" { + metadata { + name = var.kubernetes_secret_name + namespace = local.namespace + } + + data = { + github_app_id = var.github_app_id + github_app_installation_id = var.github_app_install_id + github_app_private_key = var.github_app_private_key + } +} + +resource "helm_release" "this" { + name = var.helm_deployment_name + repository = "https://actions-runner-controller.github.io/actions-runner-controller" + chart = "actions-runner-controller" + version = var.helm_chart_version + namespace = local.namespace + + atomic = var.atomic + timeout = var.timeout + + values = [yamlencode({ + replicaCount = var.replicas + authSecret = { + name = kubernetes_secret.this.metadata[0].name + create = false + } + rbac = { + allowGrantingKubernetesContainerModePermissions = var.allow_granting_container_mode_permissions + } + })] +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..da22710 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,14 @@ +output "namespace" { + value = local.namespace + description = "The namespace runner controller was deployed into" +} + +output "secret_name" { + value = kubernetes_secret.this.metadata[0].name + description = "The name of the secret created" +} + +output "helm_deployment_name" { + value = helm_release.this.metadata[0].name + description = "The name of the helm deployment" +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..0365a86 --- /dev/null +++ b/variables.tf @@ -0,0 +1,71 @@ +variable "namespace" { + type = string + description = "The namespace to deploy the runner controller into" + default = "github-actions-runner-controller" +} + +variable "create_namespace" { + type = bool + description = "If true, the namespace will be created" + default = true +} + +variable "allow_granting_container_mode_permissions" { + type = bool + description = "If true, the runner controller will be allowed to grant container mode permissions" + default = false +} + +variable "github_app_id" { + type = string + description = "GitHub App ID" + sensitive = true +} + +variable "github_app_install_id" { + type = string + description = "GitHub App Install ID" + sensitive = true +} + +variable "github_app_private_key" { + type = string + description = "GitHub App Private Key" + sensitive = true +} + +variable "kubernetes_secret_name" { + type = string + description = "The name of the secret to create" + default = "github-auth-secret" +} + +variable "helm_deployment_name" { + type = string + description = "The name of the helm deployment" + default = "actions-runner-controller" +} + +variable "helm_chart_version" { + type = string + description = "The version of the helm chart to deploy" + default = "0.23.5" +} + +variable "replicas" { + type = number + description = "The number of replicas for the runner controller" + default = 3 +} + +variable "atomic" { + type = bool + description = "If true, installation process purges chart on fail. If false, installation process deletes resources created by chart, but not purge them" + default = true +} + +variable "timeout" { + type = number + description = "Time in seconds to wait for helm deployment operation (like Jobs for hooks)" + default = 600 +} diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..2f4493d --- /dev/null +++ b/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 0.13" + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.23.0" + } + helm = { + source = "hashicorp/helm" + version = ">= 2.11.0" + } + } +}