Deploy sheet_sync as k8s CronJob (hourly) #439

Open
opened 2026-04-10 23:31:39 +00:00 by forgejo_admin · 0 comments
Contributor

Type

Feature

Lineage

Standalone — spawned from the westside-sheet-sync project scaffold on 2026-04-10. Depends on the sheet_sync service module being merged first.

Repo

forgejo_admin/pal-e-deployments

User Story

As ops
I want the sheet_sync service to run automatically every hour
So that Marcus's Google Sheet stays current without anyone having to trigger the sync manually

Ties to story:sheet-sync.

Context

The sheet_sync service module lives inside basketball-api but is not wired to any HTTP route. It's meant to be invoked by a scheduled job. The pal-e platform pattern is to deploy scheduled jobs as k8s CronJobs in the service's namespace, with the image being the same as the main service deployment.

The CronJob spec runs python -m basketball_api.jobs.sheet_sync (a new thin entrypoint module that imports sync_jersey_orders and calls it with a SessionLocal). Exit code 0 = success, non-zero = failure. Logs go to stdout, picked up by Promtail → Loki.

The CronJob needs the same PGURL as the main basketball-api deployment, plus access to the new Google Sheets write token (mounted from a k8s secret, which is a separate concern this ticket creates).

File Targets

Files to create:

  • bases/basketball-api/cronjob-sheet-sync.yaml — CronJob manifest. Schedule: 0 * * * * (top of every hour). Uses the same image as the basketball-api Deployment, same env vars, one extra mount for the sheets write token.
  • overlays/basketball-api/secret-sheets-token.enc.yaml — SOPS-encrypted secret containing the contents of ~/secrets/google-oauth/sheets-westsidebasketball-write.json.

Files to modify:

  • bases/basketball-api/kustomization.yaml — add the new CronJob to the resources list.
  • overlays/basketball-api/kustomization.yaml — add the new secret to the resources list.
  • src/basketball_api/jobs/sheet_sync.py (in basketball-api repo, not pal-e-deployments) — the new thin entrypoint. ~15 lines: import sessionmaker, call sync_jersey_orders, print result as JSON.

Files NOT to touch:

  • Other service CronJobs, Deployments, Services — strict isolation.
  • Any existing basketball-api secret.

Acceptance Criteria

  • When kustomize build overlays/basketball-api/ runs, then the output is a valid manifest list including the new CronJob and secret.
  • When ArgoCD syncs the overlay, then the CronJob sheet-sync appears in the basketball-api namespace.
  • When I run kubectl -n basketball-api create job --from=cronjob/sheet-sync sheet-sync-manual and tail the logs, then the job runs to completion with exit code 0.
  • When the job runs hourly on schedule, then new paid jersey orders appear in Marcus's sheet within 1 hour of payment.
  • When the job fails (e.g., bad credentials), then the pod exits non-zero and the failure is visible in kubectl get jobs -n basketball-api.
  • When I SOPS-decrypt overlays/basketball-api/secret-sheets-token.enc.yaml, then the content matches ~/secrets/google-oauth/sheets-westsidebasketball-write.json.

Test Expectations

  • Kustomize build test: kustomize build overlays/basketball-api/ | kubectl apply --dry-run=server -f - succeeds.
  • SOPS decrypt test: sops -d overlays/basketball-api/secret-sheets-token.enc.yaml returns valid JSON.
  • Manual trigger test: kubectl -n basketball-api create job --from=cronjob/sheet-sync sheet-sync-manual-$(date +%s) → job completes with exit 0.
  • Schedule verification: kubectl get cronjob -n basketball-api sheet-sync -o jsonpath='{.spec.schedule}' returns 0 * * * *.
  • Run command: kustomize build overlays/basketball-api/ > /tmp/build.yaml && kubectl apply --dry-run=server -f /tmp/build.yaml

Constraints

  • CronJob schedule: 0 * * * * (hourly on the hour). Do NOT run more often than hourly without explicit justification — Google Sheets API has quotas.
  • successfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 3 (avoid flooding job history).
  • concurrencyPolicy: Forbid (never run two sheet_sync jobs concurrently).
  • Resource requests: 50m CPU / 128Mi memory. Limits: 200m / 256Mi. It's a short-lived sync job.
  • The secret must be SOPS-encrypted, never inline plaintext.
  • Use the same imagePullSecret as the basketball-api Deployment.
  • Do NOT add any new imagePullSecrets or service accounts.

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • westside-sheet-sync — project
  • story-westside-jersey-sheet-sync — user story
  • Blocks on: sheet_sync service module ticket, OAuth scope upgrade ticket
  • Blocks: sync failure alerts ticket (can't alert on failures of a job that doesn't exist yet)
### Type Feature ### Lineage Standalone — spawned from the westside-sheet-sync project scaffold on 2026-04-10. Depends on the sheet_sync service module being merged first. ### Repo `forgejo_admin/pal-e-deployments` ### User Story As ops I want the sheet_sync service to run automatically every hour So that Marcus's Google Sheet stays current without anyone having to trigger the sync manually Ties to `story:sheet-sync`. ### Context The sheet_sync service module lives inside basketball-api but is not wired to any HTTP route. It's meant to be invoked by a scheduled job. The pal-e platform pattern is to deploy scheduled jobs as k8s CronJobs in the service's namespace, with the image being the same as the main service deployment. The CronJob spec runs `python -m basketball_api.jobs.sheet_sync` (a new thin entrypoint module that imports `sync_jersey_orders` and calls it with a SessionLocal). Exit code 0 = success, non-zero = failure. Logs go to stdout, picked up by Promtail → Loki. The CronJob needs the same `PGURL` as the main basketball-api deployment, plus access to the new Google Sheets write token (mounted from a k8s secret, which is a separate concern this ticket creates). ### File Targets Files to create: - `bases/basketball-api/cronjob-sheet-sync.yaml` — CronJob manifest. Schedule: `0 * * * *` (top of every hour). Uses the same image as the basketball-api Deployment, same env vars, one extra mount for the sheets write token. - `overlays/basketball-api/secret-sheets-token.enc.yaml` — SOPS-encrypted secret containing the contents of `~/secrets/google-oauth/sheets-westsidebasketball-write.json`. Files to modify: - `bases/basketball-api/kustomization.yaml` — add the new CronJob to the resources list. - `overlays/basketball-api/kustomization.yaml` — add the new secret to the resources list. - `src/basketball_api/jobs/sheet_sync.py` (in basketball-api repo, not pal-e-deployments) — the new thin entrypoint. ~15 lines: import sessionmaker, call sync_jersey_orders, print result as JSON. Files NOT to touch: - Other service CronJobs, Deployments, Services — strict isolation. - Any existing basketball-api secret. ### Acceptance Criteria - [ ] When `kustomize build overlays/basketball-api/` runs, then the output is a valid manifest list including the new CronJob and secret. - [ ] When ArgoCD syncs the overlay, then the CronJob `sheet-sync` appears in the `basketball-api` namespace. - [ ] When I run `kubectl -n basketball-api create job --from=cronjob/sheet-sync sheet-sync-manual` and tail the logs, then the job runs to completion with exit code 0. - [ ] When the job runs hourly on schedule, then new paid jersey orders appear in Marcus's sheet within 1 hour of payment. - [ ] When the job fails (e.g., bad credentials), then the pod exits non-zero and the failure is visible in `kubectl get jobs -n basketball-api`. - [ ] When I SOPS-decrypt `overlays/basketball-api/secret-sheets-token.enc.yaml`, then the content matches `~/secrets/google-oauth/sheets-westsidebasketball-write.json`. ### Test Expectations - [ ] Kustomize build test: `kustomize build overlays/basketball-api/ | kubectl apply --dry-run=server -f -` succeeds. - [ ] SOPS decrypt test: `sops -d overlays/basketball-api/secret-sheets-token.enc.yaml` returns valid JSON. - [ ] Manual trigger test: `kubectl -n basketball-api create job --from=cronjob/sheet-sync sheet-sync-manual-$(date +%s)` → job completes with exit 0. - [ ] Schedule verification: `kubectl get cronjob -n basketball-api sheet-sync -o jsonpath='{.spec.schedule}'` returns `0 * * * *`. - Run command: `kustomize build overlays/basketball-api/ > /tmp/build.yaml && kubectl apply --dry-run=server -f /tmp/build.yaml` ### Constraints - CronJob schedule: `0 * * * *` (hourly on the hour). Do NOT run more often than hourly without explicit justification — Google Sheets API has quotas. - `successfulJobsHistoryLimit: 3`, `failedJobsHistoryLimit: 3` (avoid flooding job history). - `concurrencyPolicy: Forbid` (never run two sheet_sync jobs concurrently). - Resource requests: 50m CPU / 128Mi memory. Limits: 200m / 256Mi. It's a short-lived sync job. - The secret must be SOPS-encrypted, never inline plaintext. - Use the same `imagePullSecret` as the basketball-api Deployment. - Do NOT add any new imagePullSecrets or service accounts. ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `westside-sheet-sync` — project - `story-westside-jersey-sheet-sync` — user story - Blocks on: sheet_sync service module ticket, OAuth scope upgrade ticket - Blocks: sync failure alerts ticket (can't alert on failures of a job that doesn't exist yet)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/basketball-api#439
No description provided.