Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,23 @@ concurrency:
jobs:
terraform:
name: Terraform
uses: ./.github/workflows/terraform.yaml
runs-on: ubuntu-latest
permissions:
contents: read
with:
aws_bucket: ${{ vars.AWS_TF_BUCKET }}
aws_endpoint_url_s3: ${{ vars.AWS_ENDPOINT_URL_S3 }}
gh_tf_owner: ${{ vars.GH_TF_OWNER }}
gh_tf_app_id: ${{ vars.GH_TF_APP_ID }}
gh_tf_app_installation_id: ${{ vars.GH_TF_APP_INSTALLATION_ID }}
path: test.yaml
secrets:
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
gh_tf_app_pem_file: ${{ secrets.GH_TF_APP_PEM_FILE }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Terraform
uses: ./
with:
path: test.yaml
owner: ${{ vars.GH_TF_OWNER }}
app-id: ${{ vars.GH_TF_APP_ID }}
app-installation-id: ${{ vars.GH_TF_APP_INSTALLATION_ID }}
app-pem-file: ${{ secrets.GH_TF_APP_PEM_FILE }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-bucket: ${{ vars.AWS_TF_BUCKET }}
aws-endpoint-url-s3: ${{ vars.AWS_ENDPOINT_URL_S3 }}
73 changes: 57 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ GitOps workflow turning a declarative YAML organization definition into GitHub r

- **Automated GitHub Organization management** - Define repositories using simple YAML file.
- **Repository metadata** - Define description, homepage URL, topics.
- **Reusable GitOps Workflow** - Manage configurations using pull requests and automate updates using GitHub Actions.
- **GitOps Composite Action** - Manage configurations using pull requests and automate updates using a [composite action](action.yaml).
- **Terraform** - Uses Terraform under the hood to apply changes efficiently.
- **Terraform State Management** - Stores Terraform state securely in AWS S3.
- **GitHub App Integration** - Uses a GitHub App for authentication and API interactions.
Expand Down Expand Up @@ -55,9 +55,9 @@ repositories:
- name: .github
```

### GitHub Workflow
### Use Terraform Action

Create the workflow:
Create a workflow, for example, `.github/workflows/github-organization-as-code.yaml`:

```yaml
---
Expand All @@ -68,22 +68,34 @@ on:
branches:
- main

concurrency:
group: ${{ github.workflow }}

jobs:
call-terraform:
uses: bruzit/github-organization-as-code/.github/workflows/terraform.yaml@v0
with:
aws_bucket: ${{ vars.AWS_TF_BUCKET }}
aws_endpoint_url_s3: ${{ vars.AWS_ENDPOINT_URL_S3 }}
gh_tf_owner: ${{ vars.GH_TF_OWNER }}
gh_tf_app_id: ${{ vars.GH_TF_APP_ID }}
gh_tf_app_installation_id: ${{ vars.GH_TF_APP_INSTALLATION_ID }}
path: config.yaml
secrets:
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
gh_tf_app_pem_file: ${{ secrets.GH_TF_APP_PEM_FILE }}
terraform:
name: Terraform
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Terraform
uses: bruzit/github-organization-as-code@v0
with:
path: config.yaml
owner: ${{ vars.GH_TF_OWNER }}
app-id: ${{ vars.GH_TF_APP_ID }}
app-installation-id: ${{ vars.GH_TF_APP_INSTALLATION_ID }}
app-pem-file: ${{ secrets.GH_TF_APP_PEM_FILE }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-bucket: ${{ vars.AWS_TF_BUCKET }}
aws-endpoint-url-s3: ${{ vars.AWS_ENDPOINT_URL_S3 }}
```

The [action](action.yaml) runs the Terraform code shipped with the action against the configuration file at `path`, relative to the workspace, so the caller checks out its repository first. It sets up the latest Terraform, checks formatting, initializes the S3 backend in `aws-bucket`, selects the workspace named after `owner`, validates, and applies with `-auto-approve`. `concurrency` queues pushes instead of failing the apply on the state lock.

Set up GitHub actions, variables and secrets:

- GitHub / _Repository_ / Settings
Expand All @@ -101,6 +113,35 @@ Set up GitHub actions, variables and secrets:
- `AWS_ENDPOINT_URL_S3`
- `AWS_TF_BUCKET` (S3 bucket name for Terraform state)

### Use Terraform Workflow

Similar to [Use Terraform Action](#use-terraform-action), with the reusable workflow:

```yaml
---
name: GitHub Organization as Code

on:
push:
branches:
- main

jobs:
call-terraform:
uses: bruzit/github-organization-as-code/.github/workflows/terraform.yaml@v0
with:
path: config.yaml
gh_tf_owner: ${{ vars.GH_TF_OWNER }}
gh_tf_app_id: ${{ vars.GH_TF_APP_ID }}
gh_tf_app_installation_id: ${{ vars.GH_TF_APP_INSTALLATION_ID }}
aws_bucket: ${{ vars.AWS_TF_BUCKET }}
aws_endpoint_url_s3: ${{ vars.AWS_ENDPOINT_URL_S3 }}
secrets:
gh_tf_app_pem_file: ${{ secrets.GH_TF_APP_PEM_FILE }}
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
```

## Usage

### GitHub Organization Configuration YAML
Expand Down
74 changes: 74 additions & 0 deletions action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
name: GitHub Organization as Code
description: Apply a GitHub organization YAML configuration with Terraform, authenticated by a GitHub App.
inputs:
path:
required: true
description: Organization configuration YAML, relative to the workspace.
owner:
required: true
description: GitHub organization to manage, also the Terraform workspace.
app-id:
required: true
description: GitHub App ID.
app-installation-id:
required: true
description: GitHub App installation ID.
app-pem-file:
required: true
description: GitHub App private key (PEM file content).
aws-access-key-id:
required: true
description: S3 access key ID.
aws-secret-access-key:
required: true
description: S3 secret access key.
aws-bucket:
required: true
description: S3 bucket name for Terraform state.
aws-endpoint-url-s3:
required: true
description: S3 endpoint URL.
runs:
using: composite
steps:
- name: Set up Terraform
uses: hashicorp/setup-terraform@v4
- name: Terraform fmt
shell: bash
run: terraform -chdir="$GITHUB_ACTION_PATH/terraform" fmt -check
- name: Terraform init
shell: bash
env:
AWS_ACCESS_KEY_ID: ${{ inputs.aws-access-key-id }}
AWS_SECRET_ACCESS_KEY: ${{ inputs.aws-secret-access-key }}
AWS_BUCKET: ${{ inputs.aws-bucket }}
AWS_ENDPOINT_URL_S3: ${{ inputs.aws-endpoint-url-s3 }}
TF_WORKSPACE: ${{ inputs.owner }}
TF_IN_AUTOMATION: true
run: terraform -chdir="$GITHUB_ACTION_PATH/terraform" init -input=false -backend-config="bucket=$AWS_BUCKET"
- name: Terraform validate
shell: bash
env:
GITHUB_APP_ID: ${{ inputs.app-id }}
GITHUB_APP_INSTALLATION_ID: ${{ inputs.app-installation-id }}
GITHUB_APP_PEM_FILE: |
${{ inputs.app-pem-file }}
TF_WORKSPACE: ${{ inputs.owner }}
TF_IN_AUTOMATION: true
run: terraform -chdir="$GITHUB_ACTION_PATH/terraform" validate
- name: Terraform apply
shell: bash
env:
CONFIG_PATH: ${{ inputs.path }}
GITHUB_OWNER: ${{ inputs.owner }}
GITHUB_APP_ID: ${{ inputs.app-id }}
GITHUB_APP_INSTALLATION_ID: ${{ inputs.app-installation-id }}
GITHUB_APP_PEM_FILE: |
${{ inputs.app-pem-file }}
AWS_ACCESS_KEY_ID: ${{ inputs.aws-access-key-id }}
AWS_SECRET_ACCESS_KEY: ${{ inputs.aws-secret-access-key }}
AWS_ENDPOINT_URL_S3: ${{ inputs.aws-endpoint-url-s3 }}
TF_WORKSPACE: ${{ inputs.owner }}
TF_IN_AUTOMATION: true
run: TF_VAR_config="$GITHUB_WORKSPACE/$CONFIG_PATH" terraform -chdir="$GITHUB_ACTION_PATH/terraform" apply -auto-approve -input=false
2 changes: 1 addition & 1 deletion terraform/config.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ terraform {
}

backend "s3" {
# bucket is supplied at init time via -backend-config (see .github/workflows/terraform.yaml)
# bucket is supplied at init time via -backend-config (see action.yaml)
workspace_key_prefix = ""
key = "terraform.tfstate"
use_lockfile = true # Set to false only for non-AWS S3 compatible APIs without "conditional object PUTs" capability
Expand Down
Loading