diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b628db6..7d3c40a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -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 }} diff --git a/README.md b/README.md index 08f5182..d266bf0 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 --- @@ -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 @@ -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 diff --git a/action.yaml b/action.yaml new file mode 100644 index 0000000..5ff1f01 --- /dev/null +++ b/action.yaml @@ -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 diff --git a/terraform/config.tf b/terraform/config.tf index ac88258..91901d4 100644 --- a/terraform/config.tf +++ b/terraform/config.tf @@ -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