mirror of
https://github.com/Sea-Haven-Industries/payments-dashboard.git
synced 2026-09-30 05:23:12 +00:00
feat(ci): deploy Lambda zips through the org reusable (PLAT-79) (#119)
* feat(ci): deploy Lambda zips through the org reusable (PLAT-79) * fix(iam): trust only this account's deploy environment (PLAT-79)
This commit is contained in:
parent
0bf3c7121c
commit
30a1737345
9 changed files with 166 additions and 189 deletions
162
.github/workflows/deploy.yaml
vendored
162
.github/workflows/deploy.yaml
vendored
|
|
@ -1,8 +1,14 @@
|
|||
name: Deploy
|
||||
|
||||
# Terraform owns Lambda skeletons. This workflow ships zips to prod and calls
|
||||
# update-function-code. It never creates an HCP run. No GitHub Releases and no
|
||||
# tagging in this workflow.
|
||||
# Lambda zip CD. The org reusable builds the zips, uploads them, and calls
|
||||
# update-function-code. Terraform owns the functions and ignores code attributes.
|
||||
#
|
||||
# push to main -> dev, at github.sha
|
||||
# release: published -> prod, at the release tag
|
||||
# workflow_dispatch -> chosen environment at a chosen ref
|
||||
#
|
||||
# Releases are cut by a human with `gh release create vX.Y.Z --target main`.
|
||||
# Nothing here creates an HCP run.
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -10,11 +16,19 @@ on:
|
|||
paths-ignore:
|
||||
- "terraform/**"
|
||||
- "docs/**"
|
||||
- "README.md"
|
||||
- "SETUP.md"
|
||||
- "AGENTS.md"
|
||||
- "*.md"
|
||||
- ".github/workflows/ci.yaml"
|
||||
- ".github/workflows/labeler.yml"
|
||||
- ".github/workflows/dependency-review.yml"
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: "Target Environment"
|
||||
required: true
|
||||
type: choice
|
||||
options: [dev, prod]
|
||||
ref:
|
||||
description: "Git ref to build and deploy (tag, branch, or SHA). Empty means the workflow ref."
|
||||
required: false
|
||||
|
|
@ -25,119 +39,31 @@ permissions:
|
|||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy to prod
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
environment: prod
|
||||
concurrency:
|
||||
group: deploy-payments-dashboard-prod
|
||||
cancel-in-progress: false
|
||||
deploy-dev:
|
||||
name: Deploy to dev
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.environment == 'dev')
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/cd-hcp-lambda.yaml@ee5b843ca105422b679c3fdeb9eaa68c6e500a85 # v1.0.20
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
env:
|
||||
AWS_REGION: us-east-1
|
||||
DEPLOY_ROLE_ARN: ${{ vars.DEPLOY_ROLE_ARN }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
persist-credentials: false
|
||||
secrets: inherit
|
||||
with:
|
||||
environment: dev
|
||||
ref: ${{ inputs.ref }}
|
||||
ssm-prefix: /payments-dashboard/deploy
|
||||
function-keys: process_csv,slack_app_home,fetch_boa,expense_receiver,expense_processor
|
||||
|
||||
- name: Resolve commit
|
||||
id: commit
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sha="$(git rev-parse HEAD)"
|
||||
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
|
||||
echo "Building ${sha}"
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
|
||||
- name: Build function zips
|
||||
env:
|
||||
GIT_SHA: ${{ steps.commit.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node scripts/package_lambdas.mjs --git-sha "${GIT_SHA}" --out-dir build/packages
|
||||
python3 - <<'PY'
|
||||
import os, zipfile
|
||||
from pathlib import Path
|
||||
sha = os.environ["GIT_SHA"]
|
||||
names = [
|
||||
"process_csv",
|
||||
"slack_app_home",
|
||||
"fetch_boa",
|
||||
"expense_receiver",
|
||||
"expense_processor",
|
||||
]
|
||||
for name in names:
|
||||
path = Path("build/packages") / f"{name}.zip"
|
||||
if not path.is_file():
|
||||
raise SystemExit(f"missing {path}")
|
||||
with zipfile.ZipFile(path) as zf:
|
||||
info = zf.read("src/buildInfo.js").decode()
|
||||
if sha not in info:
|
||||
raise SystemExit(f"{path} missing GIT_SHA {sha}")
|
||||
if "src/processPaymentCsv.js" not in zf.namelist():
|
||||
raise SystemExit(f"{path} missing src/")
|
||||
print("zips ok")
|
||||
PY
|
||||
|
||||
- name: Configure AWS credentials using OIDC
|
||||
uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
|
||||
with:
|
||||
role-to-assume: ${{ env.DEPLOY_ROLE_ARN }}
|
||||
aws-region: us-east-1
|
||||
audience: sts.amazonaws.com
|
||||
|
||||
- name: Get deploy parameters
|
||||
id: deploy
|
||||
run: |
|
||||
set -euo pipefail
|
||||
prefix=/payments-dashboard/deploy
|
||||
ARTIFACTS_BUCKET=$(aws ssm get-parameter --name "${prefix}/artifacts-bucket" --query Parameter.Value --output text)
|
||||
{
|
||||
echo "artifacts_bucket=${ARTIFACTS_BUCKET}"
|
||||
echo "process_csv=$(aws ssm get-parameter --name "${prefix}/process_csv-function-name" --query Parameter.Value --output text)"
|
||||
echo "slack_app_home=$(aws ssm get-parameter --name "${prefix}/slack_app_home-function-name" --query Parameter.Value --output text)"
|
||||
echo "fetch_boa=$(aws ssm get-parameter --name "${prefix}/fetch_boa-function-name" --query Parameter.Value --output text)"
|
||||
echo "expense_receiver=$(aws ssm get-parameter --name "${prefix}/expense_receiver-function-name" --query Parameter.Value --output text)"
|
||||
echo "expense_processor=$(aws ssm get-parameter --name "${prefix}/expense_processor-function-name" --query Parameter.Value --output text)"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Upload zips and update function code
|
||||
env:
|
||||
ARTIFACTS_BUCKET: ${{ steps.deploy.outputs.artifacts_bucket }}
|
||||
GIT_SHA: ${{ steps.commit.outputs.sha }}
|
||||
PROCESS_CSV: ${{ steps.deploy.outputs.process_csv }}
|
||||
SLACK_APP_HOME: ${{ steps.deploy.outputs.slack_app_home }}
|
||||
FETCH_BOA: ${{ steps.deploy.outputs.fetch_boa }}
|
||||
EXPENSE_RECEIVER: ${{ steps.deploy.outputs.expense_receiver }}
|
||||
EXPENSE_PROCESSOR: ${{ steps.deploy.outputs.expense_processor }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
keys=(
|
||||
process_csv:"${PROCESS_CSV}"
|
||||
slack_app_home:"${SLACK_APP_HOME}"
|
||||
fetch_boa:"${FETCH_BOA}"
|
||||
expense_receiver:"${EXPENSE_RECEIVER}"
|
||||
expense_processor:"${EXPENSE_PROCESSOR}"
|
||||
)
|
||||
for pair in "${keys[@]}"; do
|
||||
name="${pair%%:*}"
|
||||
fn="${pair#*:}"
|
||||
key="functions/${name}/${GIT_SHA}.zip"
|
||||
aws s3 cp "build/packages/${name}.zip" "s3://${ARTIFACTS_BUCKET}/${key}"
|
||||
aws lambda update-function-code \
|
||||
--function-name "${fn}" \
|
||||
--s3-bucket "${ARTIFACTS_BUCKET}" \
|
||||
--s3-key "${key}" \
|
||||
--query '{Function:FunctionName,Sha256:CodeSha256,Updated:LastModified}' \
|
||||
--output table
|
||||
aws lambda wait function-updated-v2 --function-name "${fn}"
|
||||
done
|
||||
deploy-prod:
|
||||
name: Deploy to prod
|
||||
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.environment == 'prod')
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/cd-hcp-lambda.yaml@ee5b843ca105422b679c3fdeb9eaa68c6e500a85 # v1.0.20
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
secrets: inherit
|
||||
with:
|
||||
environment: prod
|
||||
ref: ${{ github.event.release.tag_name || inputs.ref }}
|
||||
ssm-prefix: /payments-dashboard/deploy
|
||||
function-keys: process_csv,slack_app_home,fetch_boa,expense_receiver,expense_processor
|
||||
ship-gate: true
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||

|
||||

|
||||
|
||||
HCP Terraform application that ingests payment CSVs, syncs check data with Bank of America CashPro APIs, surfaces an outstanding-payments dashboard in Slack, and routes expense approvals through a Slack reaction-driven workflow. Prod workspace: `payments-dashboard-prod` (trigger prefix `terraform/**`). Zip CD is GitHub Actions Environment `prod`.
|
||||
HCP Terraform application that ingests payment CSVs, syncs check data with Bank of America CashPro APIs, surfaces an outstanding-payments dashboard in Slack, and routes expense approvals through a Slack reaction-driven workflow. Workspaces `payments-dashboard-dev` and `payments-dashboard-prod` share tag `app:payments-dashboard` (trigger prefix `terraform/**`). Push to `main` deploys function zips to dev. A human GitHub Release deploys prod.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
|
|
|||
54
SETUP.md
54
SETUP.md
|
|
@ -1,13 +1,19 @@
|
|||
# Payments Dashboard — Setup Guide
|
||||
|
||||
Prod only. Workspace `payments-dashboard-prod` in project `seahaven-prod`
|
||||
(account `011934824531`). No seahaven-dev workspace.
|
||||
Two workspaces, one configuration, selected by HCP variable `environment`:
|
||||
|
||||
| Workspace | Project | Account | `environment` | `boa_base_url` |
|
||||
|-----------|---------|---------|---------------|----------------|
|
||||
| `payments-dashboard-prod` | `seahaven-prod` | `011934824531` | `prod` | `https://api.bofa.com` |
|
||||
| `payments-dashboard-dev` | `seahaven-dev` | `710827005802` | `dev` | `https://api-sb.bofa.com` |
|
||||
|
||||
Both carry tag `app:payments-dashboard`. `schedules_enabled` stays false until cutover.
|
||||
|
||||
## 1. Secrets
|
||||
|
||||
Six Secrets Manager names already exist in seahaven-prod (copied from mgmt
|
||||
with trailing newlines stripped). Terraform reads them by name; values stay
|
||||
out of state.
|
||||
Six Secrets Manager names exist in each account. Terraform pins the exact
|
||||
ARNs in `terraform/locals.tf`. Values stay out of state. Dev shells are not
|
||||
copies of the prod secrets.
|
||||
|
||||
| Name | Used by |
|
||||
|------|---------|
|
||||
|
|
@ -18,32 +24,34 @@ out of state.
|
|||
| `payments-dashboard/expense-slack-token` | expenseProcessor |
|
||||
| `payments-dashboard/expense-slack-signing-secret` | expenseReceiver |
|
||||
|
||||
## 2. HCP Terraform and GitHub Environment
|
||||
## 2. HCP Terraform and GitHub Environments
|
||||
|
||||
First apply uses the hcptf-bootstrap window (exact `StringEquals` trust, never
|
||||
`StringLike`):
|
||||
Prod already applied. A new workspace (dev) uses one bootstrap window:
|
||||
|
||||
1. Create the HCP workspace. Auto-apply off. No project-level variable set.
|
||||
Working directory `terraform`. File trigger prefix `terraform/**` only.
|
||||
Speculative plans on. VCS on `main`.
|
||||
1. Tag the workspace `app:payments-dashboard`. Working directory `terraform`.
|
||||
VCS on `main`. Trigger prefix `terraform/**`. Speculative plans on.
|
||||
Set `environment`, `schedules_enabled=false`, and `boa_base_url`.
|
||||
No project-level variable set.
|
||||
2. From `seahaven-org-baseline`:
|
||||
`scripts/create-hcptf-bootstrap-roles.sh --account prod --allow-workspace payments-dashboard-prod`
|
||||
3. Point workspace `TFC_AWS_APPLY_ROLE_ARN` / `TFC_AWS_PLAN_ROLE_ARN` at
|
||||
`scripts/create-hcptf-bootstrap-roles.sh --account <dev|prod> --allow-workspace payments-dashboard-<env>`
|
||||
3. Point that workspace's `TFC_AWS_APPLY_ROLE_ARN` / `TFC_AWS_PLAN_ROLE_ARN` at
|
||||
`hcptf-bootstrap` / `hcptf-bootstrap-plan`. Set `TFC_AWS_PROVIDER_AUTH=true`.
|
||||
4. One manual apply with `schedules_enabled=false`. This creates the scoped
|
||||
`hcptf-*` roles, the Lambda boundary, VPC/NAT, and the rest of the stack.
|
||||
4. One manual apply. This creates the scoped `hcptf-*` roles, the Lambda
|
||||
boundary, VPC/NAT, and the rest of the stack.
|
||||
5. Retarget `TFC_AWS_*` to `hcptf-payments-dashboard` /
|
||||
`hcptf-payments-dashboard-plan`. Re-run the create script with no
|
||||
`--allow-workspace`.
|
||||
6. Second manual apply as the scoped role. Then seal auto-apply on after
|
||||
live-path proof.
|
||||
|
||||
GitHub Environment `prod`: reviewers, branch policy `main` only, Environment
|
||||
variable `DEPLOY_ROLE_ARN` = Terraform output `github_deploy_role_arn`.
|
||||
GitHub Environment `dev`: no reviewers. `DEPLOY_ROLE_ARN` is the dev
|
||||
`github_deploy_role_arn`. Environment `prod`: reviewers, branch policy `main`
|
||||
and `v*`, prod `github_deploy_role_arn`.
|
||||
|
||||
Function zips: Actions → Deploy on push to `main`, or `workflow_dispatch`.
|
||||
Function zips: push to `main` deploys dev. A human
|
||||
`gh release create vX.Y.Z --target main` deploys prod (`ship-gate` on).
|
||||
`workflow_dispatch` takes `environment` and `ref`. The caller is
|
||||
`.github/workflows/deploy.yaml`. It calls org reusable `cd-hcp-lambda.yaml`.
|
||||
Keep `schedules_enabled=false` until Slack Request URLs and the Stampli
|
||||
uploader point at this stack.
|
||||
uploader point at the prod stack.
|
||||
|
||||
HCP outputs to copy: `slack_request_url`, `expense_slack_events_url`,
|
||||
`csv_bucket_name`, `static_outbound_ip`, `github_deploy_role_arn`.
|
||||
|
|
@ -63,8 +71,8 @@ Avoid weekday 9am ET and the 16:00/19:00/22:00 UTC intraday slots.
|
|||
2. Copy DynamoDB `PaymentsDashboard` mgmt → prod. Verify item counts for
|
||||
`payment#`, `boa_recon#`, and `boa_balance#`. Do not copy
|
||||
`seahaven-payments-boa-raw-*`.
|
||||
3. GHA `workflow_dispatch` (or the merge deploy; re-run if it raced apply) to
|
||||
overwrite stubs.
|
||||
3. Prod zip update is a human release, or `workflow_dispatch` with
|
||||
`environment=prod`, after the HCP apply. Re-run if the job raced apply.
|
||||
4. Instant cut: Slack App Home and Expense bot Request URLs → prod;
|
||||
Stampli uploader bucket → `seahaven-payments-csv-011934824531`;
|
||||
`schedules_enabled=true` via a terraform-only merge; disable mgmt
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
# GitHub Actions OIDC role for .github/workflows/deploy.yaml.
|
||||
# GitHub Actions OIDC role for the thin deploy.yaml caller of
|
||||
# org reusable cd-hcp-lambda.yaml.
|
||||
#
|
||||
# Trust is pinned three ways: aud, sub to Environment prod (immutable and
|
||||
# classic subject forms), and job_workflow_ref to deploy.yaml at
|
||||
# refs/heads/main only. No v* tags until a later release ticket.
|
||||
# One role per account: GitHub Environments have a single DEPLOY_ROLE_ARN.
|
||||
# The prod role trusts environment:prod only. The dev role trusts environment:dev only.
|
||||
# job_workflow_ref is StringEquals on the reusable SHA pinned by deploy.yaml.
|
||||
# AWS STS GitHub condition keys include job_workflow_ref, not workflow_ref.
|
||||
#
|
||||
# Not a Lambda execution role: no permissions_boundary. Path /tf-managed/ so
|
||||
# seahaven-hcptf-iam-management DenySelfMutation (role/githubdeploy-*) does not
|
||||
|
|
@ -28,18 +30,13 @@ data "aws_iam_policy_document" "github_deploy_assume" {
|
|||
condition {
|
||||
test = "StringEquals"
|
||||
variable = "token.actions.githubusercontent.com:sub"
|
||||
values = [
|
||||
local.github_oidc_sub,
|
||||
"repo:${var.github_repo}:environment:prod",
|
||||
]
|
||||
values = local.github_oidc_subs
|
||||
}
|
||||
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
variable = "token.actions.githubusercontent.com:job_workflow_ref"
|
||||
values = [
|
||||
"${var.github_repo}/.github/workflows/deploy.yaml@refs/heads/${var.github_deploy_branch}",
|
||||
]
|
||||
values = [local.github_deploy_workflow_ref]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +44,7 @@ data "aws_iam_policy_document" "github_deploy_assume" {
|
|||
resource "aws_iam_role" "github_deploy" {
|
||||
name = local.deploy_role
|
||||
path = "/tf-managed/"
|
||||
description = "GitHub Actions Lambda deploy role for ${var.github_repo} Environment prod"
|
||||
description = "GitHub Actions Lambda deploy role for ${var.github_repo}"
|
||||
assume_role_policy = data.aws_iam_policy_document.github_deploy_assume.json
|
||||
max_session_duration = 3600
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
locals {
|
||||
project = "payments-dashboard"
|
||||
account_id = "011934824531"
|
||||
environment = "prod"
|
||||
is_prod = var.environment == "prod"
|
||||
account_id = local.is_prod ? "011934824531" : "710827005802"
|
||||
environment = var.environment
|
||||
|
||||
hcp_project = "seahaven-prod"
|
||||
hcp_workspace = "payments-dashboard-prod"
|
||||
hcp_project = "seahaven-${var.environment}"
|
||||
hcp_workspace = "${local.project}-${var.environment}"
|
||||
apply_role = "hcptf-payments-dashboard"
|
||||
plan_role = "hcptf-payments-dashboard-plan"
|
||||
deploy_role = "githubdeploy-payments-dashboard"
|
||||
|
|
@ -20,21 +21,48 @@ locals {
|
|||
dynamodb_cmk_ssm = "/seahaven/dynamodb/cmk-arn"
|
||||
|
||||
github_oidc_provider_arn = "arn:aws:iam::${local.account_id}:oidc-provider/token.actions.githubusercontent.com"
|
||||
# Org has Actions OIDC use_immutable_subject=true.
|
||||
github_oidc_sub = "repo:Sea-Haven-Industries@183236204/payments-dashboard@1206210946:environment:prod"
|
||||
# Repo OIDC subject customization is the default (use_default=true), so tokens
|
||||
# use the classic repo:owner/name:environment:<env> form. Each account's role
|
||||
# trusts only its own Environment. The prod role must not trust dev.
|
||||
github_oidc_subs_prod = [
|
||||
"repo:${var.github_repo}:environment:prod",
|
||||
]
|
||||
github_oidc_subs_dev = [
|
||||
"repo:${var.github_repo}:environment:dev",
|
||||
]
|
||||
github_oidc_subs = local.is_prod ? local.github_oidc_subs_prod : local.github_oidc_subs_dev
|
||||
# Matches the SHA pin in .github/workflows/deploy.yaml. A reusable bump
|
||||
# updates both together. StringEquals, not @*.
|
||||
github_deploy_workflow_ref = "Sea-Haven-Industries/.github/.github/workflows/cd-hcp-lambda.yaml@ee5b843ca105422b679c3fdeb9eaa68c6e500a85"
|
||||
|
||||
dynamodb_cmk_arn = "arn:aws:kms:us-east-1:011934824531:key/be5fa4cb-c546-40fe-a13d-c7bec79f5d12"
|
||||
dynamodb_cmk_arns = {
|
||||
prod = "arn:aws:kms:us-east-1:011934824531:key/be5fa4cb-c546-40fe-a13d-c7bec79f5d12"
|
||||
dev = "arn:aws:kms:us-east-1:710827005802:key/600997e6-418e-4b7f-9d63-cd42a7505a95"
|
||||
}
|
||||
dynamodb_cmk_arn = local.dynamodb_cmk_arns[var.environment]
|
||||
|
||||
# Exact ARNs (ticket rule). Hardcoded so the first plan can run as
|
||||
# hcptf-bootstrap-plan, which cannot ssm:GetParameter / DescribeSecret.
|
||||
secret_arns = {
|
||||
"payments-dashboard/slack-bot-token" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/slack-bot-token-0pAM3S"
|
||||
"payments-dashboard/slack-signing-secret" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/slack-signing-secret-u0T6h8"
|
||||
"payments-dashboard/boa-check-mgmt" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/boa-check-mgmt-LEbC65"
|
||||
"payments-dashboard/boa-reporting" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/boa-reporting-JoR9lq"
|
||||
"payments-dashboard/expense-slack-token" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/expense-slack-token-SeMg3s"
|
||||
"payments-dashboard/expense-slack-signing-secret" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/expense-slack-signing-secret-lbb78J"
|
||||
# Dev values are shells created for this workspace. They are not prod secrets.
|
||||
secret_arns_by_env = {
|
||||
prod = {
|
||||
"payments-dashboard/slack-bot-token" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/slack-bot-token-0pAM3S"
|
||||
"payments-dashboard/slack-signing-secret" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/slack-signing-secret-u0T6h8"
|
||||
"payments-dashboard/boa-check-mgmt" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/boa-check-mgmt-LEbC65"
|
||||
"payments-dashboard/boa-reporting" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/boa-reporting-JoR9lq"
|
||||
"payments-dashboard/expense-slack-token" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/expense-slack-token-SeMg3s"
|
||||
"payments-dashboard/expense-slack-signing-secret" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/expense-slack-signing-secret-lbb78J"
|
||||
}
|
||||
dev = {
|
||||
"payments-dashboard/slack-bot-token" = "arn:aws:secretsmanager:us-east-1:710827005802:secret:payments-dashboard/slack-bot-token-KhPaLp"
|
||||
"payments-dashboard/slack-signing-secret" = "arn:aws:secretsmanager:us-east-1:710827005802:secret:payments-dashboard/slack-signing-secret-CDxsUK"
|
||||
"payments-dashboard/boa-check-mgmt" = "arn:aws:secretsmanager:us-east-1:710827005802:secret:payments-dashboard/boa-check-mgmt-kkEnCw"
|
||||
"payments-dashboard/boa-reporting" = "arn:aws:secretsmanager:us-east-1:710827005802:secret:payments-dashboard/boa-reporting-uMHHVo"
|
||||
"payments-dashboard/expense-slack-token" = "arn:aws:secretsmanager:us-east-1:710827005802:secret:payments-dashboard/expense-slack-token-05OZg3"
|
||||
"payments-dashboard/expense-slack-signing-secret" = "arn:aws:secretsmanager:us-east-1:710827005802:secret:payments-dashboard/expense-slack-signing-secret-DQAoXS"
|
||||
}
|
||||
}
|
||||
secret_arns = local.secret_arns_by_env[var.environment]
|
||||
|
||||
functions = {
|
||||
process_csv = {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ output "table_name" {
|
|||
}
|
||||
|
||||
output "github_deploy_role_arn" {
|
||||
description = "OIDC role ARN for .github/workflows/deploy.yaml (GitHub Environment prod variable DEPLOY_ROLE_ARN)."
|
||||
description = "OIDC role ARN for the deploy.yaml caller (GitHub Environment variable DEPLOY_ROLE_ARN)."
|
||||
value = aws_iam_role.github_deploy.arn
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,15 @@ variable "github_repo" {
|
|||
default = "Sea-Haven-Industries/payments-dashboard"
|
||||
}
|
||||
|
||||
variable "github_deploy_branch" {
|
||||
description = "Git branch pinned in job_workflow_ref for the deploy role."
|
||||
variable "environment" {
|
||||
description = "HCP workspace stage. Selects account and workspace name."
|
||||
type = string
|
||||
default = "main"
|
||||
default = "prod"
|
||||
|
||||
validation {
|
||||
condition = contains(["dev", "prod"], var.environment)
|
||||
error_message = "environment must be \"dev\" or \"prod\"."
|
||||
}
|
||||
}
|
||||
|
||||
variable "boa_base_url" {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ terraform {
|
|||
organization = "seahaven"
|
||||
|
||||
workspaces {
|
||||
name = "payments-dashboard-prod"
|
||||
tags = ["app:payments-dashboard"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,11 +34,13 @@ describe("HCP Terraform seam (PLAT-79)", () => {
|
|||
assert.match(chunk, /default\s+= false/);
|
||||
});
|
||||
|
||||
it("is prod-only", () => {
|
||||
assert.match(versions, /payments-dashboard-prod/);
|
||||
assert.doesNotMatch(versions, /payments-dashboard-dev/);
|
||||
assert.match(locals, /environment = "prod"/);
|
||||
assert.doesNotMatch(locals, /seahaven-dev/);
|
||||
it("selects dev and prod workspaces by tag", () => {
|
||||
assert.match(versions, /app:payments-dashboard/);
|
||||
assert.doesNotMatch(versions, /name = "payments-dashboard-prod"/);
|
||||
assert.match(locals, /seahaven-\$\{var\.environment\}/);
|
||||
assert.match(locals, /710827005802/);
|
||||
assert.match(locals, /011934824531/);
|
||||
assert.match(variables, /contains\(\["dev", "prod"\], var\.environment\)/);
|
||||
});
|
||||
|
||||
it("declares in-repo hcptf roles", () => {
|
||||
|
|
@ -48,14 +50,18 @@ describe("HCP Terraform seam (PLAT-79)", () => {
|
|||
assert.match(hcpIam, /DenyCreatePolicy/);
|
||||
});
|
||||
|
||||
it("uses prod zip CD without SAM or GitHub Releases", () => {
|
||||
assert.doesNotMatch(deploy, /release: published/);
|
||||
it("calls the Lambda zip reusable for dev and prod", () => {
|
||||
assert.match(deploy, /release:\s*\n\s*types: \[published\]/);
|
||||
assert.doesNotMatch(deploy, /cd-sam/);
|
||||
assert.doesNotMatch(deploy, /aws lambda update-function-code/);
|
||||
assert.match(deploy, /gh release create vX\.Y\.Z --target main/);
|
||||
assert.doesNotMatch(deploy, /release\.yaml@/);
|
||||
assert.match(deploy, /cd-hcp-lambda\.yaml@/);
|
||||
assert.match(deploy, /environment: dev/);
|
||||
assert.match(deploy, /environment: prod/);
|
||||
assert.match(deploy, /deploy-payments-dashboard-prod/);
|
||||
assert.doesNotMatch(deploy, /gh release create/);
|
||||
assert.match(deploy, /package_lambdas\.mjs/);
|
||||
assert.match(deploy, /update-function-code/);
|
||||
assert.match(deploy, /ship-gate: true/);
|
||||
assert.match(deploy, /ssm-prefix: \/payments-dashboard\/deploy/);
|
||||
assert.match(deploy, /function-keys: process_csv,slack_app_home,fetch_boa,expense_receiver,expense_processor/);
|
||||
});
|
||||
|
||||
it("runs npm test and terraform validate behind ci / ci", () => {
|
||||
|
|
@ -81,11 +87,18 @@ describe("HCP Terraform seam (PLAT-79)", () => {
|
|||
assert.doesNotMatch(locals, /payments-processPayrollEmail/);
|
||||
});
|
||||
|
||||
it("pins GitHub deploy trust to Environment prod", () => {
|
||||
assert.match(githubDeploy, /environment:prod/);
|
||||
assert.match(githubDeploy, /deploy.yaml@refs\/heads\/\$\{var.github_deploy_branch\}/);
|
||||
assert.doesNotMatch(githubDeploy, /deploy.yaml@\*/);
|
||||
assert.doesNotMatch(githubDeploy, /refs\/tags\/v\*/);
|
||||
it("pins GitHub deploy trust to the Lambda reusable", () => {
|
||||
const prodSubs = locals.split("github_oidc_subs_prod")[1].split("github_oidc_subs_dev")[0];
|
||||
assert.match(prodSubs, /environment:prod/);
|
||||
assert.doesNotMatch(prodSubs, /environment:dev/);
|
||||
assert.match(githubDeploy, /github_oidc_subs/);
|
||||
assert.match(githubDeploy, /job_workflow_ref/);
|
||||
assert.match(locals, /cd-hcp-lambda\.yaml@ee5b843ca105422b679c3fdeb9eaa68c6e500a85/);
|
||||
assert.match(deploy, /cd-hcp-lambda\.yaml@ee5b843ca105422b679c3fdeb9eaa68c6e500a85/);
|
||||
assert.doesNotMatch(githubDeploy, /cd-hcp-lambda\.yaml@\*/);
|
||||
assert.doesNotMatch(locals, /cd-hcp-lambda\.yaml@\*/);
|
||||
assert.doesNotMatch(githubDeploy, /deploy\.yaml@refs\/heads/);
|
||||
assert.doesNotMatch(variables, /github_deploy_branch/);
|
||||
});
|
||||
|
||||
it("includes provider-6 S3 Get* needed for refresh", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue