Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.
This slash command can only be invoked by Claude, not directly by users. Ask Claude to run /deep-think for you.
deep-think I want to have ledger jobs/cronjobs built and deployed at the same time as the service. We currenlty have a service build action and a separate job build action. Can we easily change the service build action to call the job builder with the right parameters when ledger is getting built and then update the helm chart with not only the service image as we do now, but also the job/cronjob images that were then just built?
The user wants to consolidate the build process for the ledger service so that when the ledger service is built and deployed, its associated jobs/cronjobs are also built and deployed at the same time. Currently, they have separate build actions for services and jobs.
Let me explore the codebase to understand: 1. The current service build action 2. The current job build action 3. How the helm charts are structured for ledger 4. What jobs/cronjobs ledger has
I should launch Explore agents to investigate these areas in parallel.
I'll explore the codebase to understand the current build setup for services and jobs, and how they relate to the ledger service and its helm charts.
{
"subagent_type": "Explore",
"prompt": "Explore the GitHub Actions workflows for building services and jobs. I need to understand:\n\n1. The service build action - how services like \"ledger\" are built and deployed\n2. The job build action - how jobs/cronjobs are built separately\n3. How these workflows update helm charts with image tags\n\nLook in `.github/workflows/` for relevant workflow files and any reusable actions in `.github/actions/`. Focus on understanding the build and deployment pipeline, especially how images are built and how helm charts are updated."
}{
"subagent_type": "Explore",
"prompt": "Explore the ledger service structure, specifically:\n\n1. What jobs/cronjobs does ledger have? Look in `ledger/jobs/` or similar directories\n2. How are these jobs currently built? Check for Dockerfiles or build configurations\n3. What is the relationship between the main ledger service and its jobs?\n\nFocus on understanding what needs to be built together when deploying ledger."
}{
"subagent_type": "Explore",
"prompt": "Explore the helm charts for ledger and understand how they're structured:\n\n1. Look in `helm/` or similar directories for ledger's helm chart\n2. How are service images currently referenced in the helm values?\n3. Are there job/cronjob definitions in the helm chart that need image references?\n4. How is the helm chart updated during the build/deploy process?\n\nFocus on understanding what changes would be needed to add job images to the helm deployment."
}[
{
"type": "text",
"text": "Perfect! I now have a comprehensive understanding of the GitHub Actions workflows and build pipeline. Let me create a detailed summary of the build and deployment infrastructure.\n\n## GitHub Actions Build & Deployment Pipeline Analysis\n\nI've thoroughly explored the GitHub Actions workflows and can provide you with a complete understanding of the build and deployment infrastructure. Here's what I found:\n\n### 1. SERVICE BUILD WORKFLOW (`push_ecr.yml`)\n\n**Trigger Events:**\n- Push to `main` or `heb-release` branches\n- PR labeled with \"needs branch build\"\n- Manual workflow dispatch\n\n**Key Pipeline Stages:**\n\n1. **Version Tagging** (`tagging-new-version`):\n - Uses `mathieudutour/github-tag-action` to automatically bump semantic versions\n - For PRs: generates version as `brc.svc.{pr_num}.{short_sha}`\n - For main branch: bumps version based on conventional commits\n\n2. **Service Detection** (`compute-affected-services`):\n - Analyzes changed files to determine which services were modified\n - Uses Python script (`parse_service_labels.py`) to extract service labels from PR\n - Filters services based on environment availability lists:\n - `SERVICES_WEST`: 15 services (includes ledger, banking, rewards)\n - `SERVICES_PREPRD`: 25 services\n - `SERVICES_SBX`: 23 services\n\n3. **Build Matrix** (`upload-images`):\n - Runs for each affected service across environments: `stg, preprd, prd, sbx`\n - Per-service steps:\n - Go build cache retrieval\n - Protobuf generation via buf\n - Config generation: `go run scripts/genconfig/generate_config.go`\n - Compilation: `make build` in service directory\n - Docker image build and push to ECR with tag: `{version}.{service}`\n\n4. **Helm Chart Updates** (`update-helm-values`):\n - Only runs on main branch push\n - Downloads all build artifacts (image tags)\n - Updates helm chart values in separate `charts` repository\n - Modifies `{service}/chart/{env}/values.yaml` with new `image_tag`\n - Uses GitHub App token for auto-commit with bot user\n\n**Image Tagging Strategy:**\n- **Push to main**: `{version}.{service}` (e.g., `v1.2.3.ledger`)\n- **PR build**: `brc.svc.{pr_num}.{short_sha}.{service}`\n- Regional variant: `{version}.{service}.{region}` for us-west-2\n\n---\n\n### 2. JOB BUILD WORKFLOW (`job_runner_build.yml`)\n\n**Trigger:** Manual workflow dispatch with inputs:\n- `job_name`: which job to build\n- `service_name`: parent service (ledger, banking, etc.)\n\n**Key Pipeline Stages:**\n\n1. **Version Generation** (`tagging-new-version`):\n - Validates service is in `VALID_SERVICES` list (7 services support jobs)\n - Generates version: `job.{date}.{time}.pr{pr_num}` (branch)\n - Or: `job.{date}.{time}.{short_sha}` (main)\n\n2. **Job Build** (`build-job-runner`):\n - Runs for environments: `stg, prd, sbx` (only main branch for prd/sbx)\n - Prepares job manifest directory with:\n - Generated service config\n - Generated job-specific config (if exists)\n - Compiled Go binary\n - Job Dockerfile\n - Runs job's `prepare.sh` script to stage manifests\n - Builds Docker image: `{service}:{tag}`\n - Pushes to ECR\n\n3. **OpsLevel Update** (`update-opslevel-config`):\n - Only for main branch\n - Updates `{service}/jobs/{job}/opslevel.yml` with new version\n - Auto-commits to go-backend repo\n\n**Example Job Structure** (ledger/jobs/transactions-ingestion):\n```\n├── deploy.yaml # Resource requests (cpu, memory, cron settings)\n├── opslevel.yml # OpsLevel component metadata with version tracking\n├── prepare.sh # Build script that compiles and stages artifacts\n├── Dockerfile # Container definition\n└── main.go # Job executable code\n```\n\n---\n\n### 3. DML RUNNER BUILD WORKFLOW (`dml_runner_build.yml`)\n\n**Trigger:**\n- Changes to `**/databases/mysql/**` paths\n- Both PR and push to main\n\n**Pipeline:**\n\n1. **Manifest Preparation**:\n - Generates protobuf files\n - Runs Python script `dml-prepare.py` to compute tag\n - Builds dml_runner binary\n - Copies environment-specific config\n - Prepares Dockerfile\n\n2. **Image Build per Statement Type**:\n - Matrix: `stmt_type: [release, rollback]` per environment\n - Tag format: `{tag}.{stmt_type}`\n - Builds with args:\n - `STMT_TYPE`: release or rollback\n - `RUNNER_USER`: {env}-dml-runner\n - `RUNNER_PASS`: secret credentials\n\n---\n\n### 4. DEPLOYMENT WORKFLOWS\n\n**Single Job Deployment** (`single_job_deploy_helm.yml`):\n- Manual dispatch workflow\n- Inputs: environment, service, job_name, image_tag, force flag\n- Calls `common_job_deploy_helm.yml` with `run_type: job`\n\n**DML Deployment** (`dml_runner_deploy_helm.yml`):\n- Manual dispatch workflow\n- Input: environment, dml_image_tag\n- Calls `common_job_deploy_helm.yml` with `run_type: dml`\n\n**Common Helm Deploy** (`common_job_deploy_helm.yml`):\n- Reusable workflow (workflow_call)\n- Handles both job and dml deployments\n- Steps:\n 1. Install Helm and kubectl\n 2. Resolve AWS account/role/EKS cluster per environment\n 3. Configure AWS credentials with OIDC\n 4. Build kubeconfig from EKS\n 5. Helm upgrade/install with templated values\n 6. Generate Datadog log query link for debugging\n\n**Helm Chart Templates:**\n\nJob Chart (`.github/helm/job/`):\n- Parses `deploy.yaml` for resource limits/requests\n- Computes CPU limits as 2x requests, memory same as requests\n- Creates Kubernetes Job with:\n - Datadog annotations and labels\n - Temporal credentials from secrets\n - Resource management\n - Pod scheduling on backend nodes\n\nDML Chart (`.github/helm/dml/`):\n- Simpler template with fixed resources (250m CPU, 512Mi memory)\n- Creates Job for DML/DDL execution\n- Standard Datadog integration\n\n---\n\n### 5. KEY CONFIGURATION & ENVIRONMENT MAPPING\n\n**AWS Account IDs by Environment:**\n- **stg**: 943651090460 (STG_ECR_UPLOAD_ROLE)\n- **sbx**: 978416150816 (SBX_ECR_UPLOAD_ROLE)\n- **preprd**: 273709947137 (PREPRD_ECR_UPLOAD_ROLE)\n- **prd**: 191642606073 (PRD_ECR_UPLOAD_ROLE)\n\n**EKS Clusters:**\n- stg: `stg-banking-expanded-eks-cluster`\n- sbx: `sbx-banking-eks-use1`\n- preprd: `preprd-banking-eks-use1`\n- prd: `prd-banking-expanded-eks-cluster`\n\n**Helm Values Update:**\n- Only for `stg` environment and `mirror` service in other envs\n- Path: `{service}/chart/{env}/values.yaml`\n- Field updated: `image_tag: {new_tag}`\n\n---\n\n### 6. BUILD CACHE STRATEGY\n\n- Uses GitHub Actions cache with keys:\n - Service builds: `${{ runner.os}}-config-gen-${{ matrix.service }}-${{ hashFiles('**/go.sum') }}`\n - Job builds: `${{ runner.os}}-config-gen-go-${{ hashFiles('**/go.sum') }}`\n- Cache paths: `~/.cache/go-build`\n- Restore keys include fallback patterns\n\n---\n\n### 7. WORKFLOW EXECUTION FLOW DIAGRAM\n\n**Service Workflow:**\n```\nPush/PR/Dispatch\n ├── Check Label (PR only)\n ├── Tag Version\n ├── Compute Affected Services\n ├── Upload Images (matrix: service × env × region)\n │ ├── Cache Go build\n │ ├── Generate protobuf\n │ ├── Generate config\n │ ├── Build binary\n │ ├── Build & push Docker image\n │ └── Upload artifact with image digest\n ├── Update Helm Values (main only)\n │ └── Download artifacts & update charts repo\n └── Comment PR (with build links)\n```\n\n**Job Workflow:**\n```\nManual Dispatch\n ├── Tag Version\n ├── Compute Affected Jobs\n └── Build Job Runner (matrix: job × env)\n ├── Prepare manifests\n ├── Build & push Docker image\n └── Update OpsLevel (main only)\n```\n\n---\n\n### Key Files Summary\n\n1. **`/Users/danielroth/dev/go-backend/.github/workflows/push_ecr.yml`** (499 lines) - Main service build workflow\n2. **`/Users/danielroth/dev/go-backend/.github/workflows/job_runner_build.yml`** (327 lines) - Job build workflow\n3. **`/Users/danielroth/dev/go-backend/.github/workflows/dml_runner_build.yml`** (109 lines) - DML/DDL build workflow\n4. **`/Users/danielroth/dev/go-backend/.github/workflows/common_job_deploy_helm.yml`** (170 lines) - Reusable helm deployment\n5. **`/Users/danielroth/dev/go-backend/.github/helm/job/templates/job_template.yml`** (124 lines) - Job helm template\n6. **`/Users/danielroth/dev/go-backend/.github/helm/dml/templates/dml_template.yml`** (57 lines) - DML helm template\n\nThis pipeline provides a sophisticated multi-environment build and deployment system with automatic versioning, selective service building, helm chart management, and comprehensive job support."
},
{
"type": "text",
"text": "agentId: a048a35 (for resuming to continue this agent's work if needed)"
}
][
{
"type": "text",
"text": "Perfect! Now I have a comprehensive understanding. Let me provide a detailed report of the helm chart structure for ledger.\n\n## Summary: Helm Charts for Ledger Jobs\n\nI've thoroughly explored the helm chart structure for the ledger service jobs. Here's what I found:\n\n### 1. **Helm Chart Location and Structure**\n\nThe helm charts are located in `/Users/danielroth/dev/go-backend/.github/helm/` with two main chart types:\n\n```\n.github/helm/\n├── job/\n│ ├── Chart.yaml\n│ └── templates/job_template.yml\n└── dml/\n ├── Chart.yaml\n └── templates/dml_template.yml\n```\n\n### 2. **Service Image References in Helm Values**\n\n**Job Template** (`job_template.yml`):\n- Images are passed via the `image` Helm value (line 63):\n ```yaml\n image: {{ required \"image (full reference) is required\" .Values.image | quote }}\n ```\n- Full image reference format: `{ECR_ACCOUNT}.dkr.ecr.us-east-1.amazonaws.com/{SERVICE_NAME}:{TAG}`\n- Example: `943651090460.dkr.ecr.us-east-1.amazonaws.com/ledger:job.202612151200.pr12345`\n\n**DML Template** (`dml_template.yml`):\n- Also uses `image` Helm value (line 32):\n ```yaml\n image: {{ required \"image (full reference) is required, e.g. <acct>.dkr.ecr.us-east-1.amazonaws.com/dml-runner:<tag>\" .Values.image | quote }}\n ```\n- Always uses `dml-runner` as the ECR repository name\n\n### 3. **Job and Cronjob Definitions in Helm Charts**\n\n**Job Template Features**:\n- Creates Kubernetes `Job` resources (not CronJobs - those are managed separately)\n- Supports parallelism via `parallelism` and `completions` fields (configurable from deploy.yaml)\n- Resource limits are derived from job's `deploy.yaml`:\n - CPU limits = 2x the requested CPU\n - Memory limits = same as requested memory\n- Job cleanup: `ttlSecondsAfterFinished: 60` (auto-deletes pods 60s after completion)\n- No retries on failure: `backoffLimit: 0`\n\n**Job-specific Helm Values**:\n```yaml\njob.nameBase # Job name (required)\nversion # Image tag (required)\nenv.name # Environment (stg/sbx/prd, required)\nserviceName # Service name for labels (required)\nimage # Full image reference (required)\ntemporal.hostPort # Temporal server address (required)\ntemporal.namespace # Temporal namespace (required)\ndeployConfigRaw # Contents of deploy.yaml (required, passed as file)\n```\n\n**DML Template Features**:\n- Similar structure to job template\n- Uses `dml-runner` as fixed service name\n- Fewer Temporal-related configurations\n- Simpler resource management\n\n### 4. **How Helm Charts Are Updated During Build/Deploy**\n\n**Build Process** (`job_runner_build.yml`):\n1. **Image Build**:\n - Uses service name as ECR repository: `$ECR_ACCOUNT/$SERVICE_NAME:$TAG`\n - Tag format for branch builds: `job.{YYYYMMDD.HHMM}.pr{PR_NUM}`\n - Tag format for main builds: `job.{YYYYMMDD.HHMM}.{GIT_HASH}`\n\n2. **Image Deployment**:\n - `helm upgrade --install` called with specific values set\n - Full image reference passed as: `image_repo:image_tag`\n - Deploy config loaded from `deploy.yaml` as `deployConfigRaw` file\n\n**Deploy Process** (`common_job_deploy_helm.yml`):\n- Called from `single_job_deploy_helm.yml` workflow\n- Helm command structure (lines 135-145):\n ```bash\n helm upgrade --install \"$RELEASE\" \"$CHART\" \\\n --namespace \"$NS\" --create-namespace \\\n --set job.nameBase=\"${{ inputs.job_name }}\" \\\n --set version=\"${{ inputs.image_tag }}\" \\\n --set env.name=\"${{ inputs.environment }}\" \\\n --set serviceName=\"${{ inputs.service_name }}\" \\\n --set image=\"$IMAGE\" \\\n --set temporal.hostPort=\"${{ inputs.temporal_hostport }}\" \\\n --set temporal.namespace=\"${{ inputs.temporal_namespace }}\" \\\n --set-file deployConfigRaw=\"${{ inputs.deploy_cfg_path }}\"\n ```\n\n### 5. **Current Job Deploy Configuration Structure**\n\nLedger jobs use minimal `deploy.yaml` files (example from `/ledger/jobs/apply-late-payment-fee/deploy.yaml`):\n```yaml\nname: apply-late-payment-fee\n\nresources:\n requests:\n cpu: 1000m # max value recommended by infrastructure\n memory: 500Mi\n\ncron:\n enable: true\n schedule: 0 6 * * *\n```\n\n### 6. **Changes Needed to Add Job Images to Helm Deployment**\n\nBased on the current architecture, here's what would need to change to support job image references:\n\n**Option A: Service-Level Image Deployment** (Current Pattern - No Changes Needed)\n- Ledger jobs already use the service ECR repository: `ledger:{TAG}`\n- Job-specific images are NOT built separately\n- All jobs for ledger service share the same image\n- The `service_name` parameter determines ECR repository\n\n**Option B: If You Want Job-Specific Images** (Would Require Changes)\n1. **Modify Job Runner Build** (`job_runner_build.yml`):\n - Change ECR_REPOSITORY from service name to job name\n - Modify tag generation to include job identifier\n\n2. **Modify Deploy Helm Call**:\n - Pass job-specific image repo instead of service repo\n - Example: `image_repo: {ACCOUNT}.dkr.ecr.us-east-1.amazonaws.com/{SERVICE}/{JOB_NAME}`\n\n3. **Helm Template Changes**:\n - No changes needed - template already accepts full `image` reference\n - Just need different repository naming in deployment\n\n4. **Deploy Config**:\n - Could add optional job-specific image overrides in deploy.yaml\n - Template would need to parse and apply overrides\n\n### 7. **Key Files for Job Image Management**\n\n| File | Purpose |\n|------|---------|\n| `/ledger/jobs/{job-name}/deploy.yaml` | Defines CPU/memory resources and cron schedule |\n| `/ledger/jobs/{job-name}/Dockerfile` | Packages job binary into container |\n| `/ledger/jobs/{job-name}/prepare.sh` | Compiles binary and prepares Docker build context |\n| `/ledger/jobs/build_all_jobs.sh` | Triggers build workflow for all jobs |\n| `.github/workflows/job_runner_build.yml` | Builds and pushes Docker images to ECR |\n| `.github/workflows/single_job_deploy_helm.yml` | Triggers Helm deployment |\n| `.github/helm/job/templates/job_template.yml` | Kubernetes Job manifest template |\n| `.github/helm/job/Chart.yaml` | Helm chart metadata |\n\n### Summary\n\nThe current helm chart setup for ledger jobs is already well-designed for image management:\n- Images are referenced as complete URIs passed to Helm\n- The template uses dynamic substitution for image values\n- The build pipeline automatically handles ECR repository naming based on service name\n- All jobs for a service currently share one image, controlled by the service ECR repository\n\nTo support separate job-specific images, you would primarily need to modify the build workflow to generate per-job images rather than per-service images, while the helm template itself requires no changes."
},
{
"type": "text",
"text": "agentId: ace776f (for resuming to continue this agent's work if needed)"
}
][
{
"type": "text",
"text": "Perfect! Now I have all the information I need. Let me create a comprehensive report:\n\n## Ledger Service Structure - Jobs and Build Configuration\n\nI've completed a comprehensive exploration of the ledger service structure, focusing on its jobs, build configurations, and relationships. Here's what I found:\n\n---\n\n### 1. LEDGER JOBS OVERVIEW\n\nThe ledger service has **37 distinct jobs** located in `/Users/danielroth/dev/go-backend/ledger/jobs/`:\n\n**Category: Statement Processing**\n- `create-statement-cycle` - Create new statement cycles\n- `credit-statement-due` - Process statement due dates\n- `update-credit-statements` - Update statement data\n- `create-balance-statement` - Create balance statements\n- `update-statement-configs` - Update statement configurations\n- `update-statement-furnishing` - Handle credit bureau reporting\n- `backfill-statement-cycle` - Backfill missing statement cycles\n- `fix-statement-interest-misalignment` - Correct interest calculation errors\n\n**Category: Transaction Processing**\n- `update-transactions` - Update transaction records\n- `close-pending-auths` - Close pending authorization holds\n- `update-payments` - Process payment records\n- `update-settlements` - Process settlement records\n\n**Category: Credit Management**\n- `update-credit-balances` - Recalculate credit balances\n- `update-credit-line-apr` - Update APR values\n- `update-credit-profiles` - Manage credit profile data\n- `rebalance-credit-line` - Rebalance credit lines\n- `override-credit-statements` - Override statement data\n- `overwrite-credit-profile` - Overwrite profile data\n- `run-credit-replay` - Execute credit replay engine\n\n**Category: Fees and Collections**\n- `apply-late-payment-fee` - Apply late fees\n- `post-annual-fee` - Process annual fees\n- `collection-set-exempt` - Mark accounts exempt from collections\n- `backfill-collection-events` - Backfill collection history\n- `backfill-fee-profile` - Backfill fee profile data\n- `sync-debt-facility-assignment` - Sync debt facility assignments\n\n**Category: Collection File Processing**\n- `upload-collection-placement` - Upload placement files\n- `upload-collection-retraction` - Upload retraction files\n- `upload-collection-payment` - Upload payment files\n- `upload-3rd-party-collection-files` - Upload 3PC files\n- `upload-3pc-placement-file` - Upload 3PC placement files\n\n**Category: Reconciliation & Validation**\n- `secure-account-reconciliation` - Account reconciliation job\n- `daily-secure-account-reconciliation` - Daily reconciliation\n- `public-doc-access-validation` - Validate document access\n\n**Category: Workflow/System**\n- `trigger-centralized-workflow` - Trigger centralized workflows\n- `transactions-ingestion` - Process transaction ingestion\n\n---\n\n### 2. JOB STRUCTURE & BUILD CONFIGURATION\n\n**Each job is a standalone executable with:**\n\n```\nledger/jobs/{job-name}/\n├── main.go # Job entry point\n├── Dockerfile # Container image definition\n├── deploy.yaml # Kubernetes config (name, resources, cron)\n├── prepare.sh # Build & packaging script (executable)\n├── opslevel.yml # OpsLevel observability config\n└── [job-name].json # Job-specific data files\n```\n\n**Build Flow per Job:**\n\n1. **Job Compilation** (`prepare.sh`):\n ```bash\n go build -o {binary-name} main.go\n mkdir -p {manifest-dir}/{job-name}\n cp Dockerfile {manifest_dir}/Dockerfile\n cp {binary} {manifest_dir}/{job-name}/\n cp {env}-config.json {manifest_dir}/config.json\n ```\n\n2. **Docker Image Creation** (Dockerfile):\n ```dockerfile\n FROM ubuntu:22.04\n RUN apt-get install -y ca-certificates tzdata dnsutils telnet net-tools\n COPY * {job-name}/\n ENV WORKING_DIR {job-name}/\n ENV CONF {job-name}/config.json\n ENTRYPOINT [\"{job-name}/{binary}\"]\n ```\n\n3. **Image Registry Push**: Built images are pushed to AWS ECR with version tags like:\n - `job.20240816.1430.pr12345` (branch builds)\n - `job.20240816.1430.abc123` (main branch builds)\n\n---\n\n### 3. MAIN SERVICE vs. JOBS RELATIONSHIP\n\n**Critical Architecture Pattern:**\n\nThe jobs **reuse the main ledger service's core logic** but run as isolated executables:\n\n**Main Service** (`/Users/danielroth/dev/go-backend/ledger/cmd/main.go`):\n- Runs gRPC server (port 8081)\n- Uses fx dependency injection framework\n- Provides handlers and domain logic\n\n**Jobs** (each `main.go` in `jobs/*/`):\n- Use the same `server.Initializer` pattern\n- Initialize identical components: `server.InitBasics`, `server.InitClients`, `server.InitDomains`, `server.InitHandlers`\n- Call specific job execution methods from the server\n\n**Job Execution Pattern** (from `server_job_exe_funcs.go`):\n```go\n// Job entry point (example: update-credit-statements)\nfunc main() {\n // Load config from CONF env var\n cfg := &config.Config{}\n loadconfig.FromEnvToJSON(conf, cfg)\n \n // Initialize observability\n ots, err := observability.InitObserverTools(cfg.Logger, cfg.Stats, cfg.Tracer)\n \n // Create server with same initializers as main service\n server, err := server.NewServer(ctx, cfg, logger, statsd, jobInitializers)\n \n // Call specific job method (reuses handler logic)\n err = server.UpdateCreditStatementsJob(ctx)\n}\n```\n\n**Available Job Methods** in Server (37 total):\n- `CreateStatementCycleJob(ctx, jobName, now)`\n- `CreditStatementDueJob(ctx, jobName, now)`\n- `ApplyLatePaymentFeeJob(ctx, jobName, now)`\n- `UpdateCreditStatementsJob(ctx)`\n- `RunCreditReplayJob(ctx, jobName, now)`\n- ...and 32 more\n\nEach maps to a handler method that contains the actual business logic:\n```go\nfunc (s *Server) UpdateCreditStatementsJob(ctx context.Context) error {\n return s.jobHandler.UpdateCreditStatements(ctx)\n}\n```\n\n---\n\n### 4. BUILD ORCHESTRATION\n\n**GitHub Actions Workflow** (`job_runner_build.yml`):\n\n1. **Trigger**: Manual dispatch with `job_name` and `service_name` inputs\n2. **Version Tagging**: Creates timestamps like `job.20240816.1430.{job-name}`\n3. **Affected Job Detection**: Identifies which jobs changed in the PR\n4. **Per-Environment Matrix Builds**: Builds for `stg`, `prd`, `sbx` environments\n5. **Go Build Cache**: Uses action cache for `~/.cache/go-build`\n6. **Config Generation**:\n ```bash\n go run scripts/genconfig/generate_config.go -service {service} -env {env} -region us-east-1\n ```\n7. **Job Preparation**: Runs each job's `prepare.sh`\n8. **Docker Build & Push**:\n ```bash\n docker build -t {ECR_REGISTRY}/{service}:{tag} .\n docker push {ECR_REGISTRY}/{service}:{tag}\n ```\n\n**Build Script** (`build_all_jobs.sh`):\n- Uses GitHub CLI to trigger \"Build Job runner\" workflow for each job\n- Extracts job names from `deploy.yaml` files\n- Submits parallel workflow runs\n\n---\n\n### 5. DEPLOYMENT CONFIGURATION\n\n**Job Deploy Config** (`deploy.yaml`):\n```yaml\nname: update-credit-statements\nresources:\n requests:\n cpu: 200m\n memory: 500Mi\ncron:\n enable: false # Can be scheduled as CronJob\n```\n\n---\n\n### 6. KEY INSIGHTS - WHAT NEEDS TO BE BUILT TOGETHER\n\nWhen deploying ledger, you need to build:\n\n1. **Main Service Binary** (`ledger/cmd/main.go` → `bin/ledger`)\n2. **All Modified Job Binaries** (each job's `main.go`)\n3. **Shared Code**: Any changes to:\n - `/ledger/internal/server/` (affects all jobs + service)\n - `/ledger/internal/handlers/` (affects all jobs + service)\n - `/ledger/internal/domains/` (affects all jobs + service)\n - `/ledger/pkg/config/` (affects all jobs + service)\n\n**Build Dependencies:**\n- All jobs depend on the ledger service's internal packages\n- Jobs are independently compiled but share service initialization code\n- Configuration generation is service-wide, job-specific configs are merged\n\n**Docker Images Created:**\n- 1 image for the main ledger service\n- 1 image per modified job\n- All pushed to AWS ECR with semantic versioning\n\n---\n\n### 7. FILE LOCATIONS SUMMARY\n\n| Component | Location |\n|-----------|----------|\n| Main Service | `/Users/danielroth/dev/go-backend/ledger/cmd/main.go` |\n| Service Init | `/Users/danielroth/dev/go-backend/ledger/cmd/runner/runner.go` |\n| Server Logic | `/Users/danielroth/dev/go-backend/ledger/internal/server/server.go` |\n| Job Execution | `/Users/danielroth/dev/go-backend/ledger/internal/server/server_job_exe_funcs.go` |\n| All Jobs | `/Users/danielroth/dev/go-backend/ledger/jobs/` (37 directories) |\n| Build Workflow | `/Users/danielroth/dev/go-backend/.github/workflows/job_runner_build.yml` |\n| Job Build Script | `/Users/danielroth/dev/go-backend/ledger/jobs/build_all_jobs.sh` |\n| Config Generation | `/Users/danielroth/dev/go-backend/scripts/genconfig/generate_config.go` |\n\nThis architecture allows independent deployment of jobs while maintaining consistency with the main service through shared initialization and domain logic."
},
{
"type": "text",
"text": "agentId: a5a3684 (for resuming to continue this agent's work if needed)"
}
]I now have a comprehensive understanding of the current setup. Let me summarize what I've learned:
Current State:
1. Service Build (push_ecr.yml): Builds main service images for ledger and other services, updates helm charts in a separate charts repo
2. Job Build (job_runner_build.yml): Separately builds job images - manual trigger workflow, updates opslevel.yml with version
3. Helm Charts: Service helm charts are in a separate repo; job helm charts are in .github/helm/job/
Key Discovery:
- Jobs use the service name as the ECR repository (not job-specific repos)
- All ledger jobs share one ECR repo: ledger:{tag}
- Current job build generates tags like: job.{date}.{time}.{pr_num}
- Current service build generates tags like: v1.2.3.ledger
User's Goal: - Build ledger jobs/cronjobs at the same time as the service - Have the service build workflow call the job builder with correct parameters - Update helm charts with both service image and job/cronjob images
Key Challenges:
1. Different tagging schemes between service and job builds
2. Service helm values are in a separate charts repo, jobs don't have centralized helm values
3. Need to determine which jobs to build when ledger service changes
4. Jobs and service currently have independent image tags
Let me start writing my plan and ask some clarifying questions.
I now have a comprehensive understanding of the build pipeline. Let me write up my initial findings and ask some clarifying questions.
# Plan: Build Ledger Jobs Alongside Service
## Current State
### Service Build (`push_ecr.yml`)
- Triggers on push to main, PR labeled "needs branch build", or manual dispatch
- Builds service image with tag: `{version}.{service}` (e.g., `v1.2.3.ledger`)
- Updates helm values in separate `charts` repo at `{service}/chart/{env}/values.yaml`
### Job Build (`job_runner_build.yml`)
- **Manual trigger only** - not automated
- Builds job images with tag: `job.{date}.{time}.{identifier}`
- Uses the **service name** as ECR repository (jobs share `ledger` repo)
- Updates `opslevel.yml` in go-backend repo, not helm charts
### Key Observations
- Jobs share the same ECR repo as the service (`ledger`)
- Jobs have 37 different executables in `ledger/jobs/`
- Job helm charts are in `.github/helm/job/` (generic template)
- Service helm values are in a separate `charts` repo
## Questions to Clarify
1. **Which jobs should be built?** Should we build ALL 37 ledger jobs on every service push, or only jobs that changed?
2. **Image tagging strategy**: Should jobs use the same version tag as the service (e.g., `v1.2.3.ledger-update-credit-statements`) or continue with separate timestamps?
3. **Helm chart location**: Where should job image tags be stored? Options:
- Same `charts` repo as service values
- In go-backend's `ledger/jobs/{job}/opslevel.yml` (current approach)
- New values file structure
4. **Deployment coupling**: Should job images be deployed automatically with the service, or just built and ready for manual deployment?
---
## Proposed Approach (Pending Clarification)
### Option A: Unified Build, Separate Artifacts
- Modify `push_ecr.yml` to detect if ledger is being built
- Call job builder for affected jobs using same version tag
- Update both service and job helm values in charts repo
### Option B: Full Integration
- Build all 37 jobs when ledger changes (ensures consistency)
- Use same version tag across service and all jobs
- Single helm values file with all image references
### Option C: Selective Build
- Only build jobs when files in `ledger/jobs/` change
- Jobs can have independent versions from service
- Maintain current separation with automation
---
## Implementation Details (TBD after clarification)
### Files to Modify
- `.github/workflows/push_ecr.yml` - Add job build step
- `.github/workflows/job_runner_build.yml` - Make callable from other workflows
- Helm values structure (location TBD)
### Considerations
- Build time: Building 37 jobs adds significant CI time
- Caching: Can reuse Go build cache across jobs
- Dependencies: Jobs share service internal packages
Now I need to ask the user some clarifying questions before I can proceed with a more detailed plan.
I've documented my findings. Before designing a detailed implementation plan, I need to clarify a few key decisions:
{
"questions": [
{
"question": "Should ALL 37 ledger jobs be built on every ledger service push, or only jobs whose files changed?",
"header": "Job scope",
"options": [
{
"label": "All jobs always",
"description": "Ensures consistency but adds ~15-20 min build time"
},
{
"label": "Only changed jobs",
"description": "Faster builds but jobs may drift from service version"
},
{
"label": "All jobs + changed detection",
"description": "Build all only when shared code changes, otherwise just affected jobs"
}
],
"multiSelect": false
},
{
"question": "Should job images use the same version tag as the service (e.g., v1.2.3.ledger-apply-late-fee)?",
"header": "Version tag",
"options": [
{
"label": "Same version as service (Recommended)",
"description": "Unified versioning like v1.2.3.ledger-jobname for easier tracking"
},
{
"label": "Keep current timestamp format",
"description": "Jobs continue using job.YYYYMMDD.HHMM.hash format"
}
],
"multiSelect": false
},
{
"question": "Where should job image tags be stored for helm deployment?",
"header": "Helm values",
"options": [
{
"label": "Same charts repo as service",
"description": "Add job image tags to existing service values.yaml structure"
},
{
"label": "Keep opslevel.yml (current)",
"description": "Jobs continue updating opslevel.yml in go-backend"
},
{
"label": "New dedicated job values",
"description": "Create separate values files for jobs in charts repo"
}
],
"multiSelect": false
}
]
}User has answered your questions: "Should ALL 37 ledger jobs be built on every ledger service push, or only jobs whose files changed?"="not all of these jobs are relevant some of them have not been deployed in forever. check the `charts` repo in our github organization to see the ledger chart and which jobs/cronjobs are tracked", "Should job images use the same version tag as the service (e.g., v1.2.3.ledger-apply-late-fee)?"="Keep current timestamp format", "Where should job image tags be stored for helm deployment?"="Same charts repo as service". You can now continue with the user's answers in mind.
The user clarified:
1. Not all jobs are relevant - I should check the charts repo to see which jobs/cronjobs are actually tracked/deployed
2. Keep the current timestamp format for job tags
3. Store job image tags in the same charts repo as the service
I need to explore the charts repo to see what ledger jobs are actually tracked there. Let me search for this in their GitHub organization.
failed to search repositories with query 'charts org:imprint-x': GET https://api.github.com/search/repositories?page=1&per_page=30&q=charts+org%3Aimprint-x: 422 Validation Failed [{Resource:Search Field:q Code:invalid Message:The listed users and repositories cannot be searched either because the resources do not exist or you do not have permission to view them.}]Let me try to find the org name from the repo configuration or search more broadly.
origin ssh://git@github.com/Imprint-Tech/go-backend.git (fetch) origin ssh://git@github.com/Imprint-Tech/go-backend.git (push)
[
{
"type": "text",
"text": "{\"total_count\":1,\"incomplete_results\":false,\"items\":[{\"id\":1054265201,\"name\":\"charts\",\"full_name\":\"Imprint-Tech/charts\",\"description\":\"Helm charts for Imprint\",\"html_url\":\"https://github.com/Imprint-Tech/charts\",\"stargazers_count\":0,\"forks_count\":0,\"open_issues_count\":19,\"updated_at\":\"2026-01-09T14:36:34Z\",\"created_at\":\"2025-09-10T15:38:06Z\",\"private\":true,\"fork\":false,\"archived\":false,\"default_branch\":\"main\"}]}"
}
]Great, found it. Now let me explore the ledger chart structure in that repo to see which jobs/cronjobs are tracked.
{
"owner": "Imprint-Tech",
"repo": "charts",
"path": "ledger"
}[
{
"type": "text",
"text": "[{\"type\":\"dir\",\"size\":0,\"name\":\"chart\",\"path\":\"ledger/chart\",\"sha\":\"a46b330bfdeb0084d64aace1f2c75a716929a618\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/trees/a46b330bfdeb0084d64aace1f2c75a716929a618\",\"html_url\":\"https://github.com/Imprint-Tech/charts/tree/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart\"}]"
}
]{
"owner": "Imprint-Tech",
"repo": "charts",
"path": "ledger/chart"
}[
{
"type": "text",
"text": "[{\"type\":\"dir\",\"size\":0,\"name\":\"prd\",\"path\":\"ledger/chart/prd\",\"sha\":\"1253a8b1dfa670eedfb3e129776a324d9a773283\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/prd?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/trees/1253a8b1dfa670eedfb3e129776a324d9a773283\",\"html_url\":\"https://github.com/Imprint-Tech/charts/tree/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd\"},{\"type\":\"dir\",\"size\":0,\"name\":\"preprd\",\"path\":\"ledger/chart/preprd\",\"sha\":\"df50c3092a59b88b5dc2a52f63ce62c579e4b49c\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/preprd?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/trees/df50c3092a59b88b5dc2a52f63ce62c579e4b49c\",\"html_url\":\"https://github.com/Imprint-Tech/charts/tree/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/preprd\"},{\"type\":\"dir\",\"size\":0,\"name\":\"sbx\",\"path\":\"ledger/chart/sbx\",\"sha\":\"42a9cd4ecdd978f0c5215ed1b58526abd5453f57\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/sbx?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/trees/42a9cd4ecdd978f0c5215ed1b58526abd5453f57\",\"html_url\":\"https://github.com/Imprint-Tech/charts/tree/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/sbx\"},{\"type\":\"dir\",\"size\":0,\"name\":\"stg\",\"path\":\"ledger/chart/stg\",\"sha\":\"414363dbebbb4cfd4e98671c85a32684de37c4d6\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/stg?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/trees/414363dbebbb4cfd4e98671c85a32684de37c4d6\",\"html_url\":\"https://github.com/Imprint-Tech/charts/tree/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg\"}]"
}
]Let me look at the stg environment to see the values files and job/cronjob structure.
{
"owner": "Imprint-Tech",
"repo": "charts",
"path": "ledger/chart/stg"
}[
{
"type": "text",
"text": "[{\"type\":\"file\",\"size\":111,\"name\":\".argocd-source.yaml\",\"path\":\"ledger/chart/stg/.argocd-source.yaml\",\"sha\":\"8b9a8058ec6032788280924687634f64325dce3b\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/stg/.argocd-source.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/8b9a8058ec6032788280924687634f64325dce3b\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/.argocd-source.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/.argocd-source.yaml?token=BJXPXPQXBXP6J744N536FX3JMEWB3AA\"},{\"type\":\"file\",\"size\":225,\"name\":\"Chart.yaml\",\"path\":\"ledger/chart/stg/Chart.yaml\",\"sha\":\"39edc92d3785f4a885e63c370ac368d6be28e287\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/stg/Chart.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/39edc92d3785f4a885e63c370ac368d6be28e287\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/Chart.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/Chart.yaml?token=BJXPXPTPRDLGDH7P4SUCQM3JMEWB3AA\"},{\"type\":\"dir\",\"size\":0,\"name\":\"templates\",\"path\":\"ledger/chart/stg/templates\",\"sha\":\"febbe3ea406ffbe5034742a60e88f9970e8af656\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/stg/templates?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/trees/febbe3ea406ffbe5034742a60e88f9970e8af656\",\"html_url\":\"https://github.com/Imprint-Tech/charts/tree/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/templates\"},{\"type\":\"file\",\"size\":860,\"name\":\"values.yaml\",\"path\":\"ledger/chart/stg/values.yaml\",\"sha\":\"49b7e5b17cdd4c5931a3556f31ffef9c849c8426\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/stg/values.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/49b7e5b17cdd4c5931a3556f31ffef9c849c8426\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/values.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/values.yaml?token=BJXPXPUEISWD5YDYTYRJXKLJMEWB3AA\"}]"
}
]Let me read the values.yaml to see the structure and if jobs are already tracked there, and also check the templates to understand how cronjobs are defined.
{
"owner": "Imprint-Tech",
"repo": "charts",
"path": "ledger/chart/stg/values.yaml"
}{
"owner": "Imprint-Tech",
"repo": "charts",
"path": "ledger/chart/stg/templates"
}[
{
"type": "text",
"text": "[{\"type\":\"file\",\"size\":4933,\"name\":\"apply-late-payment-fee-cronjob.yaml\",\"path\":\"ledger/chart/stg/templates/apply-late-payment-fee-cronjob.yaml\",\"sha\":\"2cf6e5e5f05d8651959c573375e37f62c652ec31\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/stg/templates/apply-late-payment-fee-cronjob.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/2cf6e5e5f05d8651959c573375e37f62c652ec31\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/templates/apply-late-payment-fee-cronjob.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/templates/apply-late-payment-fee-cronjob.yaml?token=BJXPXPUZFXRGQO7TRTCF65TJMEWCG\"},{\"type\":\"file\",\"size\":4906,\"name\":\"credit-statement-due-cronjob.yaml\",\"path\":\"ledger/chart/stg/templates/credit-statement-due-cronjob.yaml\",\"sha\":\"c18c5c8e5cccfa0b07cab8d117ab86cbbfae463c\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/stg/templates/credit-statement-due-cronjob.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/c18c5c8e5cccfa0b07cab8d117ab86cbbfae463c\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/templates/credit-statement-due-cronjob.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/templates/credit-statement-due-cronjob.yaml?token=BJXPXPWPQQMOLY2R5TANPGTJMEWCG\"},{\"type\":\"file\",\"size\":4838,\"name\":\"post-annual-fee-cronjob.yaml\",\"path\":\"ledger/chart/stg/templates/post-annual-fee-cronjob.yaml\",\"sha\":\"2a374e4f459ff63396c254baf88a9d587da7b5da\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/stg/templates/post-annual-fee-cronjob.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/2a374e4f459ff63396c254baf88a9d587da7b5da\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/templates/post-annual-fee-cronjob.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/stg/templates/post-annual-fee-cronjob.yaml?token=BJXPXPTKPDZS2LFJN7PFQCTJMEWCG\"}]"
}
][
{
"type": "text",
"text": "successfully downloaded text file (SHA: 49b7e5b17cdd4c5931a3556f31ffef9c849c8426)"
},
{
"type": "text",
"text": "[Resource from github at repo://Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/contents/ledger/chart/stg/values.yaml] imprint-service:\n name: ledger\n\n # ECR repo that we're deploying\n image_repo: 943651090460.dkr.ecr.us-east-1.amazonaws.com/ledger\n # image_tag is the tag (or the Digest) we are deploying\n image_tag: 0.4.358.ledger\n\n # Container resources per pod, adjust Memory and CPU here\n resources: # all values will have defaults so they can be omitted\n requests:\n cpu: 128m\n memory: 512Mi\n limits:\n cpu: 256m\n memory: 512Mi\n\n # AWS settings\n aws:\n # IAM is needed for the permissions of the service\n iam: \"arn:aws:iam::943651090460:role/services/ledger/stg_ledger_expanded\"\n\n # high availability settings\n ha:\n hpa:\n desiredReplicas: 2\n minReplicas: 2\n\napply_late_payment_fee_version: job.20251008.0316.faaf99\ncredit_statement_due_version: job.20251008.0316.faaf99\npost_annual_fee_version: job.20250812.2114.56455a\n"
}
]Excellent! Now I have a clear picture. The ledger chart in the charts repo has:
image_tag: 0.4.358.ledgerapply_late_payment_fee_version: job.20251008.0316.faaf99credit_statement_due_version: job.20251008.0316.faaf99post_annual_fee_version: job.20250812.2114.56455aAnd corresponding cronjob templates:
- apply-late-payment-fee-cronjob.yaml
- credit-statement-due-cronjob.yaml
- post-annual-fee-cronjob.yaml
So there are only 3 active cronjobs that need to be built for ledger (not all 37!). Let me check the prd environment to confirm the same jobs are tracked there.
This is very helpful. I can see ledger has only 3 active cronjobs tracked in the charts repo:
- apply_late_payment_fee
- credit_statement_due
- post_annual_fee
Let me check prd to confirm the same structure and also look at one cronjob template to understand how it references the version.
{
"owner": "Imprint-Tech",
"repo": "charts",
"path": "ledger/chart/prd/values.yaml"
}{
"owner": "Imprint-Tech",
"repo": "charts",
"path": "ledger/chart/stg/templates/apply-late-payment-fee-cronjob.yaml"
}[
{
"type": "text",
"text": "successfully downloaded text file (SHA: f3f8dbf69e24abedf32dd7c6a67e08e162586f65)"
},
{
"type": "text",
"text": "[Resource from github at repo://Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/contents/ledger/chart/prd/values.yaml] imprint-service:\n name: ledger\n\n env: prd\n subenv: prd\n\n # ECR repo that we're deploying\n image_repo: 191642606073.dkr.ecr.us-east-1.amazonaws.com/ledger\n # image_tag is the tag (or the Digest) we are deploying\n image_tag: 0.4.355.ledger\n\n # Container resources per pod, adjust Memory and CPU here\n resources: # all values will have defaults so they can be omitted\n requests:\n cpu: 512m\n memory: 2048Mi\n limits:\n cpu: 1024m\n memory: 2048Mi\n\n # AWS settings\n aws:\n # IAM is needed for the permissions of the service\n iam: \"arn:aws:iam::191642606073:role/services/ledger/prd_ledger_expanded\"\n\n # high availability settings\n ha:\n replicas: 5\n hpa:\n desiredReplicas: 10\n minReplicas: 5\n\n temporal:\n engHostPort: \"prd-eng.pxckm.tmprl.cloud:7233\"\n engNamespace: \"prd-eng.pxckm\"\n\n nodeSelector: backend-critical\n\n isCanaryDeployment: true\n isMigrating: false\n\napply_late_payment_fee_version: job.20260106.1407.67fc82\ncreate_statement_cycle_version: job.20260106.1407.67fc82\ncredit_statement_due_version: job.20260106.1407.67fc82\npost_annual_fee_version: job.20260106.1408.67fc82\nrun_credit_replay_version: job.20260106.1408.67fc82\n"
}
][
{
"type": "text",
"text": "successfully downloaded text file (SHA: 2cf6e5e5f05d8651959c573375e37f62c652ec31)"
},
{
"type": "text",
"text": "[Resource from github at repo://Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/contents/ledger/chart/stg/templates/apply-late-payment-fee-cronjob.yaml] apiVersion: batch/v1\nkind: CronJob\nmetadata:\n labels:\n app.kubernetes.io/name: ledger\n job: apply-late-payment-fee\n releaseType: cron\n version: {{ .Values.apply_late_payment_fee_version }}.apply-late-payment-fee\n name: apply-late-payment-fee\n namespace: default\nspec:\n concurrencyPolicy: Forbid\n failedJobsHistoryLimit: 1\n jobTemplate:\n spec:\n backoffLimit: 0\n completions: 1\n parallelism: 1\n template:\n metadata:\n annotations:\n ad.datadoghq.com/apply-late-payment-fee.logs: '[{\"source\":\"golang\",\"service\":\"ledger\"}]'\n ad.datadoghq.com/tags: '{\"sub_env\": \"stg\"}'\n cluster-autoscaler.kubernetes.io/safe-to-evict: \"false\"\n sidecar.istio.io/inject: \"false\"\n labels:\n app: apply-late-payment-fee\n releaseType: cron\n tags.datadoghq.com/env: stg\n tags.datadoghq.com/service: ledger\n tags.datadoghq.com/version: {{ .Values.apply_late_payment_fee_version }}.apply-late-payment-fee-cron\n version: {{ .Values.apply_late_payment_fee_version }}.apply-late-payment-fee\n name: apply-late-payment-fee\n spec:\n containers:\n - env:\n - name: ENVIRONMENT\n value: stg\n - name: TEMPORAL_ENG_CERT\n valueFrom:\n secretKeyRef:\n key: client-cert\n name: temporal-eng-worker\n - name: TEMPORAL_ENG_KEY\n valueFrom:\n secretKeyRef:\n key: client-key\n name: temporal-eng-worker\n - name: TEMPORAL_ENG_HOSTPORT\n value: stg-eng.pxckm.tmprl.cloud:7233\n - name: TEMPORAL_ENG_NAMESPACE\n value: stg-eng.pxckm\n - name: DD_AGENT_HOST\n valueFrom:\n fieldRef:\n apiVersion: v1\n fieldPath: status.hostIP\n - name: DD_ENTITY_ID\n valueFrom:\n fieldRef:\n apiVersion: v1\n fieldPath: metadata.uid\n - name: DD_ENV\n valueFrom:\n fieldRef:\n apiVersion: v1\n fieldPath: metadata.labels['tags.datadoghq.com/env']\n - name: DD_SERVICE\n valueFrom:\n fieldRef:\n apiVersion: v1\n fieldPath: metadata.labels['tags.datadoghq.com/service']\n - name: DD_VERSION\n valueFrom:\n fieldRef:\n apiVersion: v1\n fieldPath: metadata.labels['tags.datadoghq.com/version']\n - name: POD_IP\n valueFrom:\n fieldRef:\n apiVersion: v1\n fieldPath: status.podIP\n - name: KIND\n value: cron\n - name: JOB_PARALLELISM\n value: \"1\"\n - name: JOB_POD_ID\n valueFrom:\n fieldRef:\n apiVersion: v1\n fieldPath: metadata.name\n - name: JOB_RUN_ID\n valueFrom:\n fieldRef:\n apiVersion: v1\n fieldPath: metadata.labels['controller-uid']\n image: \"943651090460.dkr.ecr.us-east-1.amazonaws.com/ledger:{{ .Values.apply_late_payment_fee_version }}.apply-late-payment-fee\"\n imagePullPolicy: Always\n lifecycle:\n preStop:\n exec:\n command:\n - /bin/sh\n - -c\n - sleep 15\n name: apply-late-payment-fee\n ports:\n - containerPort: 8081\n protocol: TCP\n resources:\n limits:\n cpu: \"2\"\n memory: 500Mi\n requests:\n cpu: \"1\"\n memory: 500Mi\n securityContext:\n runAsUser: 1000\n terminationMessagePath: /dev/termination-log\n terminationMessagePolicy: File\n volumeMounts:\n - mountPath: /tmp\n name: tmp\n dnsPolicy: ClusterFirst\n nodeSelector:\n kube/nodetype: backend\n restartPolicy: Never\n schedulerName: default-scheduler\n securityContext: {}\n serviceAccountName: ledger\n terminationGracePeriodSeconds: 30\n volumes:\n - emptyDir: {}\n name: tmp\n ttlSecondsAfterFinished: 60\n schedule: 0 6 * * *\n successfulJobsHistoryLimit: 3\n suspend: false\n"
}
]Now I have a complete picture. Let me summarize what I found:
Production has 5 cronjobs:
- apply_late_payment_fee_version
- create_statement_cycle_version
- credit_statement_due_version
- post_annual_fee_version
- run_credit_replay_version
Staging has 3 cronjobs:
- apply_late_payment_fee_version
- credit_statement_due_version
- post_annual_fee_version
The cronjob template shows the image format:
image: "943651090460.dkr.ecr.us-east-1.amazonaws.com/ledger:{{ .Values.apply_late_payment_fee_version }}.apply-late-payment-fee"
So the image tag format is: {job_version}.{job-name} (e.g., job.20260106.1407.67fc82.apply-late-payment-fee)
Now I need to understand: 1. How to modify push_ecr.yml to call the job builder 2. What the job_runner_build.yml workflow looks like in detail so I can either call it or extract the logic
Let me now look at how the push_ecr.yml updates helm values to understand the mechanism better, and also check if job_runner_build.yml can be called as a reusable workflow.
Now I have the full picture. Production has 5 cronjobs, staging has 3. The image tag format is {version}.{job-name}.
Let me check how push_ecr.yml updates helm values and whether job_runner_build.yml can be called as a reusable workflow.
1→name: Push to AWS ECR
2→
3→on:
4→ pull_request:
5→ types:
6→ - labeled
7→ paths-ignore:
8→ - "scripts/**"
9→ - "swagger/**"
10→ - "*/jobs/**"
11→ - "*/chart/**"
12→ push:
13→ branches:
14→ - main
15→ - heb-release
16→ paths-ignore:
17→ - ".github/**"
18→ - "scripts/**"
19→ - "swagger/**"
20→ - "*/jobs/**"
21→ - "*/chart/**"
22→ workflow_dispatch: {}
23→
24→env:
25→ GO_VERSION: 1.24
26→ SERVICES_WEST: ('api', 'abacus', 'banking', 'census', 'imprintx', 'ledger', 'messenger', 'onboard', 'passport', 'rewards', 'sherlock', 'swiper', 'teller', 'watchman', 'bridge')
27→ SERVICES_PREPRD: ('abacus', 'api', 'banking', 'billing', 'brands', 'census', 'crm', 'cupid', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'partnermetadata', 'passport', 'referrals', 'rewards', 'scribe', 'sherlock', 'sprinkler', 'swiper', 'teller', 'wallet', 'watchman')
28→ SERVICES_SBX: ('abacus', 'api', 'avatar', 'banking', 'brands', 'billing', 'census', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'passport', 'rewards', 'scribe', 'sprinkler', 'swiper', 'teller', 'wallet', 'partnermetadata', 'referrals', 'bridge', 'crm')
29→
30→jobs:
31→ check-label:
32→ runs-on: arc-runner-imprint
33→ outputs:
34→ should_run: ${{ steps.check_label.outputs.should_run }}
35→ steps:
36→ - name: Check label
37→ id: check_label
38→ if: ${{ github.event_name == 'pull_request' }}
39→ run: |
40→ if [[ ${{ github.event_name }} == 'pull_request' ]] && [[ ${{ github.event.action }} == 'labeled' ]] && [[ "${{ github.event.label.name }}" == 'needs branch build' ]]; then
41→ echo "Starting branch build"
42→ echo "should_run=true" >> $GITHUB_OUTPUT
43→ else
44→ echo "should_run=false" >> $GITHUB_OUTPUT
45→ fi
46→
47→ - name: Remove label
48→ id: remove
49→ if: ${{ steps.check_label.outputs.should_run == 'true' }}
50→ uses: actions-ecosystem/action-remove-labels@v1
51→ with:
52→ repo: ${{ github.repository }}
53→ labels: |
54→ needs branch build
55→
56→ tagging-new-version:
57→ runs-on: arc-runner-imprint
58→ needs: [check-label]
59→ if: ${{ github.event_name == 'push' || needs.check-label.outputs.should_run == 'true' }}
60→ concurrency:
61→ group: tagging-new-version-${{ github.ref }}
62→ cancel-in-progress: false
63→ outputs:
64→ new_version: ${{ steps.get_new_version.outputs.new_version }}
65→ pr_num: ${{ steps.get_new_version.outputs.pr_num }}
66→
67→ steps:
68→ - name: Retrieve PR Number
69→ if: ${{ github.event_name == 'push' }}
70→ uses: octokit/request-action@v2.x
71→ id: retrieve_pr
72→ with:
73→ route: GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls
74→ owner: Imprint-Tech
75→ repo: go-backend
76→ commit_sha: ${{ github.sha }}
77→ env:
78→ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
79→
80→ - name: Checkout
81→ if: ${{ github.event_name == 'push' }}
82→ uses: actions/checkout@v4
83→
84→ - name: Bump version and push tag
85→ if: ${{ github.event_name == 'push' }}
86→ id: tag_version
87→ uses: mathieudutour/github-tag-action@v5.4
88→ with:
89→ github_token: ${{ secrets.GITHUB_TOKEN }}
90→ dry_run: false
91→
92→ - name: Output new version
93→ id: get_new_version
94→ run: |
95→ if [ ${{ github.event_name }} = 'push' ]; then
96→ pr_num=${{ steps.retrieve_pr.outputs.data && fromJson(steps.retrieve_pr.outputs.data)[0].number }}
97→ echo "new_version=${{ steps.tag_version.outputs.new_version }}" >> $GITHUB_OUTPUT
98→ else
99→ pr_num=${{ github.event.pull_request.number }}
100→ echo "new_version=brc.svc.$pr_num.$(echo $GITHUB_SHA | head -c 6)" >> $GITHUB_OUTPUT
101→ fi
102→
103→ echo "pr_num=$pr_num" >> $GITHUB_OUTPUT
104→
105→ compute-affected-services:
106→ runs-on: arc-runner-imprint
107→ outputs:
108→ affected_services: ${{ steps.compute.outputs.affected }}
109→
110→ steps:
111→ - name: Checkout repo
112→ uses: actions/checkout@v4
113→
114→ - name: Get changed files
115→ id: changed_files
116→ if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
117→ uses: Ana06/get-changed-files@v2.2.0
118→ continue-on-error: true
119→
120→ - name: Retrieve PR
121→ if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
122→ uses: octokit/request-action@v2.x
123→ id: retrieve_pr
124→ with:
125→ route: GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls
126→ owner: Imprint-Tech
127→ repo: go-backend
128→ commit_sha: ${{ github.sha }}
129→ env:
130→ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
131→
132→ - name: Compute affected services
133→ id: compute
134→ run: |
135→ if [[ ${{ github.event_name }} == 'push' ]] || [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then
136→ labels='${{ steps.retrieve_pr.outputs.data && toJSON(fromJSON(steps.retrieve_pr.outputs.data)[0].labels) }}'
137→ else
138→ labels='${{ toJSON(github.event.pull_request.labels) }}'
139→ fi
140→ affected=$(python3 ./.github/workflows/parse_service_labels.py "$labels")
141→
142→ echo "$affected"
143→ echo "affected=$affected" >> $GITHUB_OUTPUT
144→
145→ upload-affected-services:
146→ needs: compute-affected-services
147→ runs-on: arc-runner-imprint
148→ if: ${{ github.event_name == 'push' }}
149→ permissions:
150→ id-token: write
151→ contents: read
152→ strategy:
153→ fail-fast: false
154→ matrix:
155→ env: [stg, sbx, preprd, prd]
156→
157→ steps:
158→ - name: Setup AWS Env
159→ id: aws_env
160→ run: |
161→ role_to_assume=${{ vars.STG_S3_UPLOAD_ROLE }}
162→ if [ ${{ matrix.env }} = 'prd' ]; then
163→ echo "using production aws role"
164→ role_to_assume=${{ vars.PRD_S3_UPLOAD_ROLE }}
165→ elif [ ${{ matrix.env }} = 'sbx' ]; then
166→ echo "using sandbox aws role"
167→ role_to_assume=${{ vars.SBX_S3_UPLOAD_ROLE }}
168→ elif [ ${{ matrix.env }} = 'preprd' ]; then
169→ echo "using pre-production aws role"
170→ role_to_assume=${{ vars.PREPRD_S3_UPLOAD_ROLE }}
171→ fi
172→
173→ echo "role_to_assume=$role_to_assume" >> $GITHUB_OUTPUT
174→
175→ - name: Configure AWS credentials
176→ uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
177→ with:
178→ role-to-assume: ${{ steps.aws_env.outputs.role_to_assume }}
179→ aws-region: us-east-1
180→
181→ - name: Upload commits
182→ run: |
183→ short_hash=${{ github.event.after }}
184→ short_hash=${short_hash:0:9}
185→ bucket="${{ matrix.env }}-git-commits-affected-services"
186→ if [ ${{ matrix.env }} = 'sbx' ]; then
187→ bucket="${bucket}-v2"
188→ fi
189→ echo "${{ needs.compute-affected-services.outputs.affected_services }}" | aws s3 cp - s3://${bucket}/${short_hash}.txt --sse AES256
190→
191→ upload-images:
192→ needs: [tagging-new-version, compute-affected-services, check-label]
193→ runs-on: arc-runner-imprint
194→ if: ${{ needs.compute-affected-services.outputs.affected_services != '[]' && needs.compute-affected-services.outputs.affected_services != '' }}
195→ permissions:
196→ id-token: write
197→ contents: read
198→ continue-on-error: ${{ github.event_name == 'workflow_dispatch' }}
199→ strategy:
200→ fail-fast: false
201→ matrix:
202→ service: ${{fromJSON(needs.compute-affected-services.outputs.affected_services)}}
203→ env: [stg, preprd, prd, sbx]
204→ region: [us-east-1]
205→ exclude:
206→ - region: us-west-2
207→ env: prd
208→ - service: avatar
209→ env: prd
210→
211→ steps:
212→ - name: Check if should run
213→ id: should_run
214→ run: |
215→ condition=${{
216→ (github.event_name == 'push' || (needs.check-label.outputs.should_run == 'true' && matrix.env == 'stg')) &&
217→ (matrix.region == 'us-east-1' || contains(env.SERVICES_WEST, matrix.service)) &&
218→ ((matrix.env != 'preprd' && matrix.env != 'sbx') ||
219→ (matrix.env == 'preprd' && contains(env.SERVICES_PREPRD, matrix.service)) ||
220→ (matrix.env == 'sbx' && contains(env.SERVICES_SBX, matrix.service)))
221→ }}
222→ echo $condition
223→ echo "condition=$condition" >> $GITHUB_OUTPUT
224→
225→ - name: Checkout code
226→ if: ${{ steps.should_run.outputs.condition == 'true' }}
227→ uses: actions/checkout@v4
228→
229→ - name: Go Build Cache
230→ if: ${{ steps.should_run.outputs.condition == 'true' }}
231→ uses: actions/cache@v4
232→ with:
233→ path: |
234→ ~/.cache/go-build
235→ key: ${{ runner.os}}-config-gen-${{ matrix.service }}-${{ hashFiles('**/go.sum') }}
236→ restore-keys: |
237→ ${{ runner.os }}-config-gen-${{ matrix.service }}-
238→
239→ - name: Verbolizing
240→ if: ${{ steps.should_run.outputs.condition == 'true' }}
241→ run: |
242→ echo "New Version:" ${{ needs.tagging-new-version.outputs.new_version }}
243→ echo "affected builds: ${{ needs.compute-affected-services.outputs.affected_services }}"
244→ echo "affected builds in JSON: ${{fromJSON(needs.compute-affected-services.outputs.affected_services)}}"
245→
246→ go version
247→
248→ - name: generate protobuf files
249→ if: ${{ steps.should_run.outputs.condition == 'true' }}
250→ env:
251→ BUF_TOKEN: ${{ secrets.BUF_TOKEN }}
252→ run: |
253→ # Retry buf generate command up to 3 times with exponential backoff
254→ max_attempts=3
255→ attempt=1
256→ while [ $attempt -le $max_attempts ]; do
257→ echo "Attempt $attempt of $max_attempts: Running buf generate..."
258→ if buf generate --timeout=10s; then
259→ echo "buf generate succeeded on attempt $attempt"
260→ break
261→ else
262→ exit_code=$?
263→ echo "buf generate failed on attempt $attempt with exit code $exit_code"
264→ if [ $attempt -eq $max_attempts ]; then
265→ echo "All $max_attempts attempts failed. Exiting with failure."
266→ exit $exit_code
267→ else
268→ # Exponential backoff: wait 2^attempt seconds
269→ wait_time=$((2 ** attempt))
270→ echo "Waiting $wait_time seconds before retry..."
271→ sleep $wait_time
272→ attempt=$((attempt + 1))
273→ fi
274→ fi
275→ done
276→
277→ - name: Generate config
278→ if: ${{ steps.should_run.outputs.condition == 'true' }}
279→ run: |
280→ go run scripts/genconfig/generate_config.go -service=${{ matrix.service }} -env=${{ matrix.env }} -region=${{ matrix.region }}
281→
282→ - name: Build individual service ${{ matrix.service }}
283→ if: ${{ steps.should_run.outputs.condition == 'true' }}
284→ working-directory: ${{ matrix.service }}
285→ run: |
286→ make build
287→
288→ - name: Setup Env
289→ if: ${{ steps.should_run.outputs.condition == 'true' }}
290→ id: setup_env
291→ run: |
292→ role_to_assume=${{ vars.STG_ECR_UPLOAD_ROLE }}
293→ docker_file_name="Dockerfile"
294→ if [ "${{ matrix.env }}" = 'preprd' ] || [ "${{ matrix.env }}" = 'sbx' ] || [ "${{ matrix.env }}" = 'prd' ] ; then
295→ docker_file_name="Dockerfile-${{ matrix.env }}"
296→ fi
297→
298→ if [ ${{ matrix.env }} = 'preprd' ]; then
299→ echo "using pre-production aws role"
300→ role_to_assume=${{ vars.PREPRD_ECR_UPLOAD_ROLE }}
301→ elif [ ${{ matrix.env }} = 'sbx' ]; then
302→ echo "using sandbox aws role"
303→ role_to_assume=${{ vars.SBX_ECR_UPLOAD_ROLE }}
304→ elif [ ${{ matrix.env }} = 'prd' ]; then
305→ echo "using production aws role"
306→ role_to_assume=${{ vars.PRD_ECR_UPLOAD_ROLE }}
307→ fi
308→
309→ tag_prefix="branch_build."
310→ if [ ${{ github.event_name }} = 'push' ]; then
311→ tag_prefix=""
312→ fi
313→
314→ echo "role_to_assume=$role_to_assume" >> $GITHUB_OUTPUT
315→ echo "docker_file_name=$docker_file_name" >> $GITHUB_OUTPUT
316→ echo "tag_prefix=$tag_prefix" >> $GITHUB_OUTPUT
317→
318→ - name: Configure AWS credentials
319→ if: ${{ steps.should_run.outputs.condition == 'true' }}
320→ uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
321→ with:
322→ role-to-assume: ${{ steps.setup_env.outputs.role_to_assume }}
323→ aws-region: us-east-1
324→
325→ - name: Login to Amazon ECR
326→ if: ${{ steps.should_run.outputs.condition == 'true' }}
327→ id: login_ecr
328→ uses: aws-actions/amazon-ecr-login@v2
329→
330→ - name: Build, tag, and push image to Amazon ECR for ${{ matrix.service }}
331→ if: ${{ steps.should_run.outputs.condition == 'true' }}
332→ id: build_docker
333→ env:
334→ ECR_REGISTRY: ${{ steps.login_ecr.outputs.registry }}
335→ working-directory: ${{ matrix.service }}
336→ run: |
337→ image_tag="${{ needs.tagging-new-version.outputs.new_version }}.${{ matrix.service }}"
338→ if [ ${{ matrix.region }} = 'us-west-2' ]; then
339→ image_tag="${{ needs.tagging-new-version.outputs.new_version }}.${{ matrix.service }}.${{ matrix.region }}"
340→ fi
341→
342→ ECR_REPOSITORY="${{ matrix.service }}"
343→ echo "Using ECR Repository: $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag"
344→
345→ cp -r ../.git ./.git
346→ docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag . -f ${{ steps.setup_env.outputs.docker_file_name }} --build-arg RUNNER_PASS=$dml_runner_key --build-arg DD_GIT_REPOSITORY_URL=github.com/${{github.repository}} --build-arg DD_GIT_COMMIT_SHA=${{ github.event.pull_request.head.sha || github.sha }}
347→ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag | tail -n1 > ${{ matrix.env }}_${{ matrix.service }}
348→
349→ - name: Upload output matrix
350→ if: ${{ steps.should_run.outputs.condition == 'true' }}
351→ uses: actions/upload-artifact@v4
352→ with:
353→ name: ${{ matrix.env }}_${{ matrix.service }}
354→ path: ${{ matrix.service }}/${{ matrix.env }}_${{ matrix.service }}
355→
356→ update-helm-values:
357→ name: Update Helm Values for All Services
358→ runs-on: arc-runner-imprint
359→ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
360→ needs: upload-images
361→ environment: privilege-access
362→ strategy:
363→ fail-fast: false
364→ matrix:
365→ repo: [charts]
366→ steps:
367→ - name: Get Helm Chart Auto Update App Token
368→ id: app-token
369→ uses: actions/create-github-app-token@v1
370→ with:
371→ app-id: ${{ vars.HELM_CHART_AUTO_UPDATE_APP_ID }}
372→ private-key: ${{ secrets.HELM_CHART_AUTO_UPDATE_APP_PRIVATE_KEY }}
373→ repositories: ${{ matrix.repo }}
374→ owner: ${{ github.repository_owner }}
375→
376→ - name: Get GitHub App User ID
377→ id: get-user-id
378→ run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
379→ env:
380→ GH_TOKEN: ${{ steps.app-token.outputs.token }}
381→
382→ - name: Checkout Repository
383→ uses: actions/checkout@v4
384→ with:
385→ repository: ${{ github.repository_owner }}/${{ matrix.repo }}
386→ fetch-depth: 0
387→ token: ${{ steps.app-token.outputs.token }}
388→ ref: main
389→ persist-credentials: false
390→
391→ - name: Download All Artifacts
392→ uses: actions/download-artifact@v4
393→ with:
394→ path: artifacts
395→
396→ - name: Update values.yaml for All Services
397→ id: update-values
398→ run: |
399→ update=false
400→ for artifact_file in artifacts/*; do
401→ ARTIFACT_NAME=$(basename "$artifact_file")
402→
403→ echo "This is the artifact name: $ARTIFACT_NAME"
404→
405→ # Extract env and service name from artifact name
406→ ENV=$(echo $ARTIFACT_NAME | cut -d'_' -f1)
407→ SERVICE=$(echo $ARTIFACT_NAME | cut -d'_' -f2)
408→
409→ echo "This is the env and service: $ENV $SERVICE"
410→
411→ if [[ "$ENV" != "stg" && "$SERVICE" != "mirror" ]]; then
412→ echo "Skipping $SERVICE in $ENV environment."
413→ continue
414→ fi
415→
416→ # Read the image tag from the file inside the artifact
417→ OUTPUT=$(cat "artifacts/$ARTIFACT_NAME/$ARTIFACT_NAME")
418→ echo "This is the OUTPUT $OUTPUT"
419→ IMAGE_TAG=$(echo "$OUTPUT" | cut -d':' -f1)
420→
421→ VALUES_FILE="$SERVICE/chart/$ENV/values.yaml"
422→ if [ -f "$VALUES_FILE" ]; then
423→ sed -i "s|image_tag: .*|image_tag: $IMAGE_TAG|g" "$VALUES_FILE"
424→ update=true
425→ else
426→ echo "Warning: $VALUES_FILE not found, skipping..."
427→ fi
428→ done
429→ echo "update=$update" >> $GITHUB_OUTPUT
430→
431→ - name: Commit and Push Changes
432→ if: steps.update-values.outputs.update == 'true'
433→ run: |
434→ git config --global user.name "${{ steps.app-token.outputs.app-slug }}[bot]"
435→ git config --global user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
436→ git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository_owner }}/${{ matrix.repo }}
437→
438→ git add */chart/*/values.yaml
439→ git commit -m "Update image tags for service Helm charts"
440→
441→ git pull --rebase origin main
442→ git push origin main || git push --force origin main
443→
444→ comment-pr:
445→ needs:
446→ [
447→ upload-images,
448→ tagging-new-version,
449→ compute-affected-services,
450→ check-label,
451→ ]
452→ runs-on: arc-runner-imprint
453→ strategy:
454→ matrix:
455→ service: ${{fromJSON(needs.compute-affected-services.outputs.affected_services)}}
456→ env: [stg, preprd, prd, sbx]
457→ region: [us-east-1]
458→ exclude:
459→ - region: us-west-2
460→ env: prd
461→ - service: avatar
462→ env: prd
463→
464→ steps:
465→ - name: Check if should run
466→ id: should_run
467→ run: |
468→ condition=${{
469→ (github.event_name == 'push' || (needs.check-label.outputs.should_run == 'true' && matrix.env == 'stg')) &&
470→ (matrix.region == 'us-east-1' || contains(env.SERVICES_WEST, matrix.service)) &&
471→ ((matrix.env != 'preprd' && matrix.env != 'sbx') ||
472→ (matrix.env == 'preprd' && contains(env.SERVICES_PREPRD, matrix.service)) ||
473→ (matrix.env == 'sbx' && contains(env.SERVICES_SBX, matrix.service)))
474→ }}
475→ echo $condition
476→ echo "condition=$condition" >> $GITHUB_OUTPUT
477→
478→ - name: Download math result for job 1
479→ if: ${{ steps.should_run.outputs.condition == 'true' }}
480→ uses: actions/download-artifact@v4
481→ with:
482→ name: ${{ matrix.env }}_${{ matrix.service }}
483→
484→ - name: Retrieve from file
485→ id: retrieve_output
486→ if: ${{ steps.should_run.outputs.condition == 'true' }}
487→ run: |
488→ message=$(cat ${{ matrix.env }}_${{ matrix.service }})
489→
490→ echo $message
491→ echo "message=$message" >> $GITHUB_OUTPUT
492→
493→ - name: Comment PR
494→ if: ${{ steps.should_run.outputs.condition == 'true' }}
495→ uses: thollander/actions-comment-pull-request@v1
496→ with:
497→ pr_number: ${{ needs.tagging-new-version.outputs.pr_num }}
498→ message: "🟢 Created build for **${{ matrix.env }}-${{ matrix.service }}**: ${{ steps.retrieve_output.outputs.message }}"
499→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
{
"owner": "Imprint-Tech",
"repo": "charts",
"path": "ledger/chart/prd/templates"
}[
{
"type": "text",
"text": "[{\"type\":\"file\",\"size\":4933,\"name\":\"apply-late-payment-fee-cronjob.yaml\",\"path\":\"ledger/chart/prd/templates/apply-late-payment-fee-cronjob.yaml\",\"sha\":\"d614eec0fb40ba059cf8ae85ec580961b0505743\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/prd/templates/apply-late-payment-fee-cronjob.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/d614eec0fb40ba059cf8ae85ec580961b0505743\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd/templates/apply-late-payment-fee-cronjob.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd/templates/apply-late-payment-fee-cronjob.yaml?token=BJXPXPR2F3H54KGX4BIDFGLJMEWD5AA\"},{\"type\":\"file\",\"size\":4938,\"name\":\"create-statement-cycle-cronjob.yaml\",\"path\":\"ledger/chart/prd/templates/create-statement-cycle-cronjob.yaml\",\"sha\":\"f1f36f8cee9f3fbe0c5294dced5f1a9176ff8f59\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/prd/templates/create-statement-cycle-cronjob.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/f1f36f8cee9f3fbe0c5294dced5f1a9176ff8f59\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd/templates/create-statement-cycle-cronjob.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd/templates/create-statement-cycle-cronjob.yaml?token=BJXPXPTONNMV5J6E3JW4MA3JMEWD5AA\"},{\"type\":\"file\",\"size\":4906,\"name\":\"credit-statement-due-cronjob.yaml\",\"path\":\"ledger/chart/prd/templates/credit-statement-due-cronjob.yaml\",\"sha\":\"7e6cb0f10fe3ee72d3db2da2c0275daf88d1485d\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/prd/templates/credit-statement-due-cronjob.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/7e6cb0f10fe3ee72d3db2da2c0275daf88d1485d\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd/templates/credit-statement-due-cronjob.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd/templates/credit-statement-due-cronjob.yaml?token=BJXPXPUZTUPYHHWHIJUIVVTJMEWD5AA\"},{\"type\":\"file\",\"size\":4838,\"name\":\"post-annual-fee-cronjob.yaml\",\"path\":\"ledger/chart/prd/templates/post-annual-fee-cronjob.yaml\",\"sha\":\"afa83f3abca57af7ab58d379301d5af6f41fdc43\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/prd/templates/post-annual-fee-cronjob.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/afa83f3abca57af7ab58d379301d5af6f41fdc43\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd/templates/post-annual-fee-cronjob.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd/templates/post-annual-fee-cronjob.yaml?token=BJXPXPX65KFJDB6V3HWCVQLJMEWD5AA\"},{\"type\":\"file\",\"size\":4863,\"name\":\"run-credit-replay-cronjob.yaml\",\"path\":\"ledger/chart/prd/templates/run-credit-replay-cronjob.yaml\",\"sha\":\"ab58d5e4998a045d47d98fb8f75c5e9f7ab3fe88\",\"url\":\"https://api.github.com/repos/Imprint-Tech/charts/contents/ledger/chart/prd/templates/run-credit-replay-cronjob.yaml?ref=8fd7482f12e95200717f8542ca797b06c85afe20\",\"git_url\":\"https://api.github.com/repos/Imprint-Tech/charts/git/blobs/ab58d5e4998a045d47d98fb8f75c5e9f7ab3fe88\",\"html_url\":\"https://github.com/Imprint-Tech/charts/blob/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd/templates/run-credit-replay-cronjob.yaml\",\"download_url\":\"https://raw.githubusercontent.com/Imprint-Tech/charts/8fd7482f12e95200717f8542ca797b06c85afe20/ledger/chart/prd/templates/run-credit-replay-cronjob.yaml?token=BJXPXPTN3XAQ5ZMVWKBJC7DJMEWD5AA\"}]"
}
]Production has 5 cronjobs:
1. apply-late-payment-fee
2. create-statement-cycle
3. credit-statement-due
4. post-annual-fee
5. run-credit-replay
Now let me look at the job_runner_build.yml to understand how jobs are currently built and see if it can be made into a reusable workflow.
Now I have the complete picture. Production has 5 cronjobs:
1. apply-late-payment-fee
2. create-statement-cycle
3. credit-statement-due
4. post-annual-fee
5. run-credit-replay
Let me check the job_runner_build.yml to understand how to integrate it.
{
"file_path": "/Users/danielroth/dev/go-backend/.github/workflows/job_runner_build.yml"
} 1→name: Build Job runner
2→
3→on:
4→ workflow_dispatch:
5→ inputs:
6→ job_name:
7→ description: "Job Name to build"
8→ required: true
9→ service_name:
10→ description: "Service Name to build"
11→ required: true
12→
13→env:
14→ VALID_SERVICES: ('banking', 'cupid', 'ledger', 'onboard', 'sprinkler', 'swiper', 'teller')
15→ SERVICES_SBX: ('api', 'abacus', 'banking', 'census', 'crm', 'cupid', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'passport', 'rewards', 'sherlock', 'sprinkler', 'swiper', 'teller', 'wallet', 'watchman', 'brands', 'gavel')
16→
17→jobs:
18→ tagging-new-version:
19→ runs-on: arc-runner-imprint
20→ outputs:
21→ new_version: ${{ steps.get_new_version.outputs.new_version }}
22→ pr_num: ${{ steps.get_new_version.outputs.pr_num }}
23→ build_type: ${{ steps.get_new_version.outputs.build_type }}
24→
25→ steps:
26→ - name: Check if valid service
27→ if: ${{ !contains(env.VALID_SERVICES, github.event.inputs.service_name) }}
28→ run: |
29→ echo "Invalid service: '${{ github.event.inputs.service_name }}' was provided. Only the following services are allowed to build new images: ${{ env.VALID_SERVICES }}"
30→ exit 1
31→
32→ - name: Retrieve PR Number
33→ if: ${{ github.event_name != 'pull_request' }}
34→ uses: octokit/request-action@v2.x
35→ id: retrieve_pr
36→ with:
37→ route: GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls
38→ owner: Imprint-Tech
39→ repo: go-backend
40→ commit_sha: ${{ github.sha }}
41→ env:
42→ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43→
44→ - name: Output new version
45→ id: get_new_version
46→ run: |
47→ build_type="master"
48→ pr_num="latest"
49→
50→ branch=$(echo ${GITHUB_REF#refs/heads/})
51→ echo "current branch is $branch"
52→
53→ if [ ${{ steps.retrieve_pr.outputs.data && true }} ]; then
54→ pr_num=${{ steps.retrieve_pr.outputs.data && fromJson(steps.retrieve_pr.outputs.data)[0].number }}
55→ elif [ ${{ github.event_name }} = 'pull_request' ]; then
56→ pr_num=${{ github.event.pull_request.number }}
57→ fi
58→
59→ new_version=''
60→ date_str=$(date '+%Y%m%d.%H%M')
61→ if [ $branch != 'main' ]; then
62→ build_type="branch"
63→ new_version=job.$date_str.pr$pr_num
64→ else
65→ git_hash=$(echo $GITHUB_SHA | head -c 6)
66→ new_version=job.$date_str.$git_hash
67→ fi
68→
69→ echo "New version is $new_version"
70→ echo "new_version=$new_version" >> $GITHUB_OUTPUT
71→ echo "pr_num=$pr_num" >> $GITHUB_OUTPUT
72→ echo "build_type=$build_type" >> $GITHUB_OUTPUT
73→
74→ compute-affected-services-and-jobs:
75→ needs: tagging-new-version
76→ runs-on: arc-runner-imprint
77→ outputs:
78→ jobs_and_services: ${{ steps.compute.outputs.jobs_and_services }}
79→
80→ steps:
81→ - name: Checkout repo
82→ if: ${{ github.event_name != 'workflow_dispatch' }}
83→ uses: actions/checkout@v4
84→
85→ - name: Get changed files
86→ if: ${{ github.event_name != 'workflow_dispatch' }}
87→ id: changed_files
88→ uses: Ana06/get-changed-files@v2.2.0
89→ continue-on-error: true
90→
91→ - name: Set job_name
92→ if: ${{ github.event_name == 'workflow_dispatch' }}
93→ run: |
94→ echo "job_name=${{ github.event.inputs.job_name }}" >> $GITHUB_ENV
95→ echo "service_name=${{ github.event.inputs.service_name }}" >> $GITHUB_ENV
96→
97→ - name: Compute affected jobs and services
98→ id: compute
99→ run: |
100→ if [ ${{ github.event_name }} = 'workflow_dispatch' ]; then
101→ jobs_and_services='["${{ env.job_name }} ${{ env.service_name }}"]'
102→ else
103→ jobs_and_services=$(python3 ./.github/workflows/affected_services.py True True ${{ steps.changed_files.outputs.all }})
104→ fi
105→
106→ if [ $? -ne 0 ]; then
107→ echo "find unique jobs failed, terminating"
108→ exit 1
109→ fi
110→
111→ echo $jobs_and_services
112→ echo "jobs_and_services=$jobs_and_services" >> $GITHUB_OUTPUT
113→
114→ build-job-runner:
115→ needs: [compute-affected-services-and-jobs, tagging-new-version]
116→ if: ${{ needs.compute-affected-services-and-jobs.outputs.jobs_and_services != '[]' && needs.compute-affected-services-and-jobs.outputs.jobs_and_services != '' }}
117→ permissions:
118→ id-token: write
119→ contents: read
120→ strategy:
121→ matrix:
122→ jobs_and_services: "${{fromJSON(needs.compute-affected-services-and-jobs.outputs.jobs_and_services)}}"
123→ env: [stg, prd, sbx] # TODO: Re-enable preprd after V2 is released
124→ # only building images for us-east-1
125→ runs-on: arc-runner-imprint
126→ env:
127→ JOB_MANIFEST_DIR: manifests
128→
129→ steps:
130→ - name: Check if should run
131→ id: should_run
132→ run: |
133→ condition=${{ matrix.env == 'stg' || ((matrix.env == 'prd' || matrix.env == 'sbx' ) && needs.tagging-new-version.outputs.build_type == 'master') }}
134→ echo "condition=$condition" >> $GITHUB_OUTPUT
135→
136→ - name: Checkout repo
137→ if: ${{ steps.should_run.outputs.condition == 'true' }}
138→ uses: actions/checkout@v4
139→
140→ - name: Go Build Cache
141→ if: ${{ steps.should_run.outputs.condition == 'true' }}
142→ uses: actions/cache@v4
143→ with:
144→ path: |
145→ ~/.cache/go-build
146→ key: ${{ runner.os}}-config-gen-go-${{ hashFiles('**/go.sum') }}
147→ restore-keys: |
148→ ${{ runner.os }}-config-gen-go-
149→
150→ - name: generate protobuf files
151→ if: ${{ steps.should_run.outputs.condition == 'true' }}
152→ env:
153→ BUF_TOKEN: ${{ secrets.BUF_TOKEN }}
154→ run: buf generate
155→
156→ - name: Prepare Job runner manifest
157→ id: prepare
158→ if: ${{ steps.should_run.outputs.condition == 'true' }}
159→ run: |
160→ mkdir -p ${{ env.JOB_MANIFEST_DIR }}
161→
162→ job=$(echo ${{ matrix.jobs_and_services }} | cut -f1 -d " ")
163→ service=$(echo ${{ matrix.jobs_and_services }} | cut -f2 -d " ")
164→
165→ job_manifest_path=$(pwd)/${{ env.JOB_MANIFEST_DIR }}
166→
167→ echo "generating $service config at $job_manifest_path"
168→
169→ go run scripts/genconfig/generate_config.go -service $service -env ${{ matrix.env }} -region us-east-1
170→ cp $service/*config.json $service/jobs/$job
171→
172→ # if the job has its own config generator, generate the job config and move it to the job dir
173→ go run scripts/genconfig/generate_config.go -service $job -env ${{ matrix.env }} -region all
174→ cp *config.json $service/jobs/$job 2>/dev/null || true
175→
176→ echo "preparing job $job, manifest dir at $job_manifest_path"
177→
178→ pushd .
179→ cd $service/jobs/$job
180→
181→ chmod 755 ./prepare.sh
182→ ./prepare.sh $job_manifest_path ${{ matrix.env }}
183→
184→ popd
185→
186→ ls -lah ${{ env.JOB_MANIFEST_DIR }}
187→
188→ tag=$(echo ${{ needs.tagging-new-version.outputs.new_version }}.$job | head -c 55 | sed -E 's/[^a-zA-Z0-9]$//')
189→
190→ echo "job=$job" >> $GITHUB_OUTPUT
191→ echo "tag=$tag" >> $GITHUB_OUTPUT
192→ echo "service=$service" >> $GITHUB_OUTPUT
193→
194→ - name: Setup Env
195→ if: ${{ steps.should_run.outputs.condition == 'true' }}
196→ id: aws_env
197→ run: |
198→ role_to_assume=${{ vars.STG_ECR_UPLOAD_ROLE }}
199→ if [ ${{ matrix.env }} = 'prd' ]; then
200→ echo "using production aws role"
201→ role_to_assume=${{ vars.PRD_ECR_UPLOAD_ROLE }}
202→ elif [ ${{ matrix.env }} = 'sbx' ]; then
203→ echo "using sandbox aws role"
204→ role_to_assume=${{ vars.SBX_ECR_UPLOAD_ROLE }}
205→ fi
206→ echo "role_to_assume=$role_to_assume" >> $GITHUB_OUTPUT
207→
208→ - name: Configure AWS credentials
209→ if: ${{ steps.should_run.outputs.condition == 'true' }}
210→ uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
211→ with:
212→ role-to-assume: ${{ steps.aws_env.outputs.role_to_assume }}
213→ aws-region: us-east-1
214→
215→ - name: Login to Amazon ECR
216→ id: login-ecr
217→ if: ${{ steps.should_run.outputs.condition == 'true' }}
218→ uses: aws-actions/amazon-ecr-login@v2
219→
220→ - name: Build and push image to Amazon ECR
221→ id: build_push
222→ if: ${{ steps.should_run.outputs.condition == 'true' && ((matrix.env != 'sbx') || (matrix.env == 'sbx' && contains(env.SERVICES_SBX, steps.prepare.outputs.service))) }}
223→ env:
224→ ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
225→ IMAGE_TAG: "${{ steps.prepare.outputs.tag }}"
226→ working-directory: ${{ env.JOB_MANIFEST_DIR }}
227→ run: |
228→ ECR_REPOSITORY="${{ steps.prepare.outputs.service }}"
229→
230→ docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
231→ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
232→
233→ echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
234→
235→ # Make it very visible in the run UI
236→ echo "::notice title=Image Built::${{ matrix.env }} • $ECR_REPOSITORY • $IMAGE_TAG"
237→
238→ - name: Publish build summary
239→ if: ${{ steps.should_run.outputs.condition == 'true' && ((matrix.env != 'sbx') || (matrix.env == 'sbx' && contains(env.SERVICES_SBX, steps.prepare.outputs.service))) }}
240→ run: |
241→ {
242→ echo "## :whale: Image Built"
243→ echo ""
244→ echo "- Env: \`${{ matrix.env }}\`"
245→ echo "- Service: \`${{ steps.prepare.outputs.service }}\`"
246→ echo "- Job: \`${{ steps.prepare.outputs.job }}\`"
247→ echo "- Tag: \`${{ steps.build_push.outputs.image_tag }}\`"
248→ } >> "$GITHUB_STEP_SUMMARY"
249→
250→ update-opslevel-config:
251→ name: Update image tag version in Opslevel
252→ runs-on: arc-runner-imprint
253→ if: github.ref == 'refs/heads/main'
254→ needs: [build-job-runner, tagging-new-version]
255→ environment: privilege-access
256→ strategy:
257→ fail-fast: false
258→ steps:
259→ - name: Get Helm Chart Auto Update App Token
260→ id: app-token
261→ uses: actions/create-github-app-token@v1
262→ with:
263→ app-id: ${{ vars.HELM_CHART_AUTO_UPDATE_APP_ID }}
264→ private-key: ${{ secrets.HELM_CHART_AUTO_UPDATE_APP_PRIVATE_KEY }}
265→
266→ - name: Get GitHub App User ID
267→ id: get-user-id
268→ run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
269→ env:
270→ GH_TOKEN: ${{ steps.app-token.outputs.token }}
271→
272→ - name: Checkout Repository
273→ uses: actions/checkout@v4
274→ with:
275→ fetch-depth: 0
276→ token: ${{ steps.app-token.outputs.token }}
277→ ref: main
278→ persist-credentials: false
279→
280→ - name: Download All Artifacts
281→ uses: actions/download-artifact@v4
282→ with:
283→ path: artifacts
284→
285→ - name: Update image tag in corresponding opslevel.yml
286→ id: update-values
287→ run: |
288→ update=false
289→
290→ SERVICE="${{ github.event.inputs.service_name }}"
291→ JOB="${{ github.event.inputs.job_name }}"
292→ FILE="$SERVICE/jobs/$JOB/opslevel.yml"
293→
294→ if [[ ! -f "$FILE" ]]; then
295→ echo "No opslevel.yml at $FILE — skipping."
296→ exit 0
297→ fi
298→
299→ sed -E -i "s|^([[:space:]]{4}version:[[:space:]]*).*|\1\"${{ needs.tagging-new-version.outputs.new_version }}\"|" "$FILE"
300→
301→ echo "update=true" >> $GITHUB_OUTPUT
302→
303→ - name: Commit and Push Changes
304→ if: steps.update-values.outputs.update == 'true'
305→ run: |
306→ git config --global user.name "${{ steps.app-token.outputs.app-slug }}[bot]"
307→ git config --global user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
308→ git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}
309→
310→ git add */jobs/*/opslevel.yml
311→ git commit -m "Update job's newest image version in Opslevel"
312→
313→ git pull --rebase origin main
314→ git push origin main || git push --force origin main
315→
316→ comment-pr:
317→ if: ${{ github.event_name == 'pull_request' }}
318→ needs: [build-job-runner, tagging-new-version]
319→ runs-on: arc-runner-imprint
320→
321→ steps:
322→ - name: Comment PR
323→ uses: thollander/actions-comment-pull-request@v1
324→ with:
325→ message: "🟢 Created ${{ needs.tagging-new-version.outputs.build_type }} build: ${{ needs.tagging-new-version.outputs.new_version }}"
326→ pr_number: ${{ needs.tagging-new-version.outputs.pr_num }}
327→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Now I have all the information I need to design the implementation plan. Let me launch a Plan agent to design this properly.
Key observations:
1. push_ecr.yml excludes */jobs/** from triggers - jobs are intentionally built separately
2. job_runner_build.yml is manual dispatch only, takes job_name and service_name as inputs
3. Service build updates charts repo with image_tag for the service
4. Job build currently updates opslevel.yml in go-backend, not the charts repo
5. The 5 prd cronjobs use version variables like apply_late_payment_fee_version in charts values.yaml
The user wants to: 1. Build jobs when ledger service is built 2. Update the charts repo with job versions too (not just service version)
This is very doable. The plan would be:
push_ecr.yml to add a new job that:Uploads artifacts with job image tags
Modify the update-helm-values job to also update job version variables in values.yaml
I have enough information now. Let me design the plan.
{
"subagent_type": "Plan",
"prompt": "Design an implementation plan to build ledger jobs alongside the ledger service.\n\n## Context\n\n**Current State:**\n- `push_ecr.yml` builds services and updates `charts` repo with `image_tag` for the service\n- `job_runner_build.yml` is manual-only, builds job images with timestamp tags like `job.20260106.1407.67fc82`\n- Service build explicitly excludes `*/jobs/**` paths\n- Job build updates `opslevel.yml` in go-backend (not charts repo)\n\n**Target State:**\n- When ledger service is built, also build the 5 tracked cronjobs:\n 1. `apply-late-payment-fee`\n 2. `create-statement-cycle`\n 3. `credit-statement-due`\n 4. `post-annual-fee`\n 5. `run-credit-replay`\n- Update charts repo with both service image_tag AND job versions\n\n**Charts Repo Structure (Imprint-Tech/charts):**\n```yaml\n# ledger/chart/prd/values.yaml\nimprint-service:\n image_tag: 0.4.355.ledger\n\napply_late_payment_fee_version: job.20260106.1407.67fc82\ncreate_statement_cycle_version: job.20260106.1407.67fc82\ncredit_statement_due_version: job.20260106.1407.67fc82\npost_annual_fee_version: job.20260106.1408.67fc82\nrun_credit_replay_version: job.20260106.1408.67fc82\n```\n\n**Job Image Tag Format:** `{version}.{job-name}` (e.g., `job.20260106.1407.67fc82.apply-late-payment-fee`)\n\n**Key Constraints:**\n1. Keep current timestamp format for job tags (per user preference)\n2. Only build the 5 tracked jobs for ledger (not all 37)\n3. Job versions should be stored in the same charts repo\n\n## Requirements\n\n1. **Modify `push_ecr.yml`** to:\n - Detect when ledger is in the affected services\n - Build all 5 cronjobs (reuse logic from job_runner_build.yml)\n - Generate a timestamp-based version for jobs\n\n2. **Modify `update-helm-values` job** to:\n - Also update job version variables in values.yaml\n - Pattern: `{job_name_underscored}_version: {version}.{job-name}`\n\n3. **Consider:**\n - How to handle the matrix expansion (service + jobs)\n - Whether to make job_runner_build.yml a reusable workflow or inline the logic\n - Build caching and parallel execution for efficiency\n - Whether stg-only jobs need different handling\n\nPlease provide a detailed implementation plan with specific file changes needed."
}[
{
"type": "text",
"text": "Now I have a comprehensive understanding of the codebase. Let me now provide a detailed implementation plan.\n\n## Implementation Plan: Build Ledger Jobs Alongside Ledger Service\n\nBased on my exploration of the codebase, here is a detailed implementation plan for building ledger jobs alongside the ledger service in `push_ecr.yml`.\n\n---\n\n## Summary\n\nThe goal is to automatically build the 5 tracked ledger cronjobs whenever the ledger service is built, and update both the service image tag and job version variables in the charts repository.\n\n**Tracked Jobs:**\n1. `apply-late-payment-fee`\n2. `create-statement-cycle`\n3. `credit-statement-due`\n4. `post-annual-fee`\n5. `run-credit-replay`\n\n---\n\n## Implementation Plan\n\n### Phase 1: Add Ledger Job Configuration\n\n**File: `.github/workflows/push_ecr.yml`**\n\nAdd a new environment variable to define the tracked ledger jobs:\n\n```yaml\nenv:\n GO_VERSION: 1.24\n # ... existing env vars ...\n LEDGER_TRACKED_JOBS: ('apply-late-payment-fee', 'create-statement-cycle', 'credit-statement-due', 'post-annual-fee', 'run-credit-replay')\n```\n\n### Phase 2: Add Job Detection Output\n\n**File: `.github/workflows/push_ecr.yml`**\n\nModify the `compute-affected-services` job to output whether ledger is affected:\n\n```yaml\ncompute-affected-services:\n runs-on: arc-runner-imprint\n outputs:\n affected_services: ${{ steps.compute.outputs.affected }}\n ledger_affected: ${{ steps.compute.outputs.ledger_affected }} # NEW\n```\n\nAdd a step to detect ledger:\n\n```yaml\n- name: Check if ledger is affected\n id: check_ledger\n run: |\n affected='${{ steps.compute.outputs.affected }}'\n if echo \"$affected\" | grep -q '\"ledger\"'; then\n echo \"ledger_affected=true\" >> $GITHUB_OUTPUT\n else\n echo \"ledger_affected=false\" >> $GITHUB_OUTPUT\n fi\n```\n\n### Phase 3: Add Job Version Generation\n\n**File: `.github/workflows/push_ecr.yml`**\n\nAdd a new job to generate the job version tag (similar to `job_runner_build.yml`):\n\n```yaml\ngenerate-job-version:\n runs-on: arc-runner-imprint\n needs: [check-label, compute-affected-services]\n if: ${{ needs.compute-affected-services.outputs.ledger_affected == 'true' && (github.event_name == 'push' || needs.check-label.outputs.should_run == 'true') }}\n outputs:\n job_version: ${{ steps.version.outputs.job_version }}\n steps:\n - name: Generate job version\n id: version\n run: |\n date_str=$(date '+%Y%m%d.%H%M')\n git_hash=$(echo $GITHUB_SHA | head -c 6)\n job_version=\"job.$date_str.$git_hash\"\n echo \"job_version=$job_version\" >> $GITHUB_OUTPUT\n echo \"Generated job version: $job_version\"\n```\n\n### Phase 4: Add Job Build Matrix\n\n**File: `.github/workflows/push_ecr.yml`**\n\nAdd a new job to build the ledger cronjobs:\n\n```yaml\nbuild-ledger-jobs:\n name: Build Ledger Jobs\n needs: [tagging-new-version, compute-affected-services, check-label, generate-job-version]\n if: ${{ needs.compute-affected-services.outputs.ledger_affected == 'true' && (github.event_name == 'push' || needs.check-label.outputs.should_run == 'true') }}\n runs-on: arc-runner-imprint\n permissions:\n id-token: write\n contents: read\n strategy:\n fail-fast: false\n matrix:\n job: [apply-late-payment-fee, create-statement-cycle, credit-statement-due, post-annual-fee, run-credit-replay]\n env: [stg, prd, sbx]\n env:\n JOB_MANIFEST_DIR: manifests\n\n steps:\n - name: Check if should run\n id: should_run\n run: |\n # Only build prd/sbx on main branch push, always build stg\n condition=${{ matrix.env == 'stg' || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}\n echo \"condition=$condition\" >> $GITHUB_OUTPUT\n\n - name: Checkout repo\n if: ${{ steps.should_run.outputs.condition == 'true' }}\n uses: actions/checkout@v4\n\n - name: Go Build Cache\n if: ${{ steps.should_run.outputs.condition == 'true' }}\n uses: actions/cache@v4\n with:\n path: |\n ~/.cache/go-build\n key: ${{ runner.os}}-ledger-job-${{ matrix.job }}-${{ hashFiles('**/go.sum') }}\n restore-keys: |\n ${{ runner.os }}-ledger-job-${{ matrix.job }}-\n ${{ runner.os }}-config-gen-ledger-\n\n - name: Generate protobuf files\n if: ${{ steps.should_run.outputs.condition == 'true' }}\n env:\n BUF_TOKEN: ${{ secrets.BUF_TOKEN }}\n run: |\n max_attempts=3\n attempt=1\n while [ $attempt -le $max_attempts ]; do\n echo \"Attempt $attempt of $max_attempts: Running buf generate...\"\n if buf generate --timeout=10s; then\n break\n else\n if [ $attempt -eq $max_attempts ]; then\n exit 1\n fi\n sleep $((2 ** attempt))\n attempt=$((attempt + 1))\n fi\n done\n\n - name: Prepare Job Manifest\n id: prepare\n if: ${{ steps.should_run.outputs.condition == 'true' }}\n run: |\n mkdir -p ${{ env.JOB_MANIFEST_DIR }}\n job=\"${{ matrix.job }}\"\n service=\"ledger\"\n job_manifest_path=$(pwd)/${{ env.JOB_MANIFEST_DIR }}\n\n # Generate service config\n go run scripts/genconfig/generate_config.go -service $service -env ${{ matrix.env }} -region us-east-1\n cp $service/*config.json $service/jobs/$job/\n\n # Generate job-specific config if exists\n go run scripts/genconfig/generate_config.go -service $job -env ${{ matrix.env }} -region all\n cp *config.json $service/jobs/$job 2>/dev/null || true\n\n # Run job's prepare.sh\n pushd .\n cd $service/jobs/$job\n chmod 755 ./prepare.sh\n ./prepare.sh $job_manifest_path ${{ matrix.env }}\n popd\n\n # Generate image tag (truncated to 55 chars for Docker compatibility)\n tag=$(echo \"${{ needs.generate-job-version.outputs.job_version }}.$job\" | head -c 55 | sed -E 's/[^a-zA-Z0-9]$//')\n \n echo \"job=$job\" >> $GITHUB_OUTPUT\n echo \"tag=$tag\" >> $GITHUB_OUTPUT\n\n - name: Setup AWS Env\n if: ${{ steps.should_run.outputs.condition == 'true' }}\n id: aws_env\n run: |\n role_to_assume=${{ vars.STG_ECR_UPLOAD_ROLE }}\n if [ ${{ matrix.env }} = 'prd' ]; then\n role_to_assume=${{ vars.PRD_ECR_UPLOAD_ROLE }}\n elif [ ${{ matrix.env }} = 'sbx' ]; then\n role_to_assume=${{ vars.SBX_ECR_UPLOAD_ROLE }}\n fi\n echo \"role_to_assume=$role_to_assume\" >> $GITHUB_OUTPUT\n\n - name: Configure AWS credentials\n if: ${{ steps.should_run.outputs.condition == 'true' }}\n uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722\n with:\n role-to-assume: ${{ steps.aws_env.outputs.role_to_assume }}\n aws-region: us-east-1\n\n - name: Login to Amazon ECR\n if: ${{ steps.should_run.outputs.condition == 'true' }}\n id: login-ecr\n uses: aws-actions/amazon-ecr-login@v2\n\n - name: Build and push image\n if: ${{ steps.should_run.outputs.condition == 'true' }}\n id: build_push\n env:\n ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}\n IMAGE_TAG: \"${{ steps.prepare.outputs.tag }}\"\n working-directory: ${{ env.JOB_MANIFEST_DIR }}\n run: |\n ECR_REPOSITORY=\"ledger\"\n docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .\n docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG\n echo \"image_tag=$IMAGE_TAG\" >> \"$GITHUB_OUTPUT\"\n\n - name: Upload job artifact\n if: ${{ steps.should_run.outputs.condition == 'true' }}\n uses: actions/upload-artifact@v4\n with:\n name: job_${{ matrix.env }}_${{ matrix.job }}\n path: |\n echo \"${{ steps.prepare.outputs.tag }}\" > job_version.txt\n```\n\n### Phase 5: Modify update-helm-values Job\n\n**File: `.github/workflows/push_ecr.yml`**\n\nModify the `update-helm-values` job to also update job versions:\n\n```yaml\nupdate-helm-values:\n name: Update Helm Values for All Services\n runs-on: arc-runner-imprint\n if: github.event_name == 'push' && github.ref == 'refs/heads/main'\n needs: [upload-images, build-ledger-jobs, generate-job-version] # Add dependencies\n environment: privilege-access\n strategy:\n fail-fast: false\n matrix:\n repo: [charts]\n steps:\n # ... existing steps for app token, checkout, download artifacts ...\n\n - name: Update values.yaml for All Services\n id: update-values\n run: |\n update=false\n \n # Process service artifacts (existing logic)\n for artifact_file in artifacts/*; do\n ARTIFACT_NAME=$(basename \"$artifact_file\")\n \n # Skip job artifacts (they start with \"job_\")\n if [[ \"$ARTIFACT_NAME\" == job_* ]]; then\n continue\n fi\n \n # ... existing service update logic ...\n done\n \n # Process ledger job artifacts (NEW)\n job_version=\"${{ needs.generate-job-version.outputs.job_version }}\"\n if [ -n \"$job_version\" ]; then\n for env in stg prd sbx; do\n VALUES_FILE=\"ledger/chart/$env/values.yaml\"\n if [ -f \"$VALUES_FILE\" ]; then\n # Update each job version variable\n for job in apply-late-payment-fee create-statement-cycle credit-statement-due post-annual-fee run-credit-replay; do\n # Convert job name to underscore format for variable name\n var_name=$(echo \"$job\" | tr '-' '_')_version\n # Create full tag: version.job-name\n full_tag=\"$job_version.$job\"\n \n # Check if variable exists in file\n if grep -q \"^${var_name}:\" \"$VALUES_FILE\"; then\n sed -i \"s|^${var_name}:.*|${var_name}: $full_tag|g\" \"$VALUES_FILE\"\n update=true\n echo \"Updated $var_name to $full_tag in $VALUES_FILE\"\n fi\n done\n fi\n done\n fi\n \n echo \"update=$update\" >> $GITHUB_OUTPUT\n\n # ... existing commit and push steps ...\n```\n\n### Phase 6: Update Commit Message\n\nModify the commit step to include job version updates:\n\n```yaml\n- name: Commit and Push Changes\n if: steps.update-values.outputs.update == 'true'\n run: |\n git config --global user.name \"${{ steps.app-token.outputs.app-slug }}[bot]\"\n git config --global user.email \"${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com\"\n git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository_owner }}/${{ matrix.repo }}\n\n git add */chart/*/values.yaml\n git commit -m \"Update image tags for service Helm charts and ledger jobs\"\n\n git pull --rebase origin main\n git push origin main || git push --force origin main\n```\n\n---\n\n## Key Design Decisions\n\n### 1. Matrix Strategy for Jobs\n- Use a separate matrix for jobs rather than combining with services\n- This allows independent failure handling and clearer logs\n- Jobs only build for `stg`, `prd`, `sbx` (no `preprd` per existing pattern)\n\n### 2. Job Version Format\n- Keep existing timestamp format: `job.YYYYMMDD.HHMM.{git_hash}`\n- Full image tag: `job.YYYYMMDD.HHMM.{git_hash}.{job-name}`\n- Matches existing `job_runner_build.yml` pattern\n\n### 3. Charts Repo Variable Names\n- Use underscore format: `apply_late_payment_fee_version`\n- Value includes full tag with job name suffix\n\n### 4. Conditional Execution\n- Jobs only build when ledger is in affected services\n- Branch builds only create stg images (matching service behavior)\n- Production/sandbox builds only on main branch push\n\n### 5. Build Caching\n- Reuse Go build cache with job-specific keys\n- Fall back to service-level cache for shared dependencies\n\n### 6. Artifact Strategy\n- Upload job artifacts with `job_` prefix to distinguish from service artifacts\n- Download and process separately in helm update step\n\n---\n\n## Alternative Approaches Considered\n\n### Option A: Reusable Workflow (Not Recommended)\n- Extract `job_runner_build.yml` into a reusable workflow\n- Pros: DRY, single source of truth\n- Cons: Complex parameter passing, harder to debug, may require workflow changes\n\n### Option B: Inline Logic (Recommended)\n- Duplicate relevant job build logic into `push_ecr.yml`\n- Pros: Self-contained, easier to maintain, clear dependencies\n- Cons: Some code duplication\n\n### Option C: Composite Action\n- Create a composite action for job building\n- Pros: Reusable across workflows\n- Cons: Additional file to maintain, version management overhead\n\n**Recommendation:** Option B (Inline Logic) is recommended because:\n1. The job build logic is straightforward\n2. Keeps all ledger service build logic in one place\n3. Easier to modify and debug\n4. Avoids workflow versioning complexity\n\n---\n\n## Testing Strategy\n\n1. **Branch Build Test:**\n - Create branch with ledger changes\n - Add `needs branch build` label\n - Verify only stg job images are built\n\n2. **Main Branch Test:**\n - Merge PR with ledger changes\n - Verify all environments get job images\n - Verify charts repo is updated with both service and job versions\n\n3. **Non-Ledger Service Test:**\n - Modify a different service (e.g., `api`)\n - Verify no job builds are triggered\n\n---\n\n## Rollback Plan\n\nIf issues arise:\n1. Revert the `push_ecr.yml` changes\n2. Continue using manual `job_runner_build.yml` workflow\n3. No impact to existing service builds\n\n---\n\n### Critical Files for Implementation\n\n1. **`/Users/danielroth/dev/go-backend/.github/workflows/push_ecr.yml`** - Main workflow file to modify with job build logic and helm update changes\n\n2. **`/Users/danielroth/dev/go-backend/.github/workflows/job_runner_build.yml`** - Reference implementation for job build steps (prepare, build, push)\n\n3. **`/Users/danielroth/dev/go-backend/ledger/jobs/apply-late-payment-fee/prepare.sh`** - Example job prepare script to understand build structure\n\n4. **`/Users/danielroth/dev/go-backend/scripts/genconfig/generate_config.go`** - Config generation used by both service and job builds\n\n5. **Charts repo `ledger/chart/*/values.yaml`** - Target files for helm value updates (external repo, structure shown in requirements)"
},
{
"type": "text",
"text": "agentId: af06abd (for resuming to continue this agent's work if needed)"
}
]I now have a comprehensive plan from the Plan agent. Let me review the critical files and update my plan file with the final implementation details. I should also verify the job prepare.sh script structure to ensure the plan is accurate.
{
"file_path": "/Users/danielroth/dev/go-backend/ledger/jobs/apply-late-payment-fee/prepare.sh"
} 1→#!/usr/bin/env bash
2→
3→set -euo pipefail
4→
5→go build -o apply_late_payment_fee main.go
6→
7→cp Dockerfile $1/Dockerfile
8→cp apply_late_payment_fee $1/apply_late_payment_fee
9→cp $2-config.json $1/config.json
10→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Now I have a good understanding of how the prepare.sh script works. The script:
1. Takes two arguments: $1 = manifest directory, $2 = environment
2. Builds the Go binary
3. Copies Dockerfile, binary, and config to the manifest directory
Now I can finalize my plan. Let me update the plan file with the complete implementation details.
# Plan: Build Ledger Jobs Alongside Service
## Summary
Modify `push_ecr.yml` to automatically build the 5 tracked ledger cronjobs whenever the ledger service is built, and update the charts repo with both service and job image tags.
## Tracked Jobs (from prd chart)
1. `apply-late-payment-fee`
2. `create-statement-cycle`
3. `credit-statement-due`
4. `post-annual-fee`
5. `run-credit-replay`
## Implementation
### File: `.github/workflows/push_ecr.yml`
#### 1. Add Environment Variable for Tracked Jobs
```yaml
env:
GO_VERSION: 1.24
# ... existing vars ...
LEDGER_TRACKED_JOBS: "apply-late-payment-fee,create-statement-cycle,credit-statement-due,post-annual-fee,run-credit-replay"
```
#### 2. Add Job Version Generation Job (after `tagging-new-version`)
```yaml
generate-job-version:
runs-on: arc-runner-imprint
needs: [check-label, compute-affected-services]
if: >-
${{ contains(needs.compute-affected-services.outputs.affected_services, 'ledger') &&
(github.event_name == 'push' || needs.check-label.outputs.should_run == 'true') }}
outputs:
job_version: ${{ steps.version.outputs.job_version }}
steps:
- name: Generate job version
id: version
run: |
date_str=$(date '+%Y%m%d.%H%M')
git_hash=$(echo $GITHUB_SHA | head -c 6)
job_version="job.$date_str.$git_hash"
echo "job_version=$job_version" >> $GITHUB_OUTPUT
```
#### 3. Add Build Ledger Jobs Job (after `upload-images`)
New job that builds all 5 tracked jobs using matrix strategy. Key steps:
- Checkout, Go build cache, buf generate
- Generate service config and job-specific config
- Run each job's `prepare.sh` script
- Build and push Docker image to ECR
- Upload artifact with job version
Matrix: `job × env` (5 jobs × 3 environments = 15 builds max)
#### 4. Modify `update-helm-values` Job
Add `generate-job-version` and `build-ledger-jobs` to `needs`.
Update the "Update values.yaml" step to also process job versions:
```bash
# After existing service artifact processing...
# Process ledger job versions
job_version="${{ needs.generate-job-version.outputs.job_version }}"
if [ -n "$job_version" ]; then
for env in stg prd sbx; do
VALUES_FILE="ledger/chart/$env/values.yaml"
if [ -f "$VALUES_FILE" ]; then
for job in apply-late-payment-fee create-statement-cycle credit-statement-due post-annual-fee run-credit-replay; do
var_name=$(echo "$job" | tr '-' '_')_version
full_tag="$job_version.$job"
if grep -q "^${var_name}:" "$VALUES_FILE"; then
sed -i "s|^${var_name}:.*|${var_name}: $full_tag|g" "$VALUES_FILE"
update=true
fi
done
fi
done
fi
```
### Key Design Decisions
1. **Matrix Strategy**: Separate job from services to allow independent failure handling
2. **Version Format**: Keep timestamp format `job.YYYYMMDD.HHMM.{hash}.{job-name}`
3. **Conditional Execution**: Jobs only build when ledger is affected
4. **Branch Builds**: Only stg images for branch builds, all envs for main
5. **Build Caching**: Reuse Go build cache from service builds
### Build Flow
```
push_ecr.yml (ledger affected)
├── tagging-new-version
├── compute-affected-services (outputs: ledger_affected=true)
├── generate-job-version (outputs: job_version)
├── upload-images (builds ledger service)
├── build-ledger-jobs (matrix: 5 jobs × 3 envs)
└── update-helm-values
├── Update service image_tag
└── Update job versions (5 vars per env)
```
### Charts Repo Updates
After merge to main, `ledger/chart/{env}/values.yaml` will have:
```yaml
imprint-service:
image_tag: 0.4.359.ledger # Updated by service build
apply_late_payment_fee_version: job.20260109.1500.abc123.apply-late-payment-fee
create_statement_cycle_version: job.20260109.1500.abc123.create-statement-cycle
credit_statement_due_version: job.20260109.1500.abc123.credit-statement-due
post_annual_fee_version: job.20260109.1500.abc123.post-annual-fee
run_credit_replay_version: job.20260109.1500.abc123.run-credit-replay
```
## Files to Modify
| File | Changes |
|------|---------|
| `.github/workflows/push_ecr.yml` | Add env var, job version job, build-ledger-jobs job, modify update-helm-values |
## Verification
1. **Branch Build Test**: Create PR with ledger changes, add "needs branch build" label
- Verify only stg job images built
- Verify no chart updates (branch builds don't update charts)
2. **Main Branch Test**: Merge PR with ledger changes
- Verify all 5 jobs built for stg, prd, sbx
- Verify charts repo updated with both service image_tag and all 5 job versions
- Check ArgoCD shows new versions pending
3. **Non-Ledger Test**: Modify a different service
- Verify no job builds triggered
- Verify charts repo only updates service image_tag
The file /Users/danielroth/.claude/plans/mellow-shimmying-kay.md has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
1→# Plan: Build Ledger Jobs Alongside Service
2→
3→## Summary
4→
5→Modify `push_ecr.yml` to automatically build the 5 tracked ledger cronjobs whenever the ledger service is built, and update the charts repo with both service and job image tags.
6→
7→## Tracked Jobs (from prd chart)
8→
9→1. `apply-late-payment-fee`
10→2. `create-statement-cycle`
11→3. `credit-statement-due`
12→4. `post-annual-fee`
13→5. `run-credit-replay`
14→
15→## Implementation
16→
17→### File: `.github/workflows/push_ecr.yml`
18→
19→#### 1. Add Environment Variable for Tracked Jobs
20→
21→```yaml
22→env:
23→ GO_VERSION: 1.24
24→ # ... existing vars ...
25→ LEDGER_TRACKED_JOBS: "apply-late-payment-fee,create-statement-cycle,credit-statement-due,post-annual-fee,run-credit-replay"
26→```
27→
28→#### 2. Add Job Version Generation Job (after `tagging-new-version`)
29→
30→```yaml
31→generate-job-version:
32→ runs-on: arc-runner-imprint
33→ needs: [check-label, compute-affected-services]
34→ if: >-
35→ ${{ contains(needs.compute-affected-services.outputs.affected_services, 'ledger') &&
36→ (github.event_name == 'push' || needs.check-label.outputs.should_run == 'true') }}
37→ outputs:
38→ job_version: ${{ steps.version.outputs.job_version }}
39→ steps:
40→ - name: Generate job version
41→ id: version
42→ run: |
43→ date_str=$(date '+%Y%m%d.%H%M')
44→ git_hash=$(echo $GITHUB_SHA | head -c 6)
45→ job_version="job.$date_str.$git_hash"
46→ echo "job_version=$job_version" >> $GITHUB_OUTPUT
47→```
48→
49→#### 3. Add Build Ledger Jobs Job (after `upload-images`)
50→
51→New job that builds all 5 tracked jobs using matrix strategy. Key steps:
52→- Checkout, Go build cache, buf generate
53→- Generate service config and job-specific config
54→- Run each job's `prepare.sh` script
55→- Build and push Docker image to ECR
56→- Upload artifact with job version
57→
58→Matrix: `job × env` (5 jobs × 3 environments = 15 builds max)
59→
60→#### 4. Modify `update-helm-values` Job
61→
62→Add `generate-job-version` and `build-ledger-jobs` to `needs`.
63→
64→Update the "Update values.yaml" step to also process job versions:
65→
66→```bash
67→# After existing service artifact processing...
68→
69→# Process ledger job versions
70→job_version="${{ needs.generate-job-version.outputs.job_version }}"
71→if [ -n "$job_version" ]; then
72→ for env in stg prd sbx; do
73→ VALUES_FILE="ledger/chart/$env/values.yaml"
74→ if [ -f "$VALUES_FILE" ]; then
75→ for job in apply-late-payment-fee create-statement-cycle credit-statement-due post-annual-fee run-credit-replay; do
76→ var_name=$(echo "$job" | tr '-' '_')_version
77→ full_tag="$job_version.$job"
78→ if grep -q "^${var_name}:" "$VALUES_FILE"; then
79→ sed -i "s|^${var_name}:.*|${var_name}: $full_tag|g" "$VALUES_FILE"
80→ update=true
81→ fi
82→ done
83→ fi
84→ done
85→fi
86→```
87→
88→### Key Design Decisions
89→
90→1. **Matrix Strategy**: Separate job from services to allow independent failure handling
91→2. **Version Format**: Keep timestamp format `job.YYYYMMDD.HHMM.{hash}.{job-name}`
92→3. **Conditional Execution**: Jobs only build when ledger is affected
93→4. **Branch Builds**: Only stg images for branch builds, all envs for main
94→5. **Build Caching**: Reuse Go build cache from service builds
95→
96→### Build Flow
97→
98→```
99→push_ecr.yml (ledger affected)
100→├── tagging-new-version
101→├── compute-affected-services (outputs: ledger_affected=true)
102→├── generate-job-version (outputs: job_version)
103→├── upload-images (builds ledger service)
104→├── build-ledger-jobs (matrix: 5 jobs × 3 envs)
105→└── update-helm-values
106→ ├── Update service image_tag
107→ └── Update job versions (5 vars per env)
108→```
109→
110→### Charts Repo Updates
111→
112→After merge to main, `ledger/chart/{env}/values.yaml` will have:
113→
114→```yaml
115→imprint-service:
116→ image_tag: 0.4.359.ledger # Updated by service build
117→
118→apply_late_payment_fee_version: job.20260109.1500.abc123.apply-late-payment-fee
119→create_statement_cycle_version: job.20260109.1500.abc123.create-statement-cycle
120→credit_statement_due_version: job.20260109.1500.abc123.credit-statement-due
121→post_annual_fee_version: job.20260109.1500.abc123.post-annual-fee
122→run_credit_replay_version: job.20260109.1500.abc123.run-credit-replay
123→```
124→
125→## Files to Modify
126→
127→| File | Changes |
128→|------|---------|
129→| `.github/workflows/push_ecr.yml` | Add env var, job version job, build-ledger-jobs job, modify update-helm-values |
130→
131→## Verification
132→
133→1. **Branch Build Test**: Create PR with ledger changes, add "needs branch build" label
134→ - Verify only stg job images built
135→ - Verify no chart updates (branch builds don't update charts)
136→
137→2. **Main Branch Test**: Merge PR with ledger changes
138→ - Verify all 5 jobs built for stg, prd, sbx
139→ - Verify charts repo updated with both service image_tag and all 5 job versions
140→ - Check ArgoCD shows new versions pending
141→
142→3. **Non-Ledger Test**: Modify a different service
143→ - Verify no job builds triggered
144→ - Verify charts repo only updates service image_tag
145→This session is being continued from a previous conversation that ran out of context. The conversation is summarized below: Analysis: Let me analyze the conversation chronologically:
Initial Request: The user asked about building ledger jobs/cronjobs at the same time as the service. They want to modify the service build action (push_ecr.yml) to call the job builder and update helm charts with both service and job/cronjob images.
Phase 1 - Exploration: I launched 3 Explore agents in parallel to investigate:
.github/workflows/ledger/jobs/Helm charts for ledger
Key Findings from Exploration:
push_ecr.yml builds services, excludes */jobs/** pathsjob_runner_build.yml is manual-only, builds job images with timestamp tagsJobs share the service's ECR repo (ledger)
User Clarification Questions:
Asked about helm values location
User Answers:
charts repo""the most up-to-date jobs/cronjobs representation is on the prd chart"
Charts Repo Investigation:
Imprint-Tech/charts repoapply_late_payment_fee_version: job.20260106.1407.67fc82Image format: ledger:{version}.{job-name}
Production Tracked Jobs (5 total):
run-credit-replay
Phase 2 - Design: Launched Plan agent with comprehensive context to design implementation
Phase 4 - Final Plan: Created detailed plan file at /Users/danielroth/.claude/plans/mellow-shimmying-kay.md
Files examined:
- .github/workflows/push_ecr.yml - Main service build workflow (499 lines)
- .github/workflows/job_runner_build.yml - Job build workflow (327 lines)
- ledger/jobs/apply-late-payment-fee/prepare.sh - Job build script
- Charts repo: ledger/chart/prd/values.yaml and templates
No errors encountered during exploration.
User messages (non-tool results): 1. Initial request about building jobs with service 2. Clarification that not all jobs are relevant, check charts repo 3. "the most up-to-date jobs/cronjobs representation is on the prd chart"
Current status: Plan mode complete, ready to exit plan mode for approval.
Summary:
1. Primary Request and Intent:
The user wants to modify the ledger service build pipeline so that jobs/cronjobs are built and deployed at the same time as the service. Currently there are separate build actions for services (push_ecr.yml) and jobs (job_runner_build.yml). The goal is to:
- Have the service build action call the job builder when ledger is being built
- Update the helm chart with both the service image and job/cronjob images
- Store job image tags in the same charts repo as the service
job.YYYYMMDD.HHMM.{git_hash}.{job-name}{version}.{service} (e.g., 0.4.355.ledger)ledger)Job prepare.sh scripts for building job binaries
Files and Code Sections:
.github/workflows/push_ecr.yml (Main file to modify)
*/jobs/** paths currentlyImprint-Tech/charts repoimage_tag.github/workflows/job_runner_build.yml
job.{date}.{time}.{hash}opslevel.yml (not charts repo)ledger/jobs/apply-late-payment-fee/prepare.sh
bash
#!/usr/bin/env bash
set -euo pipefail
go build -o apply_late_payment_fee main.go
cp Dockerfile $1/Dockerfile
cp apply_late_payment_fee $1/apply_late_payment_fee
cp $2-config.json $1/config.json
Imprint-Tech/charts/ledger/chart/prd/values.yaml
apply_late_payment_fee_version: job.20260106.1407.67fc82 create_statement_cycle_version: job.20260106.1407.67fc82 credit_statement_due_version: job.20260106.1407.67fc82 post_annual_fee_version: job.20260106.1408.67fc82 run_credit_replay_version: job.20260106.1408.67fc82 ```
ledger/chart/stg/templates/apply-late-payment-fee-cronjob.yaml
ledger:{{ .Values.apply_late_payment_fee_version }}.apply-late-payment-feeErrors and fixes:
GitHub search error: Initial search for charts org:imprint-x failed with 422 validation error
git remote -v to find correct org name (Imprint-Tech)charts org:Imprint-TechProblem Solving:
Designed inline job build logic rather than making job_runner_build.yml a reusable workflow for simplicity
All user messages:
charts repo in our github organization to see the ledger chart and which jobs/cronjobs are tracked" (response to question about which jobs to build)"the most up-to-date jobs/cronjobs representation is on the prd chart"
Pending Tasks:
Implement the plan by modifying .github/workflows/push_ecr.yml
Current Work:
Completed the planning phase. Created final plan at /Users/danielroth/.claude/plans/mellow-shimmying-kay.md with:
LEDGER_TRACKED_JOBS env vargenerate-job-version jobbuild-ledger-jobs job with matrix strategy (5 jobs × 3 envs)Modify update-helm-values to also update job version variables in charts repo
Optional Next Step:
Call ExitPlanMode to indicate planning is complete and request user approval before implementation.
The plan is complete and ready for review. Per plan mode instructions: "At the very end of your turn, once you have asked the user questions and are happy with your final plan file - you should always call ExitPlanMode to indicate to the user that you are done planning.".
If you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: /Users/danielroth/.claude/projects/-Users-danielroth-dev-go-backend/bb5e45d0-af8f-410d-81b1-71cd3c888b93.jsonl Please continue the conversation from where we left it off without asking the user any further questions. Continue with the last task that you were asked to work on.
The user wants me to continue from where we left off. Based on the summary, I was in plan mode and had completed the plan for building ledger jobs alongside the service. The plan is saved at /Users/danielroth/.claude/plans/mellow-shimmying-kay.md.
The summary says my next step should be to call ExitPlanMode to get user approval, but then the user says "continue the conversation from where we left it off without asking the user any further questions. Continue with the last task that you were asked to work on."
This suggests I should continue implementing the plan rather than waiting for approval. Let me read the plan file and the current push_ecr.yml to implement the changes.
Actually, looking at the git status, I'm on branch fix-pr-lint-with-build-cache which seems to be a different branch than the one for this task. The recent commits show things about "build cache for the compile step of linting" which is different from the ledger jobs task.
Let me read the plan file and the push_ecr.yml to implement the changes.
I'll continue implementing the plan to build ledger jobs alongside the service. Let me first read the current state of the files I need to modify.
1→# Plan: Build Ledger Jobs Alongside Service
2→
3→## Summary
4→
5→Modify `push_ecr.yml` to automatically build the 5 tracked ledger cronjobs whenever the ledger service is built, and update the charts repo with both service and job image tags.
6→
7→## Tracked Jobs (from prd chart)
8→
9→1. `apply-late-payment-fee`
10→2. `create-statement-cycle`
11→3. `credit-statement-due`
12→4. `post-annual-fee`
13→5. `run-credit-replay`
14→
15→## Implementation
16→
17→### File: `.github/workflows/push_ecr.yml`
18→
19→#### 1. Add Environment Variable for Tracked Jobs
20→
21→```yaml
22→env:
23→ GO_VERSION: 1.24
24→ # ... existing vars ...
25→ LEDGER_TRACKED_JOBS: "apply-late-payment-fee,create-statement-cycle,credit-statement-due,post-annual-fee,run-credit-replay"
26→```
27→
28→#### 2. Add Job Version Generation Job (after `tagging-new-version`)
29→
30→```yaml
31→generate-job-version:
32→ runs-on: arc-runner-imprint
33→ needs: [check-label, compute-affected-services]
34→ if: >-
35→ ${{ contains(needs.compute-affected-services.outputs.affected_services, 'ledger') &&
36→ (github.event_name == 'push' || needs.check-label.outputs.should_run == 'true') }}
37→ outputs:
38→ job_version: ${{ steps.version.outputs.job_version }}
39→ steps:
40→ - name: Generate job version
41→ id: version
42→ run: |
43→ date_str=$(date '+%Y%m%d.%H%M')
44→ git_hash=$(echo $GITHUB_SHA | head -c 6)
45→ job_version="job.$date_str.$git_hash"
46→ echo "job_version=$job_version" >> $GITHUB_OUTPUT
47→```
48→
49→#### 3. Add Build Ledger Jobs Job (after `upload-images`)
50→
51→New job that builds all 5 tracked jobs using matrix strategy. Key steps:
52→- Checkout, Go build cache, buf generate
53→- Generate service config and job-specific config
54→- Run each job's `prepare.sh` script
55→- Build and push Docker image to ECR
56→- Upload artifact with job version
57→
58→Matrix: `job × env` (5 jobs × 3 environments = 15 builds max)
59→
60→#### 4. Modify `update-helm-values` Job
61→
62→Add `generate-job-version` and `build-ledger-jobs` to `needs`.
63→
64→Update the "Update values.yaml" step to also process job versions:
65→
66→```bash
67→# After existing service artifact processing...
68→
69→# Process ledger job versions
70→job_version="${{ needs.generate-job-version.outputs.job_version }}"
71→if [ -n "$job_version" ]; then
72→ for env in stg prd sbx; do
73→ VALUES_FILE="ledger/chart/$env/values.yaml"
74→ if [ -f "$VALUES_FILE" ]; then
75→ for job in apply-late-payment-fee create-statement-cycle credit-statement-due post-annual-fee run-credit-replay; do
76→ var_name=$(echo "$job" | tr '-' '_')_version
77→ full_tag="$job_version.$job"
78→ if grep -q "^${var_name}:" "$VALUES_FILE"; then
79→ sed -i "s|^${var_name}:.*|${var_name}: $full_tag|g" "$VALUES_FILE"
80→ update=true
81→ fi
82→ done
83→ fi
84→ done
85→fi
86→```
87→
88→### Key Design Decisions
89→
90→1. **Matrix Strategy**: Separate job from services to allow independent failure handling
91→2. **Version Format**: Keep timestamp format `job.YYYYMMDD.HHMM.{hash}.{job-name}`
92→3. **Conditional Execution**: Jobs only build when ledger is affected
93→4. **Branch Builds**: Only stg images for branch builds, all envs for main
94→5. **Build Caching**: Reuse Go build cache from service builds
95→
96→### Build Flow
97→
98→```
99→push_ecr.yml (ledger affected)
100→├── tagging-new-version
101→├── compute-affected-services (outputs: ledger_affected=true)
102→├── generate-job-version (outputs: job_version)
103→├── upload-images (builds ledger service)
104→├── build-ledger-jobs (matrix: 5 jobs × 3 envs)
105→└── update-helm-values
106→ ├── Update service image_tag
107→ └── Update job versions (5 vars per env)
108→```
109→
110→### Charts Repo Updates
111→
112→After merge to main, `ledger/chart/{env}/values.yaml` will have:
113→
114→```yaml
115→imprint-service:
116→ image_tag: 0.4.359.ledger # Updated by service build
117→
118→apply_late_payment_fee_version: job.20260109.1500.abc123.apply-late-payment-fee
119→create_statement_cycle_version: job.20260109.1500.abc123.create-statement-cycle
120→credit_statement_due_version: job.20260109.1500.abc123.credit-statement-due
121→post_annual_fee_version: job.20260109.1500.abc123.post-annual-fee
122→run_credit_replay_version: job.20260109.1500.abc123.run-credit-replay
123→```
124→
125→## Files to Modify
126→
127→| File | Changes |
128→|------|---------|
129→| `.github/workflows/push_ecr.yml` | Add env var, job version job, build-ledger-jobs job, modify update-helm-values |
130→
131→## Verification
132→
133→1. **Branch Build Test**: Create PR with ledger changes, add "needs branch build" label
134→ - Verify only stg job images built
135→ - Verify no chart updates (branch builds don't update charts)
136→
137→2. **Main Branch Test**: Merge PR with ledger changes
138→ - Verify all 5 jobs built for stg, prd, sbx
139→ - Verify charts repo updated with both service image_tag and all 5 job versions
140→ - Check ArgoCD shows new versions pending
141→
142→3. **Non-Ledger Test**: Modify a different service
143→ - Verify no job builds triggered
144→ - Verify charts repo only updates service image_tag
145→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
1→name: Push to AWS ECR
2→
3→on:
4→ pull_request:
5→ types:
6→ - labeled
7→ paths-ignore:
8→ - "scripts/**"
9→ - "swagger/**"
10→ - "*/jobs/**"
11→ - "*/chart/**"
12→ push:
13→ branches:
14→ - main
15→ - heb-release
16→ paths-ignore:
17→ - ".github/**"
18→ - "scripts/**"
19→ - "swagger/**"
20→ - "*/jobs/**"
21→ - "*/chart/**"
22→ workflow_dispatch: {}
23→
24→env:
25→ GO_VERSION: 1.24
26→ SERVICES_WEST: ('api', 'abacus', 'banking', 'census', 'imprintx', 'ledger', 'messenger', 'onboard', 'passport', 'rewards', 'sherlock', 'swiper', 'teller', 'watchman', 'bridge')
27→ SERVICES_PREPRD: ('abacus', 'api', 'banking', 'billing', 'brands', 'census', 'crm', 'cupid', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'partnermetadata', 'passport', 'referrals', 'rewards', 'scribe', 'sherlock', 'sprinkler', 'swiper', 'teller', 'wallet', 'watchman')
28→ SERVICES_SBX: ('abacus', 'api', 'avatar', 'banking', 'brands', 'billing', 'census', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'passport', 'rewards', 'scribe', 'sprinkler', 'swiper', 'teller', 'wallet', 'partnermetadata', 'referrals', 'bridge', 'crm')
29→
30→jobs:
31→ check-label:
32→ runs-on: arc-runner-imprint
33→ outputs:
34→ should_run: ${{ steps.check_label.outputs.should_run }}
35→ steps:
36→ - name: Check label
37→ id: check_label
38→ if: ${{ github.event_name == 'pull_request' }}
39→ run: |
40→ if [[ ${{ github.event_name }} == 'pull_request' ]] && [[ ${{ github.event.action }} == 'labeled' ]] && [[ "${{ github.event.label.name }}" == 'needs branch build' ]]; then
41→ echo "Starting branch build"
42→ echo "should_run=true" >> $GITHUB_OUTPUT
43→ else
44→ echo "should_run=false" >> $GITHUB_OUTPUT
45→ fi
46→
47→ - name: Remove label
48→ id: remove
49→ if: ${{ steps.check_label.outputs.should_run == 'true' }}
50→ uses: actions-ecosystem/action-remove-labels@v1
51→ with:
52→ repo: ${{ github.repository }}
53→ labels: |
54→ needs branch build
55→
56→ tagging-new-version:
57→ runs-on: arc-runner-imprint
58→ needs: [check-label]
59→ if: ${{ github.event_name == 'push' || needs.check-label.outputs.should_run == 'true' }}
60→ concurrency:
61→ group: tagging-new-version-${{ github.ref }}
62→ cancel-in-progress: false
63→ outputs:
64→ new_version: ${{ steps.get_new_version.outputs.new_version }}
65→ pr_num: ${{ steps.get_new_version.outputs.pr_num }}
66→
67→ steps:
68→ - name: Retrieve PR Number
69→ if: ${{ github.event_name == 'push' }}
70→ uses: octokit/request-action@v2.x
71→ id: retrieve_pr
72→ with:
73→ route: GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls
74→ owner: Imprint-Tech
75→ repo: go-backend
76→ commit_sha: ${{ github.sha }}
77→ env:
78→ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
79→
80→ - name: Checkout
81→ if: ${{ github.event_name == 'push' }}
82→ uses: actions/checkout@v4
83→
84→ - name: Bump version and push tag
85→ if: ${{ github.event_name == 'push' }}
86→ id: tag_version
87→ uses: mathieudutour/github-tag-action@v5.4
88→ with:
89→ github_token: ${{ secrets.GITHUB_TOKEN }}
90→ dry_run: false
91→
92→ - name: Output new version
93→ id: get_new_version
94→ run: |
95→ if [ ${{ github.event_name }} = 'push' ]; then
96→ pr_num=${{ steps.retrieve_pr.outputs.data && fromJson(steps.retrieve_pr.outputs.data)[0].number }}
97→ echo "new_version=${{ steps.tag_version.outputs.new_version }}" >> $GITHUB_OUTPUT
98→ else
99→ pr_num=${{ github.event.pull_request.number }}
100→ echo "new_version=brc.svc.$pr_num.$(echo $GITHUB_SHA | head -c 6)" >> $GITHUB_OUTPUT
101→ fi
102→
103→ echo "pr_num=$pr_num" >> $GITHUB_OUTPUT
104→
105→ compute-affected-services:
106→ runs-on: arc-runner-imprint
107→ outputs:
108→ affected_services: ${{ steps.compute.outputs.affected }}
109→
110→ steps:
111→ - name: Checkout repo
112→ uses: actions/checkout@v4
113→
114→ - name: Get changed files
115→ id: changed_files
116→ if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
117→ uses: Ana06/get-changed-files@v2.2.0
118→ continue-on-error: true
119→
120→ - name: Retrieve PR
121→ if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
122→ uses: octokit/request-action@v2.x
123→ id: retrieve_pr
124→ with:
125→ route: GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls
126→ owner: Imprint-Tech
127→ repo: go-backend
128→ commit_sha: ${{ github.sha }}
129→ env:
130→ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
131→
132→ - name: Compute affected services
133→ id: compute
134→ run: |
135→ if [[ ${{ github.event_name }} == 'push' ]] || [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then
136→ labels='${{ steps.retrieve_pr.outputs.data && toJSON(fromJSON(steps.retrieve_pr.outputs.data)[0].labels) }}'
137→ else
138→ labels='${{ toJSON(github.event.pull_request.labels) }}'
139→ fi
140→ affected=$(python3 ./.github/workflows/parse_service_labels.py "$labels")
141→
142→ echo "$affected"
143→ echo "affected=$affected" >> $GITHUB_OUTPUT
144→
145→ upload-affected-services:
146→ needs: compute-affected-services
147→ runs-on: arc-runner-imprint
148→ if: ${{ github.event_name == 'push' }}
149→ permissions:
150→ id-token: write
151→ contents: read
152→ strategy:
153→ fail-fast: false
154→ matrix:
155→ env: [stg, sbx, preprd, prd]
156→
157→ steps:
158→ - name: Setup AWS Env
159→ id: aws_env
160→ run: |
161→ role_to_assume=${{ vars.STG_S3_UPLOAD_ROLE }}
162→ if [ ${{ matrix.env }} = 'prd' ]; then
163→ echo "using production aws role"
164→ role_to_assume=${{ vars.PRD_S3_UPLOAD_ROLE }}
165→ elif [ ${{ matrix.env }} = 'sbx' ]; then
166→ echo "using sandbox aws role"
167→ role_to_assume=${{ vars.SBX_S3_UPLOAD_ROLE }}
168→ elif [ ${{ matrix.env }} = 'preprd' ]; then
169→ echo "using pre-production aws role"
170→ role_to_assume=${{ vars.PREPRD_S3_UPLOAD_ROLE }}
171→ fi
172→
173→ echo "role_to_assume=$role_to_assume" >> $GITHUB_OUTPUT
174→
175→ - name: Configure AWS credentials
176→ uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
177→ with:
178→ role-to-assume: ${{ steps.aws_env.outputs.role_to_assume }}
179→ aws-region: us-east-1
180→
181→ - name: Upload commits
182→ run: |
183→ short_hash=${{ github.event.after }}
184→ short_hash=${short_hash:0:9}
185→ bucket="${{ matrix.env }}-git-commits-affected-services"
186→ if [ ${{ matrix.env }} = 'sbx' ]; then
187→ bucket="${bucket}-v2"
188→ fi
189→ echo "${{ needs.compute-affected-services.outputs.affected_services }}" | aws s3 cp - s3://${bucket}/${short_hash}.txt --sse AES256
190→
191→ upload-images:
192→ needs: [tagging-new-version, compute-affected-services, check-label]
193→ runs-on: arc-runner-imprint
194→ if: ${{ needs.compute-affected-services.outputs.affected_services != '[]' && needs.compute-affected-services.outputs.affected_services != '' }}
195→ permissions:
196→ id-token: write
197→ contents: read
198→ continue-on-error: ${{ github.event_name == 'workflow_dispatch' }}
199→ strategy:
200→ fail-fast: false
201→ matrix:
202→ service: ${{fromJSON(needs.compute-affected-services.outputs.affected_services)}}
203→ env: [stg, preprd, prd, sbx]
204→ region: [us-east-1]
205→ exclude:
206→ - region: us-west-2
207→ env: prd
208→ - service: avatar
209→ env: prd
210→
211→ steps:
212→ - name: Check if should run
213→ id: should_run
214→ run: |
215→ condition=${{
216→ (github.event_name == 'push' || (needs.check-label.outputs.should_run == 'true' && matrix.env == 'stg')) &&
217→ (matrix.region == 'us-east-1' || contains(env.SERVICES_WEST, matrix.service)) &&
218→ ((matrix.env != 'preprd' && matrix.env != 'sbx') ||
219→ (matrix.env == 'preprd' && contains(env.SERVICES_PREPRD, matrix.service)) ||
220→ (matrix.env == 'sbx' && contains(env.SERVICES_SBX, matrix.service)))
221→ }}
222→ echo $condition
223→ echo "condition=$condition" >> $GITHUB_OUTPUT
224→
225→ - name: Checkout code
226→ if: ${{ steps.should_run.outputs.condition == 'true' }}
227→ uses: actions/checkout@v4
228→
229→ - name: Go Build Cache
230→ if: ${{ steps.should_run.outputs.condition == 'true' }}
231→ uses: actions/cache@v4
232→ with:
233→ path: |
234→ ~/.cache/go-build
235→ key: ${{ runner.os}}-config-gen-${{ matrix.service }}-${{ hashFiles('**/go.sum') }}
236→ restore-keys: |
237→ ${{ runner.os }}-config-gen-${{ matrix.service }}-
238→
239→ - name: Verbolizing
240→ if: ${{ steps.should_run.outputs.condition == 'true' }}
241→ run: |
242→ echo "New Version:" ${{ needs.tagging-new-version.outputs.new_version }}
243→ echo "affected builds: ${{ needs.compute-affected-services.outputs.affected_services }}"
244→ echo "affected builds in JSON: ${{fromJSON(needs.compute-affected-services.outputs.affected_services)}}"
245→
246→ go version
247→
248→ - name: generate protobuf files
249→ if: ${{ steps.should_run.outputs.condition == 'true' }}
250→ env:
251→ BUF_TOKEN: ${{ secrets.BUF_TOKEN }}
252→ run: |
253→ # Retry buf generate command up to 3 times with exponential backoff
254→ max_attempts=3
255→ attempt=1
256→ while [ $attempt -le $max_attempts ]; do
257→ echo "Attempt $attempt of $max_attempts: Running buf generate..."
258→ if buf generate --timeout=10s; then
259→ echo "buf generate succeeded on attempt $attempt"
260→ break
261→ else
262→ exit_code=$?
263→ echo "buf generate failed on attempt $attempt with exit code $exit_code"
264→ if [ $attempt -eq $max_attempts ]; then
265→ echo "All $max_attempts attempts failed. Exiting with failure."
266→ exit $exit_code
267→ else
268→ # Exponential backoff: wait 2^attempt seconds
269→ wait_time=$((2 ** attempt))
270→ echo "Waiting $wait_time seconds before retry..."
271→ sleep $wait_time
272→ attempt=$((attempt + 1))
273→ fi
274→ fi
275→ done
276→
277→ - name: Generate config
278→ if: ${{ steps.should_run.outputs.condition == 'true' }}
279→ run: |
280→ go run scripts/genconfig/generate_config.go -service=${{ matrix.service }} -env=${{ matrix.env }} -region=${{ matrix.region }}
281→
282→ - name: Build individual service ${{ matrix.service }}
283→ if: ${{ steps.should_run.outputs.condition == 'true' }}
284→ working-directory: ${{ matrix.service }}
285→ run: |
286→ make build
287→
288→ - name: Setup Env
289→ if: ${{ steps.should_run.outputs.condition == 'true' }}
290→ id: setup_env
291→ run: |
292→ role_to_assume=${{ vars.STG_ECR_UPLOAD_ROLE }}
293→ docker_file_name="Dockerfile"
294→ if [ "${{ matrix.env }}" = 'preprd' ] || [ "${{ matrix.env }}" = 'sbx' ] || [ "${{ matrix.env }}" = 'prd' ] ; then
295→ docker_file_name="Dockerfile-${{ matrix.env }}"
296→ fi
297→
298→ if [ ${{ matrix.env }} = 'preprd' ]; then
299→ echo "using pre-production aws role"
300→ role_to_assume=${{ vars.PREPRD_ECR_UPLOAD_ROLE }}
301→ elif [ ${{ matrix.env }} = 'sbx' ]; then
302→ echo "using sandbox aws role"
303→ role_to_assume=${{ vars.SBX_ECR_UPLOAD_ROLE }}
304→ elif [ ${{ matrix.env }} = 'prd' ]; then
305→ echo "using production aws role"
306→ role_to_assume=${{ vars.PRD_ECR_UPLOAD_ROLE }}
307→ fi
308→
309→ tag_prefix="branch_build."
310→ if [ ${{ github.event_name }} = 'push' ]; then
311→ tag_prefix=""
312→ fi
313→
314→ echo "role_to_assume=$role_to_assume" >> $GITHUB_OUTPUT
315→ echo "docker_file_name=$docker_file_name" >> $GITHUB_OUTPUT
316→ echo "tag_prefix=$tag_prefix" >> $GITHUB_OUTPUT
317→
318→ - name: Configure AWS credentials
319→ if: ${{ steps.should_run.outputs.condition == 'true' }}
320→ uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
321→ with:
322→ role-to-assume: ${{ steps.setup_env.outputs.role_to_assume }}
323→ aws-region: us-east-1
324→
325→ - name: Login to Amazon ECR
326→ if: ${{ steps.should_run.outputs.condition == 'true' }}
327→ id: login_ecr
328→ uses: aws-actions/amazon-ecr-login@v2
329→
330→ - name: Build, tag, and push image to Amazon ECR for ${{ matrix.service }}
331→ if: ${{ steps.should_run.outputs.condition == 'true' }}
332→ id: build_docker
333→ env:
334→ ECR_REGISTRY: ${{ steps.login_ecr.outputs.registry }}
335→ working-directory: ${{ matrix.service }}
336→ run: |
337→ image_tag="${{ needs.tagging-new-version.outputs.new_version }}.${{ matrix.service }}"
338→ if [ ${{ matrix.region }} = 'us-west-2' ]; then
339→ image_tag="${{ needs.tagging-new-version.outputs.new_version }}.${{ matrix.service }}.${{ matrix.region }}"
340→ fi
341→
342→ ECR_REPOSITORY="${{ matrix.service }}"
343→ echo "Using ECR Repository: $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag"
344→
345→ cp -r ../.git ./.git
346→ docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag . -f ${{ steps.setup_env.outputs.docker_file_name }} --build-arg RUNNER_PASS=$dml_runner_key --build-arg DD_GIT_REPOSITORY_URL=github.com/${{github.repository}} --build-arg DD_GIT_COMMIT_SHA=${{ github.event.pull_request.head.sha || github.sha }}
347→ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag | tail -n1 > ${{ matrix.env }}_${{ matrix.service }}
348→
349→ - name: Upload output matrix
350→ if: ${{ steps.should_run.outputs.condition == 'true' }}
351→ uses: actions/upload-artifact@v4
352→ with:
353→ name: ${{ matrix.env }}_${{ matrix.service }}
354→ path: ${{ matrix.service }}/${{ matrix.env }}_${{ matrix.service }}
355→
356→ update-helm-values:
357→ name: Update Helm Values for All Services
358→ runs-on: arc-runner-imprint
359→ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
360→ needs: upload-images
361→ environment: privilege-access
362→ strategy:
363→ fail-fast: false
364→ matrix:
365→ repo: [charts]
366→ steps:
367→ - name: Get Helm Chart Auto Update App Token
368→ id: app-token
369→ uses: actions/create-github-app-token@v1
370→ with:
371→ app-id: ${{ vars.HELM_CHART_AUTO_UPDATE_APP_ID }}
372→ private-key: ${{ secrets.HELM_CHART_AUTO_UPDATE_APP_PRIVATE_KEY }}
373→ repositories: ${{ matrix.repo }}
374→ owner: ${{ github.repository_owner }}
375→
376→ - name: Get GitHub App User ID
377→ id: get-user-id
378→ run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
379→ env:
380→ GH_TOKEN: ${{ steps.app-token.outputs.token }}
381→
382→ - name: Checkout Repository
383→ uses: actions/checkout@v4
384→ with:
385→ repository: ${{ github.repository_owner }}/${{ matrix.repo }}
386→ fetch-depth: 0
387→ token: ${{ steps.app-token.outputs.token }}
388→ ref: main
389→ persist-credentials: false
390→
391→ - name: Download All Artifacts
392→ uses: actions/download-artifact@v4
393→ with:
394→ path: artifacts
395→
396→ - name: Update values.yaml for All Services
397→ id: update-values
398→ run: |
399→ update=false
400→ for artifact_file in artifacts/*; do
401→ ARTIFACT_NAME=$(basename "$artifact_file")
402→
403→ echo "This is the artifact name: $ARTIFACT_NAME"
404→
405→ # Extract env and service name from artifact name
406→ ENV=$(echo $ARTIFACT_NAME | cut -d'_' -f1)
407→ SERVICE=$(echo $ARTIFACT_NAME | cut -d'_' -f2)
408→
409→ echo "This is the env and service: $ENV $SERVICE"
410→
411→ if [[ "$ENV" != "stg" && "$SERVICE" != "mirror" ]]; then
412→ echo "Skipping $SERVICE in $ENV environment."
413→ continue
414→ fi
415→
416→ # Read the image tag from the file inside the artifact
417→ OUTPUT=$(cat "artifacts/$ARTIFACT_NAME/$ARTIFACT_NAME")
418→ echo "This is the OUTPUT $OUTPUT"
419→ IMAGE_TAG=$(echo "$OUTPUT" | cut -d':' -f1)
420→
421→ VALUES_FILE="$SERVICE/chart/$ENV/values.yaml"
422→ if [ -f "$VALUES_FILE" ]; then
423→ sed -i "s|image_tag: .*|image_tag: $IMAGE_TAG|g" "$VALUES_FILE"
424→ update=true
425→ else
426→ echo "Warning: $VALUES_FILE not found, skipping..."
427→ fi
428→ done
429→ echo "update=$update" >> $GITHUB_OUTPUT
430→
431→ - name: Commit and Push Changes
432→ if: steps.update-values.outputs.update == 'true'
433→ run: |
434→ git config --global user.name "${{ steps.app-token.outputs.app-slug }}[bot]"
435→ git config --global user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
436→ git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository_owner }}/${{ matrix.repo }}
437→
438→ git add */chart/*/values.yaml
439→ git commit -m "Update image tags for service Helm charts"
440→
441→ git pull --rebase origin main
442→ git push origin main || git push --force origin main
443→
444→ comment-pr:
445→ needs:
446→ [
447→ upload-images,
448→ tagging-new-version,
449→ compute-affected-services,
450→ check-label,
451→ ]
452→ runs-on: arc-runner-imprint
453→ strategy:
454→ matrix:
455→ service: ${{fromJSON(needs.compute-affected-services.outputs.affected_services)}}
456→ env: [stg, preprd, prd, sbx]
457→ region: [us-east-1]
458→ exclude:
459→ - region: us-west-2
460→ env: prd
461→ - service: avatar
462→ env: prd
463→
464→ steps:
465→ - name: Check if should run
466→ id: should_run
467→ run: |
468→ condition=${{
469→ (github.event_name == 'push' || (needs.check-label.outputs.should_run == 'true' && matrix.env == 'stg')) &&
470→ (matrix.region == 'us-east-1' || contains(env.SERVICES_WEST, matrix.service)) &&
471→ ((matrix.env != 'preprd' && matrix.env != 'sbx') ||
472→ (matrix.env == 'preprd' && contains(env.SERVICES_PREPRD, matrix.service)) ||
473→ (matrix.env == 'sbx' && contains(env.SERVICES_SBX, matrix.service)))
474→ }}
475→ echo $condition
476→ echo "condition=$condition" >> $GITHUB_OUTPUT
477→
478→ - name: Download math result for job 1
479→ if: ${{ steps.should_run.outputs.condition == 'true' }}
480→ uses: actions/download-artifact@v4
481→ with:
482→ name: ${{ matrix.env }}_${{ matrix.service }}
483→
484→ - name: Retrieve from file
485→ id: retrieve_output
486→ if: ${{ steps.should_run.outputs.condition == 'true' }}
487→ run: |
488→ message=$(cat ${{ matrix.env }}_${{ matrix.service }})
489→
490→ echo $message
491→ echo "message=$message" >> $GITHUB_OUTPUT
492→
493→ - name: Comment PR
494→ if: ${{ steps.should_run.outputs.condition == 'true' }}
495→ uses: thollander/actions-comment-pull-request@v1
496→ with:
497→ pr_number: ${{ needs.tagging-new-version.outputs.pr_num }}
498→ message: "🟢 Created build for **${{ matrix.env }}-${{ matrix.service }}**: ${{ steps.retrieve_output.outputs.message }}"
499→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
I see that plan mode is still active. Let me exit plan mode first so I can proceed with implementation. The plan is ready and I should call ExitPlanMode to get user approval.
I see plan mode is still active. Let me exit plan mode so we can proceed with implementation.
{
"plan": "# Plan: Build Ledger Jobs Alongside Service\n\n## Summary\n\nModify `push_ecr.yml` to automatically build the 5 tracked ledger cronjobs whenever the ledger service is built, and update the charts repo with both service and job image tags.\n\n## Tracked Jobs (from prd chart)\n\n1. `apply-late-payment-fee`\n2. `create-statement-cycle`\n3. `credit-statement-due`\n4. `post-annual-fee`\n5. `run-credit-replay`\n\n## Implementation\n\n### File: `.github/workflows/push_ecr.yml`\n\n#### 1. Add Environment Variable for Tracked Jobs\n\n```yaml\nenv:\n GO_VERSION: 1.24\n # ... existing vars ...\n LEDGER_TRACKED_JOBS: \"apply-late-payment-fee,create-statement-cycle,credit-statement-due,post-annual-fee,run-credit-replay\"\n```\n\n#### 2. Add Job Version Generation Job (after `tagging-new-version`)\n\n```yaml\ngenerate-job-version:\n runs-on: arc-runner-imprint\n needs: [check-label, compute-affected-services]\n if: >-\n ${{ contains(needs.compute-affected-services.outputs.affected_services, 'ledger') &&\n (github.event_name == 'push' || needs.check-label.outputs.should_run == 'true') }}\n outputs:\n job_version: ${{ steps.version.outputs.job_version }}\n steps:\n - name: Generate job version\n id: version\n run: |\n date_str=$(date '+%Y%m%d.%H%M')\n git_hash=$(echo $GITHUB_SHA | head -c 6)\n job_version=\"job.$date_str.$git_hash\"\n echo \"job_version=$job_version\" >> $GITHUB_OUTPUT\n```\n\n#### 3. Add Build Ledger Jobs Job (after `upload-images`)\n\nNew job that builds all 5 tracked jobs using matrix strategy. Key steps:\n- Checkout, Go build cache, buf generate\n- Generate service config and job-specific config\n- Run each job's `prepare.sh` script\n- Build and push Docker image to ECR\n- Upload artifact with job version\n\nMatrix: `job × env` (5 jobs × 3 environments = 15 builds max)\n\n#### 4. Modify `update-helm-values` Job\n\nAdd `generate-job-version` and `build-ledger-jobs` to `needs`.\n\nUpdate the \"Update values.yaml\" step to also process job versions:\n\n```bash\n# After existing service artifact processing...\n\n# Process ledger job versions\njob_version=\"${{ needs.generate-job-version.outputs.job_version }}\"\nif [ -n \"$job_version\" ]; then\n for env in stg prd sbx; do\n VALUES_FILE=\"ledger/chart/$env/values.yaml\"\n if [ -f \"$VALUES_FILE\" ]; then\n for job in apply-late-payment-fee create-statement-cycle credit-statement-due post-annual-fee run-credit-replay; do\n var_name=$(echo \"$job\" | tr '-' '_')_version\n full_tag=\"$job_version.$job\"\n if grep -q \"^${var_name}:\" \"$VALUES_FILE\"; then\n sed -i \"s|^${var_name}:.*|${var_name}: $full_tag|g\" \"$VALUES_FILE\"\n update=true\n fi\n done\n fi\n done\nfi\n```\n\n### Key Design Decisions\n\n1. **Matrix Strategy**: Separate job from services to allow independent failure handling\n2. **Version Format**: Keep timestamp format `job.YYYYMMDD.HHMM.{hash}.{job-name}`\n3. **Conditional Execution**: Jobs only build when ledger is affected\n4. **Branch Builds**: Only stg images for branch builds, all envs for main\n5. **Build Caching**: Reuse Go build cache from service builds\n\n### Build Flow\n\n```\npush_ecr.yml (ledger affected)\n├── tagging-new-version\n├── compute-affected-services (outputs: ledger_affected=true)\n├── generate-job-version (outputs: job_version)\n├── upload-images (builds ledger service)\n├── build-ledger-jobs (matrix: 5 jobs × 3 envs)\n└── update-helm-values\n ├── Update service image_tag\n └── Update job versions (5 vars per env)\n```\n\n### Charts Repo Updates\n\nAfter merge to main, `ledger/chart/{env}/values.yaml` will have:\n\n```yaml\nimprint-service:\n image_tag: 0.4.359.ledger # Updated by service build\n\napply_late_payment_fee_version: job.20260109.1500.abc123.apply-late-payment-fee\ncreate_statement_cycle_version: job.20260109.1500.abc123.create-statement-cycle\ncredit_statement_due_version: job.20260109.1500.abc123.credit-statement-due\npost_annual_fee_version: job.20260109.1500.abc123.post-annual-fee\nrun_credit_replay_version: job.20260109.1500.abc123.run-credit-replay\n```\n\n## Files to Modify\n\n| File | Changes |\n|------|---------|\n| `.github/workflows/push_ecr.yml` | Add env var, job version job, build-ledger-jobs job, modify update-helm-values |\n\n## Verification\n\n1. **Branch Build Test**: Create PR with ledger changes, add \"needs branch build\" label\n - Verify only stg job images built\n - Verify no chart updates (branch builds don't update charts)\n\n2. **Main Branch Test**: Merge PR with ledger changes\n - Verify all 5 jobs built for stg, prd, sbx\n - Verify charts repo updated with both service image_tag and all 5 job versions\n - Check ArgoCD shows new versions pending\n\n3. **Non-Ledger Test**: Modify a different service\n - Verify no job builds triggered\n - Verify charts repo only updates service image_tag\n"
}User has approved your plan. You can now start coding. Start with updating your todo list if applicable
Your plan has been saved to: /Users/danielroth/.claude/plans/mellow-shimmying-kay.md
You can refer back to it if needed during implementation.
## Approved Plan:
# Plan: Build Ledger Jobs Alongside Service
## Summary
Modify `push_ecr.yml` to automatically build the 5 tracked ledger cronjobs whenever the ledger service is built, and update the charts repo with both service and job image tags.
## Tracked Jobs (from prd chart)
1. `apply-late-payment-fee`
2. `create-statement-cycle`
3. `credit-statement-due`
4. `post-annual-fee`
5. `run-credit-replay`
## Implementation
### File: `.github/workflows/push_ecr.yml`
#### 1. Add Environment Variable for Tracked Jobs
```yaml
env:
GO_VERSION: 1.24
# ... existing vars ...
LEDGER_TRACKED_JOBS: "apply-late-payment-fee,create-statement-cycle,credit-statement-due,post-annual-fee,run-credit-replay"
```
#### 2. Add Job Version Generation Job (after `tagging-new-version`)
```yaml
generate-job-version:
runs-on: arc-runner-imprint
needs: [check-label, compute-affected-services]
if: >-
${{ contains(needs.compute-affected-services.outputs.affected_services, 'ledger') &&
(github.event_name == 'push' || needs.check-label.outputs.should_run == 'true') }}
outputs:
job_version: ${{ steps.version.outputs.job_version }}
steps:
- name: Generate job version
id: version
run: |
date_str=$(date '+%Y%m%d.%H%M')
git_hash=$(echo $GITHUB_SHA | head -c 6)
job_version="job.$date_str.$git_hash"
echo "job_version=$job_version" >> $GITHUB_OUTPUT
```
#### 3. Add Build Ledger Jobs Job (after `upload-images`)
New job that builds all 5 tracked jobs using matrix strategy. Key steps:
- Checkout, Go build cache, buf generate
- Generate service config and job-specific config
- Run each job's `prepare.sh` script
- Build and push Docker image to ECR
- Upload artifact with job version
Matrix: `job × env` (5 jobs × 3 environments = 15 builds max)
#### 4. Modify `update-helm-values` Job
Add `generate-job-version` and `build-ledger-jobs` to `needs`.
Update the "Update values.yaml" step to also process job versions:
```bash
# After existing service artifact processing...
# Process ledger job versions
job_version="${{ needs.generate-job-version.outputs.job_version }}"
if [ -n "$job_version" ]; then
for env in stg prd sbx; do
VALUES_FILE="ledger/chart/$env/values.yaml"
if [ -f "$VALUES_FILE" ]; then
for job in apply-late-payment-fee create-statement-cycle credit-statement-due post-annual-fee run-credit-replay; do
var_name=$(echo "$job" | tr '-' '_')_version
full_tag="$job_version.$job"
if grep -q "^${var_name}:" "$VALUES_FILE"; then
sed -i "s|^${var_name}:.*|${var_name}: $full_tag|g" "$VALUES_FILE"
update=true
fi
done
fi
done
fi
```
### Key Design Decisions
1. **Matrix Strategy**: Separate job from services to allow independent failure handling
2. **Version Format**: Keep timestamp format `job.YYYYMMDD.HHMM.{hash}.{job-name}`
3. **Conditional Execution**: Jobs only build when ledger is affected
4. **Branch Builds**: Only stg images for branch builds, all envs for main
5. **Build Caching**: Reuse Go build cache from service builds
### Build Flow
```
push_ecr.yml (ledger affected)
├── tagging-new-version
├── compute-affected-services (outputs: ledger_affected=true)
├── generate-job-version (outputs: job_version)
├── upload-images (builds ledger service)
├── build-ledger-jobs (matrix: 5 jobs × 3 envs)
└── update-helm-values
├── Update service image_tag
└── Update job versions (5 vars per env)
```
### Charts Repo Updates
After merge to main, `ledger/chart/{env}/values.yaml` will have:
```yaml
imprint-service:
image_tag: 0.4.359.ledger # Updated by service build
apply_late_payment_fee_version: job.20260109.1500.abc123.apply-late-payment-fee
create_statement_cycle_version: job.20260109.1500.abc123.create-statement-cycle
credit_statement_due_version: job.20260109.1500.abc123.credit-statement-due
post_annual_fee_version: job.20260109.1500.abc123.post-annual-fee
run_credit_replay_version: job.20260109.1500.abc123.run-credit-replay
```
## Files to Modify
| File | Changes |
|------|---------|
| `.github/workflows/push_ecr.yml` | Add env var, job version job, build-ledger-jobs job, modify update-helm-values |
## Verification
1. **Branch Build Test**: Create PR with ledger changes, add "needs branch build" label
- Verify only stg job images built
- Verify no chart updates (branch builds don't update charts)
2. **Main Branch Test**: Merge PR with ledger changes
- Verify all 5 jobs built for stg, prd, sbx
- Verify charts repo updated with both service image_tag and all 5 job versions
- Check ArgoCD shows new versions pending
3. **Non-Ledger Test**: Modify a different service
- Verify no job builds triggered
- Verify charts repo only updates service image_tag
The plan is approved. Now I need to implement the changes to .github/workflows/push_ecr.yml. Let me set up my todo list and start implementing.
Key changes:
1. Add LEDGER_TRACKED_JOBS env var
2. Add generate-job-version job
3. Add build-ledger-jobs job
4. Modify update-helm-values job to update job versions
Let me start implementing.
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
Now let me start implementing the changes to push_ecr.yml. I'll start with adding the env var.
env:
GO_VERSION: 1.24
SERVICES_WEST: ('api', 'abacus', 'banking', 'census', 'imprintx', 'ledger', 'messenger', 'onboard', 'passport', 'rewards', 'sherlock', 'swiper', 'teller', 'watchman', 'bridge')
SERVICES_PREPRD: ('abacus', 'api', 'banking', 'billing', 'brands', 'census', 'crm', 'cupid', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'partnermetadata', 'passport', 'referrals', 'rewards', 'scribe', 'sherlock', 'sprinkler', 'swiper', 'teller', 'wallet', 'watchman')
SERVICES_SBX: ('abacus', 'api', 'avatar', 'banking', 'brands', 'billing', 'census', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'passport', 'rewards', 'scribe', 'sprinkler', 'swiper', 'teller', 'wallet', 'partnermetadata', 'referrals', 'bridge', 'crm')env:
GO_VERSION: 1.24
SERVICES_WEST: ('api', 'abacus', 'banking', 'census', 'imprintx', 'ledger', 'messenger', 'onboard', 'passport', 'rewards', 'sherlock', 'swiper', 'teller', 'watchman', 'bridge')
SERVICES_PREPRD: ('abacus', 'api', 'banking', 'billing', 'brands', 'census', 'crm', 'cupid', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'partnermetadata', 'passport', 'referrals', 'rewards', 'scribe', 'sherlock', 'sprinkler', 'swiper', 'teller', 'wallet', 'watchman')
SERVICES_SBX: ('abacus', 'api', 'avatar', 'banking', 'brands', 'billing', 'census', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'passport', 'rewards', 'scribe', 'sprinkler', 'swiper', 'teller', 'wallet', 'partnermetadata', 'referrals', 'bridge', 'crm')
LEDGER_TRACKED_JOBS: "apply-late-payment-fee,create-statement-cycle,credit-statement-due,post-annual-fee,run-credit-replay"The file /Users/danielroth/dev/go-backend/.github/workflows/push_ecr.yml has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
20→ - "*/jobs/**"
21→ - "*/chart/**"
22→ workflow_dispatch: {}
23→
24→env:
25→ GO_VERSION: 1.24
26→ SERVICES_WEST: ('api', 'abacus', 'banking', 'census', 'imprintx', 'ledger', 'messenger', 'onboard', 'passport', 'rewards', 'sherlock', 'swiper', 'teller', 'watchman', 'bridge')
27→ SERVICES_PREPRD: ('abacus', 'api', 'banking', 'billing', 'brands', 'census', 'crm', 'cupid', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'partnermetadata', 'passport', 'referrals', 'rewards', 'scribe', 'sherlock', 'sprinkler', 'swiper', 'teller', 'wallet', 'watchman')
28→ SERVICES_SBX: ('abacus', 'api', 'avatar', 'banking', 'brands', 'billing', 'census', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'passport', 'rewards', 'scribe', 'sprinkler', 'swiper', 'teller', 'wallet', 'partnermetadata', 'referrals', 'bridge', 'crm')
29→ LEDGER_TRACKED_JOBS: "apply-late-payment-fee,create-statement-cycle,credit-statement-due,post-annual-fee,run-credit-replay"
30→
31→jobs:
32→ check-label:
33→ runs-on: arc-runner-imprintGood, I added the env var. Now let me update the todo list and add the generate-job-version job. I'll add this after the compute-affected-services job.
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
Now let me add the generate-job-version job. I'll add it after the compute-affected-services job. Looking at the file, compute-affected-services ends at around line 144 (where it outputs the affected services). Let me add the new job after that.
echo "$affected"
echo "affected=$affected" >> $GITHUB_OUTPUT
upload-affected-services: echo "$affected"
echo "affected=$affected" >> $GITHUB_OUTPUT
generate-job-version:
runs-on: arc-runner-imprint
needs: [check-label, compute-affected-services]
if: >-
${{ contains(needs.compute-affected-services.outputs.affected_services, 'ledger') &&
(github.event_name == 'push' || needs.check-label.outputs.should_run == 'true') }}
outputs:
job_version: ${{ steps.version.outputs.job_version }}
steps:
- name: Generate job version
id: version
run: |
date_str=$(date '+%Y%m%d.%H%M')
git_hash=$(echo $GITHUB_SHA | head -c 6)
job_version="job.$date_str.$git_hash"
echo "job_version=$job_version" >> $GITHUB_OUTPUT
upload-affected-services:The file /Users/danielroth/dev/go-backend/.github/workflows/push_ecr.yml has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
139→ labels='${{ toJSON(github.event.pull_request.labels) }}'
140→ fi
141→ affected=$(python3 ./.github/workflows/parse_service_labels.py "$labels")
142→
143→ echo "$affected"
144→ echo "affected=$affected" >> $GITHUB_OUTPUT
145→
146→ generate-job-version:
147→ runs-on: arc-runner-imprint
148→ needs: [check-label, compute-affected-services]
149→ if: >-
150→ ${{ contains(needs.compute-affected-services.outputs.affected_services, 'ledger') &&
151→ (github.event_name == 'push' || needs.check-label.outputs.should_run == 'true') }}
152→ outputs:
153→ job_version: ${{ steps.version.outputs.job_version }}
154→ steps:
155→ - name: Generate job version
156→ id: version
157→ run: |
158→ date_str=$(date '+%Y%m%d.%H%M')
159→ git_hash=$(echo $GITHUB_SHA | head -c 6)
160→ job_version="job.$date_str.$git_hash"
161→ echo "job_version=$job_version" >> $GITHUB_OUTPUT
162→
163→ upload-affected-services:
164→ needs: compute-affected-services
165→ runs-on: arc-runner-imprint
166→ if: ${{ github.event_name == 'push' }}
167→ permissions:Now I need to add the build-ledger-jobs job. This should be after the upload-images job. Let me first read more of the file to find the right insertion point.
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
Now let me add the build-ledger-jobs job. I'll insert it after upload-images, which ends with the artifact upload step. Looking at the original file (line 354), it ends with uploading artifact. I need to add the new job after that.
Let me read a bit more of the file to find the exact insertion point.
{
"file_path": "/Users/danielroth/dev/go-backend/.github/workflows/push_ecr.yml",
"offset": 360,
"limit": 50
} 360→ ECR_REPOSITORY="${{ matrix.service }}"
361→ echo "Using ECR Repository: $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag"
362→
363→ cp -r ../.git ./.git
364→ docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag . -f ${{ steps.setup_env.outputs.docker_file_name }} --build-arg RUNNER_PASS=$dml_runner_key --build-arg DD_GIT_REPOSITORY_URL=github.com/${{github.repository}} --build-arg DD_GIT_COMMIT_SHA=${{ github.event.pull_request.head.sha || github.sha }}
365→ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag | tail -n1 > ${{ matrix.env }}_${{ matrix.service }}
366→
367→ - name: Upload output matrix
368→ if: ${{ steps.should_run.outputs.condition == 'true' }}
369→ uses: actions/upload-artifact@v4
370→ with:
371→ name: ${{ matrix.env }}_${{ matrix.service }}
372→ path: ${{ matrix.service }}/${{ matrix.env }}_${{ matrix.service }}
373→
374→ update-helm-values:
375→ name: Update Helm Values for All Services
376→ runs-on: arc-runner-imprint
377→ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
378→ needs: upload-images
379→ environment: privilege-access
380→ strategy:
381→ fail-fast: false
382→ matrix:
383→ repo: [charts]
384→ steps:
385→ - name: Get Helm Chart Auto Update App Token
386→ id: app-token
387→ uses: actions/create-github-app-token@v1
388→ with:
389→ app-id: ${{ vars.HELM_CHART_AUTO_UPDATE_APP_ID }}
390→ private-key: ${{ secrets.HELM_CHART_AUTO_UPDATE_APP_PRIVATE_KEY }}
391→ repositories: ${{ matrix.repo }}
392→ owner: ${{ github.repository_owner }}
393→
394→ - name: Get GitHub App User ID
395→ id: get-user-id
396→ run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
397→ env:
398→ GH_TOKEN: ${{ steps.app-token.outputs.token }}
399→
400→ - name: Checkout Repository
401→ uses: actions/checkout@v4
402→ with:
403→ repository: ${{ github.repository_owner }}/${{ matrix.repo }}
404→ fetch-depth: 0
405→ token: ${{ steps.app-token.outputs.token }}
406→ ref: main
407→ persist-credentials: false
408→
409→ - name: Download All Artifacts
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Good. I can see that:
- upload-images ends at line 372 (the artifact upload step)
- update-helm-values starts at line 374
- update-helm-values currently needs: upload-images
I need to:
1. Add build-ledger-jobs job after upload-images (before update-helm-values)
2. Modify update-helm-values to also need generate-job-version and build-ledger-jobs
Let me first add the build-ledger-jobs job. I need to look at job_runner_build.yml for reference on how jobs are built. Let me create the new job based on that workflow.
- name: Upload output matrix
if: ${{ steps.should_run.outputs.condition == 'true' }}
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.env }}_${{ matrix.service }}
path: ${{ matrix.service }}/${{ matrix.env }}_${{ matrix.service }}
update-helm-values: - name: Upload output matrix
if: ${{ steps.should_run.outputs.condition == 'true' }}
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.env }}_${{ matrix.service }}
path: ${{ matrix.service }}/${{ matrix.env }}_${{ matrix.service }}
build-ledger-jobs:
needs: [generate-job-version, check-label]
if: ${{ needs.generate-job-version.outputs.job_version != '' }}
runs-on: arc-runner-imprint
permissions:
id-token: write
contents: read
strategy:
fail-fast: false
matrix:
job: [apply-late-payment-fee, create-statement-cycle, credit-statement-due, post-annual-fee, run-credit-replay]
env: [stg, prd, sbx]
env:
JOB_MANIFEST_DIR: manifests
steps:
- name: Check if should run
id: should_run
run: |
# Branch builds only build stg, main builds all envs
condition=${{ matrix.env == 'stg' || github.event_name == 'push' }}
echo "condition=$condition" >> $GITHUB_OUTPUT
- name: Checkout repo
if: ${{ steps.should_run.outputs.condition == 'true' }}
uses: actions/checkout@v4
- name: Go Build Cache
if: ${{ steps.should_run.outputs.condition == 'true' }}
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
key: ${{ runner.os }}-config-gen-ledger-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-config-gen-ledger-
- name: generate protobuf files
if: ${{ steps.should_run.outputs.condition == 'true' }}
env:
BUF_TOKEN: ${{ secrets.BUF_TOKEN }}
run: buf generate
- name: Prepare Job manifest
id: prepare
if: ${{ steps.should_run.outputs.condition == 'true' }}
run: |
mkdir -p ${{ env.JOB_MANIFEST_DIR }}
job="${{ matrix.job }}"
service="ledger"
job_manifest_path=$(pwd)/${{ env.JOB_MANIFEST_DIR }}
echo "generating ledger config at $job_manifest_path"
go run scripts/genconfig/generate_config.go -service ledger -env ${{ matrix.env }} -region us-east-1
cp ledger/*config.json ledger/jobs/$job
# if the job has its own config generator, generate the job config
go run scripts/genconfig/generate_config.go -service $job -env ${{ matrix.env }} -region all
cp *config.json ledger/jobs/$job 2>/dev/null || true
echo "preparing job $job, manifest dir at $job_manifest_path"
pushd .
cd ledger/jobs/$job
chmod 755 ./prepare.sh
./prepare.sh $job_manifest_path ${{ matrix.env }}
popd
ls -lah ${{ env.JOB_MANIFEST_DIR }}
tag=$(echo ${{ needs.generate-job-version.outputs.job_version }}.$job | head -c 55 | sed -E 's/[^a-zA-Z0-9]$//')
echo "job=$job" >> $GITHUB_OUTPUT
echo "tag=$tag" >> $GITHUB_OUTPUT
- name: Setup AWS Env
if: ${{ steps.should_run.outputs.condition == 'true' }}
id: aws_env
run: |
role_to_assume=${{ vars.STG_ECR_UPLOAD_ROLE }}
if [ ${{ matrix.env }} = 'prd' ]; then
echo "using production aws role"
role_to_assume=${{ vars.PRD_ECR_UPLOAD_ROLE }}
elif [ ${{ matrix.env }} = 'sbx' ]; then
echo "using sandbox aws role"
role_to_assume=${{ vars.SBX_ECR_UPLOAD_ROLE }}
fi
echo "role_to_assume=$role_to_assume" >> $GITHUB_OUTPUT
- name: Configure AWS credentials
if: ${{ steps.should_run.outputs.condition == 'true' }}
uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
with:
role-to-assume: ${{ steps.aws_env.outputs.role_to_assume }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
if: ${{ steps.should_run.outputs.condition == 'true' }}
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push image to Amazon ECR
id: build_push
if: ${{ steps.should_run.outputs.condition == 'true' }}
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: "${{ steps.prepare.outputs.tag }}"
working-directory: ${{ env.JOB_MANIFEST_DIR }}
run: |
ECR_REPOSITORY="ledger"
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
# Make it visible in the run UI
echo "::notice title=Job Image Built::${{ matrix.env }} • ledger • $IMAGE_TAG"
- name: Publish build summary
if: ${{ steps.should_run.outputs.condition == 'true' }}
run: |
{
echo "## :whale: Job Image Built"
echo ""
echo "- Env: \`${{ matrix.env }}\`"
echo "- Service: \`ledger\`"
echo "- Job: \`${{ steps.prepare.outputs.job }}\`"
echo "- Tag: \`${{ steps.build_push.outputs.image_tag }}\`"
} >> "$GITHUB_STEP_SUMMARY"
update-helm-values:The file /Users/danielroth/dev/go-backend/.github/workflows/push_ecr.yml has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
363→ cp -r ../.git ./.git
364→ docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag . -f ${{ steps.setup_env.outputs.docker_file_name }} --build-arg RUNNER_PASS=$dml_runner_key --build-arg DD_GIT_REPOSITORY_URL=github.com/${{github.repository}} --build-arg DD_GIT_COMMIT_SHA=${{ github.event.pull_request.head.sha || github.sha }}
365→ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag | tail -n1 > ${{ matrix.env }}_${{ matrix.service }}
366→
367→ - name: Upload output matrix
368→ if: ${{ steps.should_run.outputs.condition == 'true' }}
369→ uses: actions/upload-artifact@v4
370→ with:
371→ name: ${{ matrix.env }}_${{ matrix.service }}
372→ path: ${{ matrix.service }}/${{ matrix.env }}_${{ matrix.service }}
373→
374→ build-ledger-jobs:
375→ needs: [generate-job-version, check-label]
376→ if: ${{ needs.generate-job-version.outputs.job_version != '' }}
377→ runs-on: arc-runner-imprint
378→ permissions:
379→ id-token: write
380→ contents: read
381→ strategy:
382→ fail-fast: false
383→ matrix:
384→ job: [apply-late-payment-fee, create-statement-cycle, credit-statement-due, post-annual-fee, run-credit-replay]
385→ env: [stg, prd, sbx]
386→ env:
387→ JOB_MANIFEST_DIR: manifests
388→
389→ steps:
390→ - name: Check if should run
391→ id: should_run
392→ run: |
393→ # Branch builds only build stg, main builds all envs
394→ condition=${{ matrix.env == 'stg' || github.event_name == 'push' }}
395→ echo "condition=$condition" >> $GITHUB_OUTPUT
396→
397→ - name: Checkout repo
398→ if: ${{ steps.should_run.outputs.condition == 'true' }}
399→ uses: actions/checkout@v4
400→
401→ - name: Go Build Cache
402→ if: ${{ steps.should_run.outputs.condition == 'true' }}
403→ uses: actions/cache@v4
404→ with:
405→ path: |
406→ ~/.cache/go-build
407→ key: ${{ runner.os }}-config-gen-ledger-${{ hashFiles('**/go.sum') }}
408→ restore-keys: |
409→ ${{ runner.os }}-config-gen-ledger-
410→
411→ - name: generate protobuf files
412→ if: ${{ steps.should_run.outputs.condition == 'true' }}
413→ env:
414→ BUF_TOKEN: ${{ secrets.BUF_TOKEN }}
415→ run: buf generate
416→
417→ - name: Prepare Job manifest
418→ id: prepare
419→ if: ${{ steps.should_run.outputs.condition == 'true' }}
420→ run: |
421→ mkdir -p ${{ env.JOB_MANIFEST_DIR }}
422→
423→ job="${{ matrix.job }}"
424→ service="ledger"
425→ job_manifest_path=$(pwd)/${{ env.JOB_MANIFEST_DIR }}
426→
427→ echo "generating ledger config at $job_manifest_path"
428→
429→ go run scripts/genconfig/generate_config.go -service ledger -env ${{ matrix.env }} -region us-east-1
430→ cp ledger/*config.json ledger/jobs/$job
431→
432→ # if the job has its own config generator, generate the job config
433→ go run scripts/genconfig/generate_config.go -service $job -env ${{ matrix.env }} -region all
434→ cp *config.json ledger/jobs/$job 2>/dev/null || true
435→
436→ echo "preparing job $job, manifest dir at $job_manifest_path"
437→
438→ pushd .
439→ cd ledger/jobs/$job
440→
441→ chmod 755 ./prepare.sh
442→ ./prepare.sh $job_manifest_path ${{ matrix.env }}
443→
444→ popd
445→
446→ ls -lah ${{ env.JOB_MANIFEST_DIR }}
447→
448→ tag=$(echo ${{ needs.generate-job-version.outputs.job_version }}.$job | head -c 55 | sed -E 's/[^a-zA-Z0-9]$//')
449→
450→ echo "job=$job" >> $GITHUB_OUTPUT
451→ echo "tag=$tag" >> $GITHUB_OUTPUT
452→
453→ - name: Setup AWS Env
454→ if: ${{ steps.should_run.outputs.condition == 'true' }}
455→ id: aws_env
456→ run: |
457→ role_to_assume=${{ vars.STG_ECR_UPLOAD_ROLE }}
458→ if [ ${{ matrix.env }} = 'prd' ]; then
459→ echo "using production aws role"
460→ role_to_assume=${{ vars.PRD_ECR_UPLOAD_ROLE }}
461→ elif [ ${{ matrix.env }} = 'sbx' ]; then
462→ echo "using sandbox aws role"
463→ role_to_assume=${{ vars.SBX_ECR_UPLOAD_ROLE }}
464→ fi
465→ echo "role_to_assume=$role_to_assume" >> $GITHUB_OUTPUT
466→
467→ - name: Configure AWS credentials
468→ if: ${{ steps.should_run.outputs.condition == 'true' }}
469→ uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
470→ with:
471→ role-to-assume: ${{ steps.aws_env.outputs.role_to_assume }}
472→ aws-region: us-east-1
473→
474→ - name: Login to Amazon ECR
475→ id: login-ecr
476→ if: ${{ steps.should_run.outputs.condition == 'true' }}
477→ uses: aws-actions/amazon-ecr-login@v2
478→
479→ - name: Build and push image to Amazon ECR
480→ id: build_push
481→ if: ${{ steps.should_run.outputs.condition == 'true' }}
482→ env:
483→ ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
484→ IMAGE_TAG: "${{ steps.prepare.outputs.tag }}"
485→ working-directory: ${{ env.JOB_MANIFEST_DIR }}
486→ run: |
487→ ECR_REPOSITORY="ledger"
488→
489→ docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
490→ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
491→
492→ echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
493→
494→ # Make it visible in the run UI
495→ echo "::notice title=Job Image Built::${{ matrix.env }} • ledger • $IMAGE_TAG"
496→
497→ - name: Publish build summary
498→ if: ${{ steps.should_run.outputs.condition == 'true' }}
499→ run: |
500→ {
501→ echo "## :whale: Job Image Built"
502→ echo ""
503→ echo "- Env: \`${{ matrix.env }}\`"
504→ echo "- Service: \`ledger\`"
505→ echo "- Job: \`${{ steps.prepare.outputs.job }}\`"
506→ echo "- Tag: \`${{ steps.build_push.outputs.image_tag }}\`"
507→ } >> "$GITHUB_STEP_SUMMARY"
508→
509→ update-helm-values:
510→ name: Update Helm Values for All Services
511→ runs-on: arc-runner-imprint
512→ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
513→ needs: upload-imagesNow I need to modify the update-helm-values job to:
1. Add generate-job-version and build-ledger-jobs to its needs
2. Modify the "Update values.yaml" step to also update job versions
Let me update the todos and then make these changes.
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
Now let me modify the update-helm-values job. I need to:1. Change its needs to also include generate-job-version and build-ledger-jobs
2. Update the "Update values.yaml" step to also process job versionsFirst, let me update the needs line:
update-helm-values:
name: Update Helm Values for All Services
runs-on: arc-runner-imprint
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: upload-images update-helm-values:
name: Update Helm Values for All Services
runs-on: arc-runner-imprint
if: ${{ always() && github.event_name == 'push' && github.ref == 'refs/heads/main' && (needs.upload-images.result == 'success' || needs.build-ledger-jobs.result == 'success') }}
needs: [upload-images, generate-job-version, build-ledger-jobs]The file /Users/danielroth/dev/go-backend/.github/workflows/push_ecr.yml has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
505→ echo "- Job: \`${{ steps.prepare.outputs.job }}\`"
506→ echo "- Tag: \`${{ steps.build_push.outputs.image_tag }}\`"
507→ } >> "$GITHUB_STEP_SUMMARY"
508→
509→ update-helm-values:
510→ name: Update Helm Values for All Services
511→ runs-on: arc-runner-imprint
512→ if: ${{ always() && github.event_name == 'push' && github.ref == 'refs/heads/main' && (needs.upload-images.result == 'success' || needs.build-ledger-jobs.result == 'success') }}
513→ needs: [upload-images, generate-job-version, build-ledger-jobs]
514→ environment: privilege-access
515→ strategy:
516→ fail-fast: false
517→ matrix:Now I need to modify the "Update values.yaml" step to also process job versions. Let me read the current step first.
{
"file_path": "/Users/danielroth/dev/go-backend/.github/workflows/push_ecr.yml",
"offset": 525,
"limit": 70
} 525→ private-key: ${{ secrets.HELM_CHART_AUTO_UPDATE_APP_PRIVATE_KEY }}
526→ repositories: ${{ matrix.repo }}
527→ owner: ${{ github.repository_owner }}
528→
529→ - name: Get GitHub App User ID
530→ id: get-user-id
531→ run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
532→ env:
533→ GH_TOKEN: ${{ steps.app-token.outputs.token }}
534→
535→ - name: Checkout Repository
536→ uses: actions/checkout@v4
537→ with:
538→ repository: ${{ github.repository_owner }}/${{ matrix.repo }}
539→ fetch-depth: 0
540→ token: ${{ steps.app-token.outputs.token }}
541→ ref: main
542→ persist-credentials: false
543→
544→ - name: Download All Artifacts
545→ uses: actions/download-artifact@v4
546→ with:
547→ path: artifacts
548→
549→ - name: Update values.yaml for All Services
550→ id: update-values
551→ run: |
552→ update=false
553→ for artifact_file in artifacts/*; do
554→ ARTIFACT_NAME=$(basename "$artifact_file")
555→
556→ echo "This is the artifact name: $ARTIFACT_NAME"
557→
558→ # Extract env and service name from artifact name
559→ ENV=$(echo $ARTIFACT_NAME | cut -d'_' -f1)
560→ SERVICE=$(echo $ARTIFACT_NAME | cut -d'_' -f2)
561→
562→ echo "This is the env and service: $ENV $SERVICE"
563→
564→ if [[ "$ENV" != "stg" && "$SERVICE" != "mirror" ]]; then
565→ echo "Skipping $SERVICE in $ENV environment."
566→ continue
567→ fi
568→
569→ # Read the image tag from the file inside the artifact
570→ OUTPUT=$(cat "artifacts/$ARTIFACT_NAME/$ARTIFACT_NAME")
571→ echo "This is the OUTPUT $OUTPUT"
572→ IMAGE_TAG=$(echo "$OUTPUT" | cut -d':' -f1)
573→
574→ VALUES_FILE="$SERVICE/chart/$ENV/values.yaml"
575→ if [ -f "$VALUES_FILE" ]; then
576→ sed -i "s|image_tag: .*|image_tag: $IMAGE_TAG|g" "$VALUES_FILE"
577→ update=true
578→ else
579→ echo "Warning: $VALUES_FILE not found, skipping..."
580→ fi
581→ done
582→ echo "update=$update" >> $GITHUB_OUTPUT
583→
584→ - name: Commit and Push Changes
585→ if: steps.update-values.outputs.update == 'true'
586→ run: |
587→ git config --global user.name "${{ steps.app-token.outputs.app-slug }}[bot]"
588→ git config --global user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
589→ git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository_owner }}/${{ matrix.repo }}
590→
591→ git add */chart/*/values.yaml
592→ git commit -m "Update image tags for service Helm charts"
593→
594→ git pull --rebase origin main
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Now I need to modify the "Update values.yaml for All Services" step to also process job versions. I'll add the job version processing logic after the existing artifact processing loop.
- name: Update values.yaml for All Services
id: update-values
run: |
update=false
for artifact_file in artifacts/*; do
ARTIFACT_NAME=$(basename "$artifact_file")
echo "This is the artifact name: $ARTIFACT_NAME"
# Extract env and service name from artifact name
ENV=$(echo $ARTIFACT_NAME | cut -d'_' -f1)
SERVICE=$(echo $ARTIFACT_NAME | cut -d'_' -f2)
echo "This is the env and service: $ENV $SERVICE"
if [[ "$ENV" != "stg" && "$SERVICE" != "mirror" ]]; then
echo "Skipping $SERVICE in $ENV environment."
continue
fi
# Read the image tag from the file inside the artifact
OUTPUT=$(cat "artifacts/$ARTIFACT_NAME/$ARTIFACT_NAME")
echo "This is the OUTPUT $OUTPUT"
IMAGE_TAG=$(echo "$OUTPUT" | cut -d':' -f1)
VALUES_FILE="$SERVICE/chart/$ENV/values.yaml"
if [ -f "$VALUES_FILE" ]; then
sed -i "s|image_tag: .*|image_tag: $IMAGE_TAG|g" "$VALUES_FILE"
update=true
else
echo "Warning: $VALUES_FILE not found, skipping..."
fi
done
echo "update=$update" >> $GITHUB_OUTPUT - name: Update values.yaml for All Services
id: update-values
run: |
update=false
for artifact_file in artifacts/*; do
ARTIFACT_NAME=$(basename "$artifact_file")
echo "This is the artifact name: $ARTIFACT_NAME"
# Extract env and service name from artifact name
ENV=$(echo $ARTIFACT_NAME | cut -d'_' -f1)
SERVICE=$(echo $ARTIFACT_NAME | cut -d'_' -f2)
echo "This is the env and service: $ENV $SERVICE"
if [[ "$ENV" != "stg" && "$SERVICE" != "mirror" ]]; then
echo "Skipping $SERVICE in $ENV environment."
continue
fi
# Read the image tag from the file inside the artifact
OUTPUT=$(cat "artifacts/$ARTIFACT_NAME/$ARTIFACT_NAME")
echo "This is the OUTPUT $OUTPUT"
IMAGE_TAG=$(echo "$OUTPUT" | cut -d':' -f1)
VALUES_FILE="$SERVICE/chart/$ENV/values.yaml"
if [ -f "$VALUES_FILE" ]; then
sed -i "s|image_tag: .*|image_tag: $IMAGE_TAG|g" "$VALUES_FILE"
update=true
else
echo "Warning: $VALUES_FILE not found, skipping..."
fi
done
# Process ledger job versions
job_version="${{ needs.generate-job-version.outputs.job_version }}"
if [ -n "$job_version" ]; then
echo "Processing ledger job versions with base version: $job_version"
for env_name in stg prd sbx; do
VALUES_FILE="ledger/chart/$env_name/values.yaml"
if [ -f "$VALUES_FILE" ]; then
for job in apply-late-payment-fee create-statement-cycle credit-statement-due post-annual-fee run-credit-replay; do
var_name=$(echo "$job" | tr '-' '_')_version
full_tag="$job_version.$job"
if grep -q "^${var_name}:" "$VALUES_FILE"; then
echo "Updating $var_name to $full_tag in $VALUES_FILE"
sed -i "s|^${var_name}:.*|${var_name}: $full_tag|g" "$VALUES_FILE"
update=true
fi
done
else
echo "Warning: $VALUES_FILE not found, skipping job version updates..."
fi
done
fi
echo "update=$update" >> $GITHUB_OUTPUTThe file /Users/danielroth/dev/go-backend/.github/workflows/push_ecr.yml has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
545→ uses: actions/download-artifact@v4
546→ with:
547→ path: artifacts
548→
549→ - name: Update values.yaml for All Services
550→ id: update-values
551→ run: |
552→ update=false
553→ for artifact_file in artifacts/*; do
554→ ARTIFACT_NAME=$(basename "$artifact_file")
555→
556→ echo "This is the artifact name: $ARTIFACT_NAME"
557→
558→ # Extract env and service name from artifact name
559→ ENV=$(echo $ARTIFACT_NAME | cut -d'_' -f1)
560→ SERVICE=$(echo $ARTIFACT_NAME | cut -d'_' -f2)
561→
562→ echo "This is the env and service: $ENV $SERVICE"
563→
564→ if [[ "$ENV" != "stg" && "$SERVICE" != "mirror" ]]; then
565→ echo "Skipping $SERVICE in $ENV environment."
566→ continue
567→ fi
568→
569→ # Read the image tag from the file inside the artifact
570→ OUTPUT=$(cat "artifacts/$ARTIFACT_NAME/$ARTIFACT_NAME")
571→ echo "This is the OUTPUT $OUTPUT"
572→ IMAGE_TAG=$(echo "$OUTPUT" | cut -d':' -f1)
573→
574→ VALUES_FILE="$SERVICE/chart/$ENV/values.yaml"
575→ if [ -f "$VALUES_FILE" ]; then
576→ sed -i "s|image_tag: .*|image_tag: $IMAGE_TAG|g" "$VALUES_FILE"
577→ update=true
578→ else
579→ echo "Warning: $VALUES_FILE not found, skipping..."
580→ fi
581→ done
582→
583→ # Process ledger job versions
584→ job_version="${{ needs.generate-job-version.outputs.job_version }}"
585→ if [ -n "$job_version" ]; then
586→ echo "Processing ledger job versions with base version: $job_version"
587→ for env_name in stg prd sbx; do
588→ VALUES_FILE="ledger/chart/$env_name/values.yaml"
589→ if [ -f "$VALUES_FILE" ]; then
590→ for job in apply-late-payment-fee create-statement-cycle credit-statement-due post-annual-fee run-credit-replay; do
591→ var_name=$(echo "$job" | tr '-' '_')_version
592→ full_tag="$job_version.$job"
593→ if grep -q "^${var_name}:" "$VALUES_FILE"; then
594→ echo "Updating $var_name to $full_tag in $VALUES_FILE"
595→ sed -i "s|^${var_name}:.*|${var_name}: $full_tag|g" "$VALUES_FILE"
596→ update=true
597→ fi
598→ done
599→ else
600→ echo "Warning: $VALUES_FILE not found, skipping job version updates..."
601→ fi
602→ done
603→ fi
604→
605→ echo "update=$update" >> $GITHUB_OUTPUT
606→
607→ - name: Commit and Push Changes
608→ if: steps.update-values.outputs.update == 'true'
609→ run: |All the changes have been made. Let me update the todo list and then verify the workflow YAML file is valid by checking its syntax.
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
Let me verify the YAML syntax is valid by reading the entire file and checking it with a YAML validator or just parsing it.
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/push_ecr.yml'))" && echo "YAML syntax is valid"The YAML syntax is valid. Let me now read the complete modified file to verify the structure is correct and then provide a summary to the user.
The YAML syntax is valid. Let me verify the complete workflow structure by reviewing the key parts.
1→name: Push to AWS ECR
2→
3→on:
4→ pull_request:
5→ types:
6→ - labeled
7→ paths-ignore:
8→ - "scripts/**"
9→ - "swagger/**"
10→ - "*/jobs/**"
11→ - "*/chart/**"
12→ push:
13→ branches:
14→ - main
15→ - heb-release
16→ paths-ignore:
17→ - ".github/**"
18→ - "scripts/**"
19→ - "swagger/**"
20→ - "*/jobs/**"
21→ - "*/chart/**"
22→ workflow_dispatch: {}
23→
24→env:
25→ GO_VERSION: 1.24
26→ SERVICES_WEST: ('api', 'abacus', 'banking', 'census', 'imprintx', 'ledger', 'messenger', 'onboard', 'passport', 'rewards', 'sherlock', 'swiper', 'teller', 'watchman', 'bridge')
27→ SERVICES_PREPRD: ('abacus', 'api', 'banking', 'billing', 'brands', 'census', 'crm', 'cupid', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'partnermetadata', 'passport', 'referrals', 'rewards', 'scribe', 'sherlock', 'sprinkler', 'swiper', 'teller', 'wallet', 'watchman')
28→ SERVICES_SBX: ('abacus', 'api', 'avatar', 'banking', 'brands', 'billing', 'census', 'gavel', 'imprintx', 'ledger', 'messenger', 'mirror', 'onboard', 'passport', 'rewards', 'scribe', 'sprinkler', 'swiper', 'teller', 'wallet', 'partnermetadata', 'referrals', 'bridge', 'crm')
29→ LEDGER_TRACKED_JOBS: "apply-late-payment-fee,create-statement-cycle,credit-statement-due,post-annual-fee,run-credit-replay"
30→
31→jobs:
32→ check-label:
33→ runs-on: arc-runner-imprint
34→ outputs:
35→ should_run: ${{ steps.check_label.outputs.should_run }}
36→ steps:
37→ - name: Check label
38→ id: check_label
39→ if: ${{ github.event_name == 'pull_request' }}
40→ run: |
41→ if [[ ${{ github.event_name }} == 'pull_request' ]] && [[ ${{ github.event.action }} == 'labeled' ]] && [[ "${{ github.event.label.name }}" == 'needs branch build' ]]; then
42→ echo "Starting branch build"
43→ echo "should_run=true" >> $GITHUB_OUTPUT
44→ else
45→ echo "should_run=false" >> $GITHUB_OUTPUT
46→ fi
47→
48→ - name: Remove label
49→ id: remove
50→ if: ${{ steps.check_label.outputs.should_run == 'true' }}
51→ uses: actions-ecosystem/action-remove-labels@v1
52→ with:
53→ repo: ${{ github.repository }}
54→ labels: |
55→ needs branch build
56→
57→ tagging-new-version:
58→ runs-on: arc-runner-imprint
59→ needs: [check-label]
60→ if: ${{ github.event_name == 'push' || needs.check-label.outputs.should_run == 'true' }}
61→ concurrency:
62→ group: tagging-new-version-${{ github.ref }}
63→ cancel-in-progress: false
64→ outputs:
65→ new_version: ${{ steps.get_new_version.outputs.new_version }}
66→ pr_num: ${{ steps.get_new_version.outputs.pr_num }}
67→
68→ steps:
69→ - name: Retrieve PR Number
70→ if: ${{ github.event_name == 'push' }}
71→ uses: octokit/request-action@v2.x
72→ id: retrieve_pr
73→ with:
74→ route: GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls
75→ owner: Imprint-Tech
76→ repo: go-backend
77→ commit_sha: ${{ github.sha }}
78→ env:
79→ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
80→
81→ - name: Checkout
82→ if: ${{ github.event_name == 'push' }}
83→ uses: actions/checkout@v4
84→
85→ - name: Bump version and push tag
86→ if: ${{ github.event_name == 'push' }}
87→ id: tag_version
88→ uses: mathieudutour/github-tag-action@v5.4
89→ with:
90→ github_token: ${{ secrets.GITHUB_TOKEN }}
91→ dry_run: false
92→
93→ - name: Output new version
94→ id: get_new_version
95→ run: |
96→ if [ ${{ github.event_name }} = 'push' ]; then
97→ pr_num=${{ steps.retrieve_pr.outputs.data && fromJson(steps.retrieve_pr.outputs.data)[0].number }}
98→ echo "new_version=${{ steps.tag_version.outputs.new_version }}" >> $GITHUB_OUTPUT
99→ else
100→ pr_num=${{ github.event.pull_request.number }}
101→ echo "new_version=brc.svc.$pr_num.$(echo $GITHUB_SHA | head -c 6)" >> $GITHUB_OUTPUT
102→ fi
103→
104→ echo "pr_num=$pr_num" >> $GITHUB_OUTPUT
105→
106→ compute-affected-services:
107→ runs-on: arc-runner-imprint
108→ outputs:
109→ affected_services: ${{ steps.compute.outputs.affected }}
110→
111→ steps:
112→ - name: Checkout repo
113→ uses: actions/checkout@v4
114→
115→ - name: Get changed files
116→ id: changed_files
117→ if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
118→ uses: Ana06/get-changed-files@v2.2.0
119→ continue-on-error: true
120→
121→ - name: Retrieve PR
122→ if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
123→ uses: octokit/request-action@v2.x
124→ id: retrieve_pr
125→ with:
126→ route: GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls
127→ owner: Imprint-Tech
128→ repo: go-backend
129→ commit_sha: ${{ github.sha }}
130→ env:
131→ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
132→
133→ - name: Compute affected services
134→ id: compute
135→ run: |
136→ if [[ ${{ github.event_name }} == 'push' ]] || [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then
137→ labels='${{ steps.retrieve_pr.outputs.data && toJSON(fromJSON(steps.retrieve_pr.outputs.data)[0].labels) }}'
138→ else
139→ labels='${{ toJSON(github.event.pull_request.labels) }}'
140→ fi
141→ affected=$(python3 ./.github/workflows/parse_service_labels.py "$labels")
142→
143→ echo "$affected"
144→ echo "affected=$affected" >> $GITHUB_OUTPUT
145→
146→ generate-job-version:
147→ runs-on: arc-runner-imprint
148→ needs: [check-label, compute-affected-services]
149→ if: >-
150→ ${{ contains(needs.compute-affected-services.outputs.affected_services, 'ledger') &&
151→ (github.event_name == 'push' || needs.check-label.outputs.should_run == 'true') }}
152→ outputs:
153→ job_version: ${{ steps.version.outputs.job_version }}
154→ steps:
155→ - name: Generate job version
156→ id: version
157→ run: |
158→ date_str=$(date '+%Y%m%d.%H%M')
159→ git_hash=$(echo $GITHUB_SHA | head -c 6)
160→ job_version="job.$date_str.$git_hash"
161→ echo "job_version=$job_version" >> $GITHUB_OUTPUT
162→
163→ upload-affected-services:
164→ needs: compute-affected-services
165→ runs-on: arc-runner-imprint
166→ if: ${{ github.event_name == 'push' }}
167→ permissions:
168→ id-token: write
169→ contents: read
170→ strategy:
171→ fail-fast: false
172→ matrix:
173→ env: [stg, sbx, preprd, prd]
174→
175→ steps:
176→ - name: Setup AWS Env
177→ id: aws_env
178→ run: |
179→ role_to_assume=${{ vars.STG_S3_UPLOAD_ROLE }}
180→ if [ ${{ matrix.env }} = 'prd' ]; then
181→ echo "using production aws role"
182→ role_to_assume=${{ vars.PRD_S3_UPLOAD_ROLE }}
183→ elif [ ${{ matrix.env }} = 'sbx' ]; then
184→ echo "using sandbox aws role"
185→ role_to_assume=${{ vars.SBX_S3_UPLOAD_ROLE }}
186→ elif [ ${{ matrix.env }} = 'preprd' ]; then
187→ echo "using pre-production aws role"
188→ role_to_assume=${{ vars.PREPRD_S3_UPLOAD_ROLE }}
189→ fi
190→
191→ echo "role_to_assume=$role_to_assume" >> $GITHUB_OUTPUT
192→
193→ - name: Configure AWS credentials
194→ uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
195→ with:
196→ role-to-assume: ${{ steps.aws_env.outputs.role_to_assume }}
197→ aws-region: us-east-1
198→
199→ - name: Upload commits
200→ run: |
201→ short_hash=${{ github.event.after }}
202→ short_hash=${short_hash:0:9}
203→ bucket="${{ matrix.env }}-git-commits-affected-services"
204→ if [ ${{ matrix.env }} = 'sbx' ]; then
205→ bucket="${bucket}-v2"
206→ fi
207→ echo "${{ needs.compute-affected-services.outputs.affected_services }}" | aws s3 cp - s3://${bucket}/${short_hash}.txt --sse AES256
208→
209→ upload-images:
210→ needs: [tagging-new-version, compute-affected-services, check-label]
211→ runs-on: arc-runner-imprint
212→ if: ${{ needs.compute-affected-services.outputs.affected_services != '[]' && needs.compute-affected-services.outputs.affected_services != '' }}
213→ permissions:
214→ id-token: write
215→ contents: read
216→ continue-on-error: ${{ github.event_name == 'workflow_dispatch' }}
217→ strategy:
218→ fail-fast: false
219→ matrix:
220→ service: ${{fromJSON(needs.compute-affected-services.outputs.affected_services)}}
221→ env: [stg, preprd, prd, sbx]
222→ region: [us-east-1]
223→ exclude:
224→ - region: us-west-2
225→ env: prd
226→ - service: avatar
227→ env: prd
228→
229→ steps:
230→ - name: Check if should run
231→ id: should_run
232→ run: |
233→ condition=${{
234→ (github.event_name == 'push' || (needs.check-label.outputs.should_run == 'true' && matrix.env == 'stg')) &&
235→ (matrix.region == 'us-east-1' || contains(env.SERVICES_WEST, matrix.service)) &&
236→ ((matrix.env != 'preprd' && matrix.env != 'sbx') ||
237→ (matrix.env == 'preprd' && contains(env.SERVICES_PREPRD, matrix.service)) ||
238→ (matrix.env == 'sbx' && contains(env.SERVICES_SBX, matrix.service)))
239→ }}
240→ echo $condition
241→ echo "condition=$condition" >> $GITHUB_OUTPUT
242→
243→ - name: Checkout code
244→ if: ${{ steps.should_run.outputs.condition == 'true' }}
245→ uses: actions/checkout@v4
246→
247→ - name: Go Build Cache
248→ if: ${{ steps.should_run.outputs.condition == 'true' }}
249→ uses: actions/cache@v4
250→ with:
251→ path: |
252→ ~/.cache/go-build
253→ key: ${{ runner.os}}-config-gen-${{ matrix.service }}-${{ hashFiles('**/go.sum') }}
254→ restore-keys: |
255→ ${{ runner.os }}-config-gen-${{ matrix.service }}-
256→
257→ - name: Verbolizing
258→ if: ${{ steps.should_run.outputs.condition == 'true' }}
259→ run: |
260→ echo "New Version:" ${{ needs.tagging-new-version.outputs.new_version }}
261→ echo "affected builds: ${{ needs.compute-affected-services.outputs.affected_services }}"
262→ echo "affected builds in JSON: ${{fromJSON(needs.compute-affected-services.outputs.affected_services)}}"
263→
264→ go version
265→
266→ - name: generate protobuf files
267→ if: ${{ steps.should_run.outputs.condition == 'true' }}
268→ env:
269→ BUF_TOKEN: ${{ secrets.BUF_TOKEN }}
270→ run: |
271→ # Retry buf generate command up to 3 times with exponential backoff
272→ max_attempts=3
273→ attempt=1
274→ while [ $attempt -le $max_attempts ]; do
275→ echo "Attempt $attempt of $max_attempts: Running buf generate..."
276→ if buf generate --timeout=10s; then
277→ echo "buf generate succeeded on attempt $attempt"
278→ break
279→ else
280→ exit_code=$?
281→ echo "buf generate failed on attempt $attempt with exit code $exit_code"
282→ if [ $attempt -eq $max_attempts ]; then
283→ echo "All $max_attempts attempts failed. Exiting with failure."
284→ exit $exit_code
285→ else
286→ # Exponential backoff: wait 2^attempt seconds
287→ wait_time=$((2 ** attempt))
288→ echo "Waiting $wait_time seconds before retry..."
289→ sleep $wait_time
290→ attempt=$((attempt + 1))
291→ fi
292→ fi
293→ done
294→
295→ - name: Generate config
296→ if: ${{ steps.should_run.outputs.condition == 'true' }}
297→ run: |
298→ go run scripts/genconfig/generate_config.go -service=${{ matrix.service }} -env=${{ matrix.env }} -region=${{ matrix.region }}
299→
300→ - name: Build individual service ${{ matrix.service }}
301→ if: ${{ steps.should_run.outputs.condition == 'true' }}
302→ working-directory: ${{ matrix.service }}
303→ run: |
304→ make build
305→
306→ - name: Setup Env
307→ if: ${{ steps.should_run.outputs.condition == 'true' }}
308→ id: setup_env
309→ run: |
310→ role_to_assume=${{ vars.STG_ECR_UPLOAD_ROLE }}
311→ docker_file_name="Dockerfile"
312→ if [ "${{ matrix.env }}" = 'preprd' ] || [ "${{ matrix.env }}" = 'sbx' ] || [ "${{ matrix.env }}" = 'prd' ] ; then
313→ docker_file_name="Dockerfile-${{ matrix.env }}"
314→ fi
315→
316→ if [ ${{ matrix.env }} = 'preprd' ]; then
317→ echo "using pre-production aws role"
318→ role_to_assume=${{ vars.PREPRD_ECR_UPLOAD_ROLE }}
319→ elif [ ${{ matrix.env }} = 'sbx' ]; then
320→ echo "using sandbox aws role"
321→ role_to_assume=${{ vars.SBX_ECR_UPLOAD_ROLE }}
322→ elif [ ${{ matrix.env }} = 'prd' ]; then
323→ echo "using production aws role"
324→ role_to_assume=${{ vars.PRD_ECR_UPLOAD_ROLE }}
325→ fi
326→
327→ tag_prefix="branch_build."
328→ if [ ${{ github.event_name }} = 'push' ]; then
329→ tag_prefix=""
330→ fi
331→
332→ echo "role_to_assume=$role_to_assume" >> $GITHUB_OUTPUT
333→ echo "docker_file_name=$docker_file_name" >> $GITHUB_OUTPUT
334→ echo "tag_prefix=$tag_prefix" >> $GITHUB_OUTPUT
335→
336→ - name: Configure AWS credentials
337→ if: ${{ steps.should_run.outputs.condition == 'true' }}
338→ uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
339→ with:
340→ role-to-assume: ${{ steps.setup_env.outputs.role_to_assume }}
341→ aws-region: us-east-1
342→
343→ - name: Login to Amazon ECR
344→ if: ${{ steps.should_run.outputs.condition == 'true' }}
345→ id: login_ecr
346→ uses: aws-actions/amazon-ecr-login@v2
347→
348→ - name: Build, tag, and push image to Amazon ECR for ${{ matrix.service }}
349→ if: ${{ steps.should_run.outputs.condition == 'true' }}
350→ id: build_docker
351→ env:
352→ ECR_REGISTRY: ${{ steps.login_ecr.outputs.registry }}
353→ working-directory: ${{ matrix.service }}
354→ run: |
355→ image_tag="${{ needs.tagging-new-version.outputs.new_version }}.${{ matrix.service }}"
356→ if [ ${{ matrix.region }} = 'us-west-2' ]; then
357→ image_tag="${{ needs.tagging-new-version.outputs.new_version }}.${{ matrix.service }}.${{ matrix.region }}"
358→ fi
359→
360→ ECR_REPOSITORY="${{ matrix.service }}"
361→ echo "Using ECR Repository: $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag"
362→
363→ cp -r ../.git ./.git
364→ docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag . -f ${{ steps.setup_env.outputs.docker_file_name }} --build-arg RUNNER_PASS=$dml_runner_key --build-arg DD_GIT_REPOSITORY_URL=github.com/${{github.repository}} --build-arg DD_GIT_COMMIT_SHA=${{ github.event.pull_request.head.sha || github.sha }}
365→ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$image_tag | tail -n1 > ${{ matrix.env }}_${{ matrix.service }}
366→
367→ - name: Upload output matrix
368→ if: ${{ steps.should_run.outputs.condition == 'true' }}
369→ uses: actions/upload-artifact@v4
370→ with:
371→ name: ${{ matrix.env }}_${{ matrix.service }}
372→ path: ${{ matrix.service }}/${{ matrix.env }}_${{ matrix.service }}
373→
374→ build-ledger-jobs:
375→ needs: [generate-job-version, check-label]
376→ if: ${{ needs.generate-job-version.outputs.job_version != '' }}
377→ runs-on: arc-runner-imprint
378→ permissions:
379→ id-token: write
380→ contents: read
381→ strategy:
382→ fail-fast: false
383→ matrix:
384→ job: [apply-late-payment-fee, create-statement-cycle, credit-statement-due, post-annual-fee, run-credit-replay]
385→ env: [stg, prd, sbx]
386→ env:
387→ JOB_MANIFEST_DIR: manifests
388→
389→ steps:
390→ - name: Check if should run
391→ id: should_run
392→ run: |
393→ # Branch builds only build stg, main builds all envs
394→ condition=${{ matrix.env == 'stg' || github.event_name == 'push' }}
395→ echo "condition=$condition" >> $GITHUB_OUTPUT
396→
397→ - name: Checkout repo
398→ if: ${{ steps.should_run.outputs.condition == 'true' }}
399→ uses: actions/checkout@v4
400→
401→ - name: Go Build Cache
402→ if: ${{ steps.should_run.outputs.condition == 'true' }}
403→ uses: actions/cache@v4
404→ with:
405→ path: |
406→ ~/.cache/go-build
407→ key: ${{ runner.os }}-config-gen-ledger-${{ hashFiles('**/go.sum') }}
408→ restore-keys: |
409→ ${{ runner.os }}-config-gen-ledger-
410→
411→ - name: generate protobuf files
412→ if: ${{ steps.should_run.outputs.condition == 'true' }}
413→ env:
414→ BUF_TOKEN: ${{ secrets.BUF_TOKEN }}
415→ run: buf generate
416→
417→ - name: Prepare Job manifest
418→ id: prepare
419→ if: ${{ steps.should_run.outputs.condition == 'true' }}
420→ run: |
421→ mkdir -p ${{ env.JOB_MANIFEST_DIR }}
422→
423→ job="${{ matrix.job }}"
424→ service="ledger"
425→ job_manifest_path=$(pwd)/${{ env.JOB_MANIFEST_DIR }}
426→
427→ echo "generating ledger config at $job_manifest_path"
428→
429→ go run scripts/genconfig/generate_config.go -service ledger -env ${{ matrix.env }} -region us-east-1
430→ cp ledger/*config.json ledger/jobs/$job
431→
432→ # if the job has its own config generator, generate the job config
433→ go run scripts/genconfig/generate_config.go -service $job -env ${{ matrix.env }} -region all
434→ cp *config.json ledger/jobs/$job 2>/dev/null || true
435→
436→ echo "preparing job $job, manifest dir at $job_manifest_path"
437→
438→ pushd .
439→ cd ledger/jobs/$job
440→
441→ chmod 755 ./prepare.sh
442→ ./prepare.sh $job_manifest_path ${{ matrix.env }}
443→
444→ popd
445→
446→ ls -lah ${{ env.JOB_MANIFEST_DIR }}
447→
448→ tag=$(echo ${{ needs.generate-job-version.outputs.job_version }}.$job | head -c 55 | sed -E 's/[^a-zA-Z0-9]$//')
449→
450→ echo "job=$job" >> $GITHUB_OUTPUT
451→ echo "tag=$tag" >> $GITHUB_OUTPUT
452→
453→ - name: Setup AWS Env
454→ if: ${{ steps.should_run.outputs.condition == 'true' }}
455→ id: aws_env
456→ run: |
457→ role_to_assume=${{ vars.STG_ECR_UPLOAD_ROLE }}
458→ if [ ${{ matrix.env }} = 'prd' ]; then
459→ echo "using production aws role"
460→ role_to_assume=${{ vars.PRD_ECR_UPLOAD_ROLE }}
461→ elif [ ${{ matrix.env }} = 'sbx' ]; then
462→ echo "using sandbox aws role"
463→ role_to_assume=${{ vars.SBX_ECR_UPLOAD_ROLE }}
464→ fi
465→ echo "role_to_assume=$role_to_assume" >> $GITHUB_OUTPUT
466→
467→ - name: Configure AWS credentials
468→ if: ${{ steps.should_run.outputs.condition == 'true' }}
469→ uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
470→ with:
471→ role-to-assume: ${{ steps.aws_env.outputs.role_to_assume }}
472→ aws-region: us-east-1
473→
474→ - name: Login to Amazon ECR
475→ id: login-ecr
476→ if: ${{ steps.should_run.outputs.condition == 'true' }}
477→ uses: aws-actions/amazon-ecr-login@v2
478→
479→ - name: Build and push image to Amazon ECR
480→ id: build_push
481→ if: ${{ steps.should_run.outputs.condition == 'true' }}
482→ env:
483→ ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
484→ IMAGE_TAG: "${{ steps.prepare.outputs.tag }}"
485→ working-directory: ${{ env.JOB_MANIFEST_DIR }}
486→ run: |
487→ ECR_REPOSITORY="ledger"
488→
489→ docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
490→ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
491→
492→ echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
493→
494→ # Make it visible in the run UI
495→ echo "::notice title=Job Image Built::${{ matrix.env }} • ledger • $IMAGE_TAG"
496→
497→ - name: Publish build summary
498→ if: ${{ steps.should_run.outputs.condition == 'true' }}
499→ run: |
500→ {
501→ echo "## :whale: Job Image Built"
502→ echo ""
503→ echo "- Env: \`${{ matrix.env }}\`"
504→ echo "- Service: \`ledger\`"
505→ echo "- Job: \`${{ steps.prepare.outputs.job }}\`"
506→ echo "- Tag: \`${{ steps.build_push.outputs.image_tag }}\`"
507→ } >> "$GITHUB_STEP_SUMMARY"
508→
509→ update-helm-values:
510→ name: Update Helm Values for All Services
511→ runs-on: arc-runner-imprint
512→ if: ${{ always() && github.event_name == 'push' && github.ref == 'refs/heads/main' && (needs.upload-images.result == 'success' || needs.build-ledger-jobs.result == 'success') }}
513→ needs: [upload-images, generate-job-version, build-ledger-jobs]
514→ environment: privilege-access
515→ strategy:
516→ fail-fast: false
517→ matrix:
518→ repo: [charts]
519→ steps:
520→ - name: Get Helm Chart Auto Update App Token
521→ id: app-token
522→ uses: actions/create-github-app-token@v1
523→ with:
524→ app-id: ${{ vars.HELM_CHART_AUTO_UPDATE_APP_ID }}
525→ private-key: ${{ secrets.HELM_CHART_AUTO_UPDATE_APP_PRIVATE_KEY }}
526→ repositories: ${{ matrix.repo }}
527→ owner: ${{ github.repository_owner }}
528→
529→ - name: Get GitHub App User ID
530→ id: get-user-id
531→ run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
532→ env:
533→ GH_TOKEN: ${{ steps.app-token.outputs.token }}
534→
535→ - name: Checkout Repository
536→ uses: actions/checkout@v4
537→ with:
538→ repository: ${{ github.repository_owner }}/${{ matrix.repo }}
539→ fetch-depth: 0
540→ token: ${{ steps.app-token.outputs.token }}
541→ ref: main
542→ persist-credentials: false
543→
544→ - name: Download All Artifacts
545→ uses: actions/download-artifact@v4
546→ with:
547→ path: artifacts
548→
549→ - name: Update values.yaml for All Services
550→ id: update-values
551→ run: |
552→ update=false
553→ for artifact_file in artifacts/*; do
554→ ARTIFACT_NAME=$(basename "$artifact_file")
555→
556→ echo "This is the artifact name: $ARTIFACT_NAME"
557→
558→ # Extract env and service name from artifact name
559→ ENV=$(echo $ARTIFACT_NAME | cut -d'_' -f1)
560→ SERVICE=$(echo $ARTIFACT_NAME | cut -d'_' -f2)
561→
562→ echo "This is the env and service: $ENV $SERVICE"
563→
564→ if [[ "$ENV" != "stg" && "$SERVICE" != "mirror" ]]; then
565→ echo "Skipping $SERVICE in $ENV environment."
566→ continue
567→ fi
568→
569→ # Read the image tag from the file inside the artifact
570→ OUTPUT=$(cat "artifacts/$ARTIFACT_NAME/$ARTIFACT_NAME")
571→ echo "This is the OUTPUT $OUTPUT"
572→ IMAGE_TAG=$(echo "$OUTPUT" | cut -d':' -f1)
573→
574→ VALUES_FILE="$SERVICE/chart/$ENV/values.yaml"
575→ if [ -f "$VALUES_FILE" ]; then
576→ sed -i "s|image_tag: .*|image_tag: $IMAGE_TAG|g" "$VALUES_FILE"
577→ update=true
578→ else
579→ echo "Warning: $VALUES_FILE not found, skipping..."
580→ fi
581→ done
582→
583→ # Process ledger job versions
584→ job_version="${{ needs.generate-job-version.outputs.job_version }}"
585→ if [ -n "$job_version" ]; then
586→ echo "Processing ledger job versions with base version: $job_version"
587→ for env_name in stg prd sbx; do
588→ VALUES_FILE="ledger/chart/$env_name/values.yaml"
589→ if [ -f "$VALUES_FILE" ]; then
590→ for job in apply-late-payment-fee create-statement-cycle credit-statement-due post-annual-fee run-credit-replay; do
591→ var_name=$(echo "$job" | tr '-' '_')_version
592→ full_tag="$job_version.$job"
593→ if grep -q "^${var_name}:" "$VALUES_FILE"; then
594→ echo "Updating $var_name to $full_tag in $VALUES_FILE"
595→ sed -i "s|^${var_name}:.*|${var_name}: $full_tag|g" "$VALUES_FILE"
596→ update=true
597→ fi
598→ done
599→ else
600→ echo "Warning: $VALUES_FILE not found, skipping job version updates..."
601→ fi
602→ done
603→ fi
604→
605→ echo "update=$update" >> $GITHUB_OUTPUT
606→
607→ - name: Commit and Push Changes
608→ if: steps.update-values.outputs.update == 'true'
609→ run: |
610→ git config --global user.name "${{ steps.app-token.outputs.app-slug }}[bot]"
611→ git config --global user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
612→ git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository_owner }}/${{ matrix.repo }}
613→
614→ git add */chart/*/values.yaml
615→ git commit -m "Update image tags for service Helm charts"
616→
617→ git pull --rebase origin main
618→ git push origin main || git push --force origin main
619→
620→ comment-pr:
621→ needs:
622→ [
623→ upload-images,
624→ tagging-new-version,
625→ compute-affected-services,
626→ check-label,
627→ ]
628→ runs-on: arc-runner-imprint
629→ strategy:
630→ matrix:
631→ service: ${{fromJSON(needs.compute-affected-services.outputs.affected_services)}}
632→ env: [stg, preprd, prd, sbx]
633→ region: [us-east-1]
634→ exclude:
635→ - region: us-west-2
636→ env: prd
637→ - service: avatar
638→ env: prd
639→
640→ steps:
641→ - name: Check if should run
642→ id: should_run
643→ run: |
644→ condition=${{
645→ (github.event_name == 'push' || (needs.check-label.outputs.should_run == 'true' && matrix.env == 'stg')) &&
646→ (matrix.region == 'us-east-1' || contains(env.SERVICES_WEST, matrix.service)) &&
647→ ((matrix.env != 'preprd' && matrix.env != 'sbx') ||
648→ (matrix.env == 'preprd' && contains(env.SERVICES_PREPRD, matrix.service)) ||
649→ (matrix.env == 'sbx' && contains(env.SERVICES_SBX, matrix.service)))
650→ }}
651→ echo $condition
652→ echo "condition=$condition" >> $GITHUB_OUTPUT
653→
654→ - name: Download math result for job 1
655→ if: ${{ steps.should_run.outputs.condition == 'true' }}
656→ uses: actions/download-artifact@v4
657→ with:
658→ name: ${{ matrix.env }}_${{ matrix.service }}
659→
660→ - name: Retrieve from file
661→ id: retrieve_output
662→ if: ${{ steps.should_run.outputs.condition == 'true' }}
663→ run: |
664→ message=$(cat ${{ matrix.env }}_${{ matrix.service }})
665→
666→ echo $message
667→ echo "message=$message" >> $GITHUB_OUTPUT
668→
669→ - name: Comment PR
670→ if: ${{ steps.should_run.outputs.condition == 'true' }}
671→ uses: thollander/actions-comment-pull-request@v1
672→ with:
673→ pr_number: ${{ needs.tagging-new-version.outputs.pr_num }}
674→ message: "🟢 Created build for **${{ matrix.env }}-${{ matrix.service }}**: ${{ steps.retrieve_output.outputs.message }}"
675→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Implementation complete. Here's a summary of the changes made to .github/workflows/push_ecr.yml:
LEDGER_TRACKED_JOBS Environment Variable (line 29)LEDGER_TRACKED_JOBS: "apply-late-payment-fee,create-statement-cycle,credit-statement-due,post-annual-fee,run-credit-replay"
generate-job-version Job (lines 146-161)job.YYYYMMDD.HHMM.{git_hash}job_version for downstream jobsbuild-ledger-jobs Job (lines 374-507)job_runner_build.yml:prepare.sh scriptledger ECR repositoryupdate-helm-values Job (lines 509-618)needs to include generate-job-version and build-ledger-jobsalways() so it runs if either service or job builds succeed{job_name}_version variables in ledger/chart/{env}/values.yamlapply_late_payment_fee_version: job.20260109.1500.abc123.apply-late-payment-feepush_ecr.yml
├── check-label
├── tagging-new-version
├── compute-affected-services
├── generate-job-version (parallel)
├── upload-images (builds ledger service)
├── build-ledger-jobs (builds 5 jobs × 3 envs = 15 builds)
└── update-helm-values
├── Updates service image_tag
└── Updates 5 job versions per env