Skip to content

feat!: support github-ops-app v1, build with buildkit, and keep secrets in ssm - #7

Merged
sgtoj merged 4 commits into
mainfrom
refactor/v1
Nov 7, 2026
Merged

sgtoj merged 4 commits into
mainfrom
refactor/v1

Conversation

@sgtoj

@sgtoj sgtoj commented Nov 7, 2026

Copy link
Copy Markdown
Member

Summary

This updates the module to deploy github-ops-app v1 (cruxstack/github-ops-app#28). It brings the module in line with the recent cruxstack Terraform module refactors, and fixes several bugs in the module itself. It contains breaking changes and will release as v1.0.0; the README's "Upgrading to v1" section lists them.

Merge order: app_version defaults to v1.0.0, the tag github-ops-app#28 will release. Merge and release that PR first, or a default build will fail to find the tag.

The PR has three commits:

  • ci: pinned actions; terraform fmt, validate, and test plus tflint and example validation on every PR; dependabot (there was no config); and a fix to the release rules that turned breaking changes into minor versions.
  • feat!: the module changes below.
  • docs: the README.

Build

  • The function is built with the cruxstack/buildkit provider instead of the docker-based artifact packager, as in the other modules. A removed block forgets the packager's resources from existing state instead of destroying them.
  • source_code_hash is now set. In v0 a rebuilt binary never redeployed, because the function's file path didn't change.
  • The binary is built for the function's architecture, now arm64 by default. v0 always built amd64, so an arm64 function couldn't start.
  • The app version is pinned to v1.0.0, built with Go 1.27 and the lambda.norpc tag. GOTOOLCHAIN=auto lets newer app versions build.

github-ops-app v1

  • Okta key ID: passed as APP_OKTA_PRIVATE_KEY_ID. v0 set APP_OKTA_KEY_ID, which the app never read.
  • Sync rules: typed and validated like the app validates them, with unset fields left out of the JSON. The v0 example's rules used fields the app ignores, so they did nothing.
  • github_user_field: defaults to githubUsername, as in the app. The old default, login, is usually an email address.
  • Installation ID: installation_id is optional.
  • New inputs and settings: base_url (GitHub Enterprise Server), Okta scopes, and log_level, plus APP_PUBLIC_URL, which the module sets for the app.
  • Installer: with installer_config.enabled, the function serves /setup, creates the GitHub App, and saves its credentials to SSM under the prefix. The role may write only there.

Secrets

Each secret the module passes to the function is now an SSM SecureString parameter, and the function gets its ARN. Before, the GitHub and Okta private keys and the webhook secret, Slack token, and admin token were plaintext environment variables that anyone with lambda:GetFunctionConfiguration could read. Customer managed keys are supported. A secret given as a parameter ARN is passed as-is and made readable. ssm_config.store_secrets = false restores plain variables.

Fixes

  • With stage_name = "$default", v0 set APP_BASE_PATH=/$default, which broke every route.
  • The webhook_url output had a double slash with $default.
  • The PR bypass footer note was only sent when a PR bypass channel was also set.
  • A disabled module (enabled = false) failed to plan.
  • Enabling a schedule without its feature is now an error.

The default timeout is now 600 seconds so a full sync can finish. API Gateway routes are throttled, and CORS is removed because webhooks don't use it.

Testing

  • terraform test passes all 15 runs with mocked providers. They cover the environment mapping for each feature, the secret storage modes, installer mode and its IAM, the $default stage, a disabled module, and validation errors.
  • terraform fmt -check, terraform validate, and tflint 0.63.1 are clean, and the complete example validates.
  • The Dockerfile was built for real against github-ops-app#28's branch. It produced a package.zip with a static arm64 bootstrap.
  • I have not applied the module to an AWS account.

🤖 Generated with Claude Code

sgtoj and others added 4 commits November 7, 2026 10:04
… bumps

Every action is pinned to a commit of its latest release, and checkouts
don't persist credentials. Tests run terraform fmt, validate, and test,
validate the complete example, and lint with tflint, on every pull
request.

The release rules listed feat:minor and refactor:minor. A custom rule that
matches a commit's type wins over the commit analyzer's "breaking change
-> major" rule, so feat! and refactor! commits were released as minor
versions. Those rules are removed, and a release is only created when the
action computes a new tag.

The semantic check no longer checks out code. It runs on
pull_request_target, with a token that can write to the repository, and
the action doesn't need the code.

Dependabot had no configuration. It now updates the actions, the
Terraform providers, and the build's base images weekly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts in ssm

Deploy github-ops-app v1 and fix the module's own bugs.

Build:
- build the function with the cruxstack/buildkit provider instead of the
  docker-based artifact packager, and forget the packager's resources
  from the state with a removed block instead of destroying them
- set source_code_hash, so the function redeploys when its code changes;
  v0 never updated the code after the first apply
- build for the function's architecture, arm64 by default; v0 always
  built an amd64 binary, so an arm64 function couldn't start
- pin the app to v1.0.0, build with go 1.27 and the lambda.norpc tag, and
  let newer app versions download the toolchain they need
- require terraform >= 1.7, aws >= 6.72.0, buildkit >= 0.0.2, and
  random >= 3.9.1

github-ops-app v1:
- pass the okta key id as APP_OKTA_PRIVATE_KEY_ID; v0 set APP_OKTA_KEY_ID,
  which the app never read
- type and validate the okta sync rules like the app does, and leave unset
  fields out of the json; default github_user_field to githubUsername
- make installation_id optional, add base_url for github enterprise
  server, okta scopes, and log_level, and set APP_PUBLIC_URL
- support the app's installer: with installer_config, the function serves
  /setup and saves the github app's credentials to ssm under the prefix,
  and its role may write only there

Secrets:
- store each secret the module passes to the function as an ssm
  SecureString parameter, optionally with a customer managed key, and
  pass the parameter's arn; the role can read only those parameters
- pass a secret given as a parameter arn as is, and make it readable

Fixes:
- the $default stage set APP_BASE_PATH to /$default, which broke every
  route, and the webhook_url output had a double slash
- the pr bypass footer note was only sent with a pr bypass channel
- a disabled module failed to plan
- a schedule without its feature enabled is an error

Also: a 600 second timeout by default, so a full sync can finish; API
Gateway throttling; no CORS, which webhooks don't use; the eventbridge
event id is passed through; and terraform tests with mocked providers.

BREAKING CHANGE: bot_version, bot_repo, and bot_force_rebuild_id are
renamed app_version (pinned to v1.0.0), app_repo, and
app_force_rebuild_id; okta_config.key_id is private_key_id; sync rules
are typed; github_user_field defaults to githubUsername;
api_gateway_config.cors_allow_origins and lambda_config.runtime are
removed; the function runs on arm64 and keeps secrets in ssm by default.
github-ops-app v1 also needs two more github app permissions. See the
README's upgrade section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the requirements, including a BuildKit endpoint for the build
- usage with credentials and with the installer, and how secrets are kept
  in ssm or passed as parameter arns
- every input and output, and the okta sync rule fields
- an upgrade section with the renamed inputs and changed defaults

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing in the module uses them. They were kept only for the removed
block that forgets the pre-buildkit module.bot_artifact, whose resources
are still in the state of anyone upgrading from v0.x.

That block doesn't need the requirements: terraform init installs the
providers that the state refers to. Planning a v0-style state that holds
the packager's null_resource and random_string, with no provider
requirements in the configuration, installs archive, null, and random from
the state and forgets both resources without destroying them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sgtoj
sgtoj merged commit 8a6df89 into main Nov 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant