Skip to content

feat(ci): deploy from the default branch and offer Postgres to JVM tests - #154

Merged
Alexanderamiri merged 2 commits into
mainfrom
feat/moresleep-ci
Oct 26, 2026
Merged

Alexanderamiri merged 2 commits into
mainfrom
feat/moresleep-ci

Conversation

@Alexanderamiri

@Alexanderamiri Alexanderamiri commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Why

Onboarding javaBin/moresleep (sleepingpill) without architectural changes hits two pipeline gaps:

  1. Deploy jobs in javabin.yml were gated on github.ref == 'refs/heads/main'. moresleep's default branch is master, so it would build and plan but never apply or deploy.
  2. build-jvm runs mvn verify with no database. moresleep's test suite needs a real PostgreSQL on localhost:5432, which is common for JVM apps.

What

  • javabin.yml: the docker-build, tf-apply, ecs-deploy and eb-deploy gates are now github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch). The full ref is compared (review fix), so a tag named like the default branch (refs/tags/master) never deploys; the broker/apply-gate doesn't check the app ref, so this gate is the only branch control. Repos on main behave exactly as before.
  • tf-plan.yml: the HIGH-risk Slack NOTIFY flag uses the same default-branch test.
  • build-jvm.yml: an optional services.postgres container. Its image comes from the new test_postgres_image input; GitHub skips a service whose image is an empty string (actions/runner#822), so nothing changes for existing callers. Its health check is pg_isready -h 127.0.0.1, with no input interpolated into the docker options string (review fix; 127.0.0.1 also skips the socket-only server the image runs while initialising). The export step fails early unless test_postgres_db / test_postgres_user are plain identifiers and the password has no newline, because the values are written to $GITHUB_ENV. The build step gets PGHOST/PGPORT/PGDATABASE/PGUSER/PGPASSWORD.
  • javabin.yml passes new inputs through: test_postgres_image, test_postgres_db, test_postgres_user, test_postgres_password (each defaults to test), and the existing maven_args / gradle_args that build-jvm already accepted but the orchestrator never passed on.
  • Docs: docs/reusable-workflows.md (inputs, default-branch rule, a Postgres example) and one line in docs/app-yaml-reference.md.

Not changed: OIDC trust and the broker/apply-gate still pin the platform workflow refs to javaBin/platform@refs/heads/main. They already accept any app repo branch (sub = repo:javaBin/*:*), so nothing in IAM depends on the app's branch name.

How verified

  • actionlint on javabin.yml, build-jvm.yml and tf-plan.yml: clean.
  • github.event.repository.default_branch is in the payload for push and workflow_dispatch, which are the only events that can reach the deploy jobs (pull requests are excluded explicitly).
  • End-to-end proof comes with the moresleep stack PR that calls javabin.yml@main with test_postgres_image: postgres:17.

Follow-ups

  • Companion PR: feat(app-yaml): adopt an existing RDS instance and Route53 record (independent; no shared files).
  • moresleep migration plan: inventory, cutover and rollback (kept outside the repo).

🤖 Generated with Claude Code

@Alexanderamiri
Alexanderamiri requested a review from a team as a code owner October 24, 2026 12:45
@github-actions

Copy link
Copy Markdown

Terraform Plan

✅ No changes — infrastructure is up to date.

Plan output
Acquiring state lock. This may take a few moments...
aws_ssm_parameter.atlassian_org_id: Refreshing state... [id=/javabin/platform/atlassian-org-id]
aws_ssm_parameter.atlassian_api_key: Refreshing state... [id=/javabin/platform/atlassian-api-key]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

@github-actions

Copy link
Copy Markdown

Terraform Plan

✅ No changes — infrastructure is up to date.

Plan output
Acquiring state lock. This may take a few moments...
aws_ssm_parameter.atlassian_org_id: Refreshing state... [id=/javabin/platform/atlassian-org-id]
aws_ssm_parameter.atlassian_api_key: Refreshing state... [id=/javabin/platform/atlassian-api-key]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

Alexanderamiri and others added 2 commits October 26, 2026 15:19
Deploy jobs in javabin.yml were gated on refs/heads/main, so repos whose
default branch is master (javaBin/moresleep) could never deploy. Gate on
github.event.repository.default_branch instead, for pushes and manual runs.

build-jvm gains an optional PostgreSQL service container
(test_postgres_image/_db/_user/_password), passed through javabin.yml
together with maven_args/gradle_args, for test suites that need a real
database.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…es inputs

Compare github.ref with refs/heads/<default branch> so a tag named like the
default branch never deploys. Stop interpolating test_postgres_* into docker
service options, and reject non-identifier db/user values before they reach
GITHUB_ENV.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Terraform Plan

🚧 Changes detected — Plan: 0 to add, 1 to change, 0 to destroy.

Plan output
Acquiring state lock. This may take a few moments...
aws_ssm_parameter.atlassian_api_key: Refreshing state... [id=/javabin/platform/atlassian-api-key]
aws_ssm_parameter.atlassian_org_id: Refreshing state... [id=/javabin/platform/atlassian-org-id]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # module.identity.aws_cognito_identity_provider.internal_google[0] will be updated in-place
  ~ resource "aws_cognito_identity_provider" "internal_google" {
        id                = "eu-central-1_Icikv3dtD:Google"
      ~ provider_details  = {
          ~ "authorize_url"                 = "https://accounts.google.com/o/oauth2/v2/auth" -> "https://accounts.google.com/o/oauth2/v2/auth?hd=java.no&prompt=select_account"
            # (8 unchanged elements hidden)
        }
        # (5 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

LLM Review

Risk: 🟢 LOW

Plan makes one in-place update to a Cognito Google identity provider, changing only the authorize_url to add an hd=java.no domain hint and prompt=select_account parameter; no creates or destroys.

  • ✅ [routine] Single in-place update (0 to add, 1 to change, 0 to destroy) to module.identity.aws_cognito_identity_provider.internal_google[0]. Resource is not replaced and no data is lost.
  • 🔒 [security] The hd=java.no parameter is a domain hint that asks Google to restrict or prefer java.no Google Workspace accounts at sign-in. This is a positive change for an internal login flow. Note that hd is a UI hint and is not a server-side enforcement control; Google will still accept accounts from other domains if the user chooses them, so enforcement depends on the app or post-auth checks.
  • ✅ [routine] prompt=select_account forces the Google account chooser to appear on each sign-in, which is a UX change with no infrastructure impact.
  • 💰 [cost] No new billable resources are created. No cost impact.

@Alexanderamiri
Alexanderamiri merged commit f936d67 into main Oct 26, 2026
4 checks passed
@Alexanderamiri
Alexanderamiri deleted the feat/moresleep-ci branch October 26, 2026 14:21
Alexanderamiri added a commit that referenced this pull request Oct 26, 2026
## Why

Today app.yaml can only **create** resources. `javaBin/moresleep`
(sleepingpill.javazone.no) is moving onto the platform with no
architectural change, so it has to keep two existing things:

- RDS `dbmoresleep` (Postgres 17, public endpoint, serves the program
API since 2021). Not in any Terraform state.
- The hand-made Route53 A record `sleepingpill.javazone.no`.
`routing.host` would fail on apply because the record already exists.

moresleep also reads `DBHOST` and `DBPORT` separately, but the RDS
modules only expose `endpoint` (`host:port`).

## What

app.yaml (validated in `scripts/generate-modules.py`):

```yaml
routing:
  host: sleepingpill.javazone.no
  priority: 120
  adopt_dns: true              # take over the existing record
resources:
  databases:
    - name: main
      engine: postgres
      adopt: dbmoresleep       # import, never create
      host_env: DBHOST         # new, postgres only (created or adopted)
      port_env: DBPORT
      deletion_protection: true          # default true
      backup_retention_period: 14        # optional; unset = keep the live value
```

- **`databases[].adopt`** generates `module "rds_<name>"` from the new
**`terraform/modules/service-rds-adopted`**, plus:
  ```hcl
  import {
    to = module.rds_main.aws_db_instance.this
    id = "dbmoresleep"
  }
  ```
- The module manages only tags, `deletion_protection`, an optional
`backup_retention_period`, and the Terraform-only final-snapshot
settings: `skip_final_snapshot=false`, a final snapshot id,
`delete_automated_backups=false`.
- `instance_class` (required by the provider) is read from the live
instance. Every other argument is in `ignore_changes`, and
`prevent_destroy` is set.
- `instance_class`, `allocated_storage`, `engine_version` and `multi_az`
are rejected together with `adopt`. `adopt` is rejected for dynamodb or
an invalid identifier.
- No task-role policy is added, because the app keeps its own password
auth.
- Once the resource is in state, Terraform skips the import block, so it
stays in the generated file.
- **`host_env` / `port_env`** map to `address` / `port` outputs.
`service-rds` gains an `address` output, and the adopted module's `port`
is a string.
- **`routing.adopt_dns: true`** passes `allow_dns_overwrite = true` to
`service-routing`, which sets `allow_overwrite` on the record, so
Route53 UPSERTs the existing record instead of failing on CREATE. The
value is `null` when unset, so existing apps' records and generated
files do not change.
- I chose this over an `import` block on purpose. moresleep runs in
parallel on `moresleep.javazone.no` first and then changes `host`. At
that point the routing record address is already in state, so Terraform
would skip an import and the replacement CREATE would fail.
`allow_overwrite` covers both the fresh case and the host-change case.
- **Each adoption is approved in a platform PR (review fix).** `adopt`
and `adopt_dns` only validate when the identifier or host is listed for
the app's team in `ADOPTIONS` in `scripts/registry.py`. This PR lists
`moresleep: rds dbmoresleep, dns sleepingpill.javazone.no`. Why: Route53
records in the shared zones have no team ABAC, and `DenyXTeam` only
fires on resources tagged with another team. Without the allowlist, any
team could take over another app's hostname (`host: submit.javazone.no`
+ `adopt_dns: true`), or adopt, tag away and later delete an untagged
legacy database with one app.yaml line. The generator always runs from
platform `main`, so a team can't edit the list itself.
- The adopted instance's final snapshot is
`<identifier>-final-<random_id hex>` (stable per state), so a later
delete/re-adopt cycle can't collide with an old snapshot. The module
pins `hashicorp/aws >= 5.40, < 6.0` because `dedicated_log_volume` is in
`ignore_changes`.
- Docs: `app-yaml-reference.md` (an "Adopting an existing PostgreSQL
instance" section, `adopt_dns`, `host_env`/`port_env`),
`reusable-modules.md`, `terraform/modules/README.md` and the module
table in `CLAUDE.md`.
- `scripts/registry.py`: `MODULE_SOURCES` gains `service-rds-adopted`.

## How verified

- `python3 -m unittest discover -s scripts/tests`: 59 tests OK, 16 of
them new in `AdoptTest` (3 cover the approval list: another team's
database, another team adopting `dbmoresleep`, an unapproved host). The
existing fmt test now also runs on the moresleep fixture.
`terraform/lambda-src/tests`: 64 OK.
- `terraform fmt -check -recursive terraform/modules` is clean.
`terraform validate` passes for `service-rds-adopted`, `service-routing`
and `service-rds` (`init -backend=false`).
- **A local, read-only `terraform plan` against the real account**
(local backend, admin profile, never applied):
  - Minimal root with the module plus the import block:
`Plan: 1 to import, 0 to add, 1 to change, 0 to destroy` (before the
review fix; the `random_id` suffix now adds `1 to add`). The only diffs
are `deletion_protection false -> true`, `tags_all` (+5 default tags),
and the Terraform-only `skip_final_snapshot`,
`final_snapshot_identifier` and `delete_automated_backups`. Engine,
class, storage, security group, publicity, PI and monitoring are
untouched.
- The generated moresleep phase-1 root (`moresleep.javazone.no`, all
modules, local module sources):
`Plan: 1 to import, 22 to add, 1 to change, 0 to destroy`. It stops only
at `reading IAM Role (javabin-ecs-exec-moresleep)`, which is created
when the `moresleep` team is registered (javaBin/registry#40).
- This PR's own Terraform Plan / Unit Tests checks are below.

## Follow-ups

- Companion PR #154: deploy from the default branch
(moresleep uses `master`) and Postgres for JVM tests.
- Security workstream: `dbmoresleep` sits in the default VPC with the
default SG open to `0.0.0.0/0`. This PR deliberately leaves the SG
alone.
- An adopted instance is only reachable if its network allows the
platform NAT. This is documented, not automated. The docs now say to
allow only the NAT address (plus legacy clients) on a public instance's
security group, never `0.0.0.0/0`. All traffic to such an instance goes
through the NAT and pays data processing.
- Tagging the remaining untagged legacy resources (so `DenyXTeam`
protects them) is still a security-workstream item. The approval list
closes the app.yaml path, not hand-written Terraform.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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