Skip to content

breakingthebot/shift-closer

Repository files navigation

Shift Closer

CI

Python Google Cloud Cloud Run Functions Cloud Scheduler Secret Manager License: MIT

A scheduled Cloud Run Function that logs out of your work platforms and emails you an end-of-shift summary, automatically, at shift end.

Stack

  • Python 3.12
  • functions-framework (Cloud Run Functions / Cloud Functions, 2nd gen, HTTP-triggered)
  • google-cloud-secret-manager for credentials (no .env secrets)
  • Cloud Scheduler (invokes the function on a cron schedule via OIDC auth)
  • smtplib (stdlib) for sending the summary email over SMTP

Setup

  1. Install Python 3.11+ and create a virtual environment:
    python -m venv venv
    venv\Scripts\activate      # Windows
    source venv/bin/activate   # Mac/Linux
  2. Install the project in editable mode with dev dependencies:
    pip install -e ".[dev]"
  3. Copy .env.example to .env and fill in the non-secret values (see below — there's exactly one credential this app needs, and it does not go in .env).

Environment Variables

See .env.example for the full list with comments. Summary:

  • GCP_PROJECT_ID — the project holding the Secret Manager secret.
  • PLATFORM_NAMES — comma-separated list of platforms to log out of.
  • SUMMARY_EMAIL_TO — where the summary email goes.
  • SMTP_USERNAME — the Gmail address the summary is sent from (not secret, just an address).
  • SMTP_HOST / SMTP_PORT — optional, default to Gmail's SMTP settings.
  • CREDENTIALS_SECRET_ID — the name of the shared Secret Manager secret holding this and other builds' credentials as one JSON blob (see API Keymaster). Defaults to app-credentials. The password itself is never an environment variable or a file on disk — see Data Handling below.
  • SMTP_CREDENTIAL_KEY — which key inside that blob holds this build's Gmail App Password. Defaults to smtp_password.

Running Locally

pip install -e ".[dev]"
pytest -v

To boot the function locally (it will try to reach real Secret Manager and SMTP, so this needs valid gcloud credentials and a real secret to fully succeed):

functions-framework --target=shift_closer --port=8080

Deployed

Not deployed yet. deploy/deploy.sh deploys the function, sets up a dedicated invoker service account, and creates the Cloud Scheduler job, in one script. It expects the shared app-credentials secret to already exist with a smtp_password key set (created via API Keymaster's deploy script or manually) and will fail fast with a clear message if it doesn't. Review the variables at the top of that file (project ID, region, schedule, timezone) before running it.

Data Handling

  • The only credential this app uses is a Gmail App Password, read from the smtp_password key of the shared app-credentials Secret Manager secret — never in a file, environment variable dump, or log line. That secret also holds credentials for other builds in this series; this app only ever reads its own key out of it.
  • The Cloud Function's own runtime service account is granted read-only access to that one shared secret (roles/secretmanager.secretAccessor), nothing broader.
  • "Logging out of platforms" is currently mocked (no real platform is targeted yet) — no platform credentials are collected, stored, or transmitted by this app at all.
  • The summary email contains only: shift end time, and each platform's name + logout success/failure. No personal data beyond the recipient's own email address (which the user configures) is ever included.
  • All logs are structured JSON via Cloud Logging; secret values are never logged, only secret IDs (names) when fetching them.

Architecture Notes

The function is one HTTP-triggered Cloud Run Function (shift_closer), invoked by Cloud Scheduler on a cron trigger. On each run it: logs out of every configured platform (currently mocked — see Notes), fetches the SMTP app password from Secret Manager, builds a plain-text summary, and sends it over SMTP.

  • src/shift_closer/config.py reads and validates all non-secret configuration from environment variables. Fails fast with a clear error if anything required is missing.
  • src/shift_closer/secrets.py is the only module that talks to Secret Manager. get_secret() fetches a raw secret's plaintext; get_credential() fetches the shared app-credentials JSON blob and pulls out this build's one key (smtp_password by default). Both take a Secret Manager client as an optional injectable parameter specifically so tests never need real GCP credentials.
  • src/shift_closer/platform_client.py currently mocks the logout call — there's no real target platform yet (this build started as a "could I build this at all" exploration, not tied to an actual job). The LogoutResult shape and logout_from_all_platforms()'s per-platform failure isolation are written so swapping in a real HTTP call per platform is a contained change to one function's body, not a redesign.
  • src/shift_closer/email_service.py builds the summary text and sends it via stdlib smtplib — no third-party email SDK, to keep the dependency footprint minimal for a single scheduled email.
  • src/shift_closer/main.py orchestrates the above and exposes the functions_framework.http-decorated shift_closer entry point.
  • main.py (repo root) is a thin shim: Google's Python Functions buildpack requires main.py at the deployment source root, but this project keeps a proper src//tests/ layout, so the root file just adds src/ to the import path and re-exports the real function.
  • deploy/deploy.sh documents (and automates, once run) the exact gcloud sequence: enable APIs, create the secret, deploy the function, create a least-privilege invoker service account, grant it invoke access, and create the Scheduler job with OIDC auth (not a public/unauthenticated endpoint).

Notes

  • This is a practice/portfolio build, not tied to a real job. There's no real "work platform" to log out of yet, so platform_client.py is intentionally mocked. The architecture (config → secrets → platform calls → email) is real and deployable; only the platform integration itself is a stand-in.
  • Not deployed yet. Run deploy/deploy.sh once the target project is ready.
  • Schedule is a placeholder. deploy/deploy.sh defaults to 0 4 * * * (4:00 AM daily, America/New_York) — adjust SCHEDULE/TIME_ZONE in that file to match the actual shift end time before deploying.
  • AGENTS.md, BUILD_NOTES.md, and .claude/ are intentionally excluded from this repo (see .gitignore) — local build-process files, not part of the shipped function.

About

Scheduled Cloud Run Function that logs out of work platforms and emails an end-of-shift summary. Credentials via Secret Manager, not .env.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors