{"id":41586175,"url":"https://github.com/wiremind/chartreuse","last_synced_at":"2026-02-17T17:03:10.766Z","repository":{"id":45956802,"uuid":"413362182","full_name":"wiremind/chartreuse","owner":"wiremind","description":"Chartreuse: Automated Alembic migrations within kubernetes","archived":false,"fork":false,"pushed_at":"2026-01-16T19:48:25.000Z","size":2206,"stargazers_count":19,"open_issues_count":5,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-01-24T18:04:47.446Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wiremind.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-10-04T09:47:02.000Z","updated_at":"2025-10-15T14:19:31.000Z","dependencies_parsed_at":"2023-01-25T12:30:21.851Z","dependency_job_id":"bf100387-f9cd-43e9-93f8-35899466c3b9","html_url":"https://github.com/wiremind/chartreuse","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/wiremind/chartreuse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiremind%2Fchartreuse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiremind%2Fchartreuse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiremind%2Fchartreuse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiremind%2Fchartreuse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wiremind","download_url":"https://codeload.github.com/wiremind/chartreuse/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiremind%2Fchartreuse/sbom","scorecard":{"id":1238612,"data":{"date":"2025-07-21","repo":{"name":"github.com/wiremind/chartreuse","commit":"0e605e2df51cb84cbfc6111d9d67a8620f05489a"},"scorecard":{"version":"v5.2.1-24-gc29a04d4","commit":"c29a04d46d1570393e94662bc34e9906398e1bfa"},"score":5.2,"checks":[{"name":"Maintained","score":2,"reason":"3 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":3,"reason":"Found 4/13 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#dangerous-workflow"}},{"name":"Security-Policy","score":9,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Warn: One or no descriptive hints of disclosure, vulnerability, and/or timelines in security policy","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#security-policy"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql-analysis.yml:16","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql-analysis.yml:17","Warn: no topLevel permission defined: .github/workflows/codeql-analysis.yml:1","Warn: no topLevel permission defined: .github/workflows/commitlint.yml:1","Warn: no topLevel permission defined: .github/workflows/python-publish.yml:1","Warn: no topLevel permission defined: .github/workflows/python-test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENCE.md:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#license"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/python-publish.yml:8"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#packaging"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#branch-protection"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/codeql-analysis.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/codeql-analysis.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/codeql-analysis.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/commitlint.yml:8: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/commitlint.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/commitlint.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/commitlint.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-publish.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/python-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-publish.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/python-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-test.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/python-test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-test.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/python-test.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/python-test.yml:50: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/python-test.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/python-test.yml:55: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/python-test.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/python-test.yml:65: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/python-test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-test.yml:74: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/python-test.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/python-test.yml:75: update your workflow using https://app.stepsecurity.io/secureworkflow/wiremind/chartreuse/python-test.yml/main?enable=pin","Warn: containerImage not pinned by hash: example/Dockerfile:1: pin your Docker image by updating python:3.12 to python:3.12@sha256:645df645815f1403566b103b2a2bb07f6a01516bbb15078ed004e41d198ba194","Warn: containerImage not pinned by hash: example/Dockerfile-dev:3: pin your Docker image by updating python:3.12 to python:3.12@sha256:645df645815f1403566b103b2a2bb07f6a01516bbb15078ed004e41d198ba194","Warn: pipCommand not pinned by hash: example/Dockerfile:5","Warn: pipCommand not pinned by hash: example/Dockerfile-dev:8","Warn: pipCommand not pinned by hash: example/Dockerfile-dev:11","Warn: pipCommand not pinned by hash: .github/workflows/python-publish.yml:19","Warn: pipCommand not pinned by hash: .github/workflows/python-publish.yml:20","Warn: pipCommand not pinned by hash: .github/workflows/python-test.yml:33","Warn: pipCommand not pinned by hash: .github/workflows/python-test.yml:34","Warn: pipCommand not pinned by hash: .github/workflows/python-test.yml:35","Info:   0 out of   9 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   6 third-party GitHubAction dependencies pinned","Info:   0 out of   2 containerImage dependencies pinned","Info:   0 out of   8 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#pinned-dependencies"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":7,"reason":"SAST tool detected but not run on all commits","details":["Info: SAST configuration detected: CodeQL","Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/c29a04d46d1570393e94662bc34e9906398e1bfa/docs/checks.md#sast"}}]},"last_synced_at":"2025-10-07T15:21:54.016Z","repository_id":45956802,"created_at":"2025-10-07T15:21:54.017Z","updated_at":"2025-10-07T15:21:54.017Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29461909,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T22:42:09.113Z","status":"ssl_error","status_checked_at":"2026-02-14T22:42:05.053Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-24T09:06:22.709Z","updated_at":"2026-02-17T17:03:10.727Z","avatar_url":"https://github.com/wiremind.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Chartreuse: Automated Alembic SQL schema migrations within kubernetes\n\n**\"How to automate management of Alembic database schema migration at scale using CI/CD and Kubernetes\"**\n\nChartreuse is a wrapper around [Alembic](https://alembic.sqlalchemy.org) to ease,\ndetect and automate migrations on deployed Python applications.\n\nChartreuse leverages [Helm Hooks](https://helm.sh/docs/topics/charts_hooks/), the Hooks are defined in [Chartreuse Helm Chart](https://github.com/wiremind/wiremind-helm-charts/tree/main/charts/chartreuse).\n\n## Usage\n\n### Requirements\n\n- Python \u003e= 3.7\n- Using Helm to deploy you application\n- This Python package requires the `expecteddeploymentscales.wiremind.io` Kubernetes `Custom Resource Definition`:\n\n```bash\nhelm repo add wiremind https://wiremind.github.io/wiremind-helm-charts\nhelm install wiremind-crds wiremind/wiremind-crds --version 0.1.0\n```\n\n- Please make sure Chartreuse Python **Package version** and Chartreuse **Helm Chart version**, you use, share `major.minor` otherwise Chartreuse won't start.\n\n## Configuration\n\n### Using Helm\n\nChartreuse comes with a Helm Chart ready to be used as a Helm Subchart in your own Helm Chart.\n\nAll you have to do is build your own container image containing:\n\n- Chartreuse Python package\n- Your Alembic migrations in an `alembic` directory\n- All required dependencies to run your alembic migrations.\n\nUsually, it will be the same container image for your project with your code as usual, with Chartreuse added as dependency in your setup.py.\n\nand state in the [Chartreuse Helm Chart](https://github.com/wiremind/wiremind-helm-charts/tree/main/charts/chartreuse) values.yaml:\n\n- the image repository and tag\n- URL to connect to your PostgreSQL\n\nDuring install and/or upgrade of your Helm Release, Chartreuse will run as Kubernetes Job and automatically migrate PostgreSQL shchema to `HEAD` if needed.\n\nIf required, it will also scale down Deployments that should NOT run during a Deployment using ExpectedDeploymentScale CRD.\n\nPlease refer to the [example](example) directory for example.\n\n#### Diagram\n\nThe state diagram of your application while upgrading using Helm and using Chartreuse for your migrations is as follows:\n\n![alt text](doc/chartreuse_sd.png)\n\n## Notes\n\n1. PG clusters managed by postgres-operator (Patroni PG):\n    - When Chartreuse starts running against a PG cluster managed by `postgres-operator` (Patroni PG), it may run the migrations before that the cluster is configured, and by configured we mean:\n      - the Roles, especially `wiremind_owner_user` and `wiremind_owner` used by Chartreuse and Alembic, are created.\n      - The [default privileges](https://www.postgresql.org/docs/12/sql-alterdefaultprivileges.html) are set, so the other Roles, like `wiremind_writer_user` used by the application, can interact with the created objects.\n    To ensure that, Chartreuse will not start until `postgres-operator` has performed the above two actions. To make Chartreuse wait, the environment variable `CHARTREUSE_PATRONI_POSTGRESQL` should be set:\n      ```yaml\n      # in the appropriate Helm values file\n      chartreuse-for-a-patroni-pg:\n        additionalEnvironmentVariables:\n          CHARTREUSE_PATRONI_POSTGRESQL: \"anything\"\n          CHARTREUSE_ALEMBIC_POSTGRES_WAIT_CONFIGURED_TIMEOUT: 100 # It's set to 60s by default\n      ```\n    - The default privileges above-mentioned are set for the NOLOGIN owner `wiremind_owner`, e.g. tables should be created by `wiremind_owner` so `wiremind_writer[_user]` can insert to them. This is why we need to `SET ROLE wiremind_owner` in the beginning of the transaction before running the migrations, Chartreuse does set `-x patroni_postgresql=yes` to `alembic upgrade head` when the environment variable `PATRONI_POSTGRESQL` is set, you can then retrieve the argument and set the role in your `env.py`:\n      ```yaml\n      ...\n      patroni_postgresql: bool = \"patroni_postgresql\" in context.get_x_argument(as_dictionary=True)\n        ...\n        with connectable.connect() as connection:\n          ...\n          with context.begin_transaction():\n            if patroni_postgresql:\n              context.execute(text(\"SET ROLE wiremind_owner\"))\n            context.run_migrations()\n       ...\n      ```\n2. Chartreuse in pre-upgrade mode:\n    - When running Chartreuse in pre-upgrade mode (`upgradeBeforeDeployment: true`), it will not start running (The Chartreuse Pod will hang in `Init` state) until one PG Pod (and ES Pod if ES is used) is running, so make sure these Pods are available to Chartreuse. To fix that:\n      - You will need to delete the Chartreuse Job so the upgrade can resume and fix you PG and ES pods (or create them if they don't exist), then you can redeploy so your migrations can run.\n      - You can also try the `upgradeBeforeDeployment: false` mode (maybe temporarily).\n\n## Development\n\n### Test\n\nThere are three kind of tests:\n\n- Unit tests\n- Integration tests: allows to run in a real environment, but still control chartreuse from the inside\n- blackbox test: deploy a real Helm Release and test if databases are migrated.\n\n### Documentation\n\n- The diagram has been drawn using the free online software https://draw.io, the\nsource code is located at `doc/chartreuse_sd.xml`, feel free\nto correct it or make it more understandable.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwiremind%2Fchartreuse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwiremind%2Fchartreuse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwiremind%2Fchartreuse/lists"}