senzu 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- senzu-0.2.0/.github/workflows/ci.yml +17 -0
- senzu-0.2.0/.github/workflows/publish.yml +40 -0
- senzu-0.2.0/.github/workflows/release.yml +17 -0
- senzu-0.2.0/.gitignore +210 -0
- senzu-0.2.0/CHANGELOG.md +44 -0
- senzu-0.2.0/LICENSE +21 -0
- senzu-0.2.0/PKG-INFO +273 -0
- senzu-0.2.0/README.md +252 -0
- senzu-0.2.0/flake.nix +117 -0
- senzu-0.2.0/pyproject.toml +44 -0
- senzu-0.2.0/senzu/__init__.py +34 -0
- senzu-0.2.0/senzu/cli.py +686 -0
- senzu-0.2.0/senzu/config.py +140 -0
- senzu-0.2.0/senzu/core.py +250 -0
- senzu-0.2.0/senzu/exceptions.py +38 -0
- senzu-0.2.0/senzu/formats.py +98 -0
- senzu-0.2.0/senzu/gcp.py +57 -0
- senzu-0.2.0/senzu/lock.py +57 -0
- senzu-0.2.0/senzu/secret_manager_source.py +41 -0
- senzu-0.2.0/senzu/settings.py +104 -0
- senzu-0.2.0/tests/__init__.py +0 -0
- senzu-0.2.0/tests/test_config.py +160 -0
- senzu-0.2.0/tests/test_core.py +350 -0
- senzu-0.2.0/tests/test_lock.py +44 -0
- senzu-0.2.0/tests/test_settings.py +33 -0
- senzu-0.2.0/uv.lock +987 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.12"
|
|
16
|
+
- run: pip install -e ".[dev]"
|
|
17
|
+
- run: pytest
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
|
|
21
|
+
- name: Build package
|
|
22
|
+
run: |
|
|
23
|
+
pip install hatch
|
|
24
|
+
hatch build
|
|
25
|
+
|
|
26
|
+
- uses: actions/upload-artifact@v4
|
|
27
|
+
with:
|
|
28
|
+
name: dist
|
|
29
|
+
path: dist/
|
|
30
|
+
|
|
31
|
+
publish:
|
|
32
|
+
needs: build
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/download-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
|
|
40
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Release Please
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release-please:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: googleapis/release-please-action@v4
|
|
16
|
+
with:
|
|
17
|
+
release-type: python
|
senzu-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
#.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
# .vscode/
|
|
190
|
+
|
|
191
|
+
# Ruff stuff:
|
|
192
|
+
.ruff_cache/
|
|
193
|
+
|
|
194
|
+
# PyPI configuration file
|
|
195
|
+
.pypirc
|
|
196
|
+
|
|
197
|
+
# Cursor
|
|
198
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
199
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
200
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
201
|
+
.cursorignore
|
|
202
|
+
.cursorindexingignore
|
|
203
|
+
|
|
204
|
+
# Marimo
|
|
205
|
+
marimo/_static/
|
|
206
|
+
marimo/_lsp/
|
|
207
|
+
__marimo__/
|
|
208
|
+
|
|
209
|
+
# Senzu
|
|
210
|
+
.env.*
|
senzu-0.2.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0](https://github.com/philip-730/senzu/compare/v0.1.0...v0.2.0) (2026-03-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* show GCP project name in pull, push, and import output ([6e1047f](https://github.com/philip-730/senzu/commit/6e1047f5eb883b0477e0c399b91780b4c1c86578))
|
|
9
|
+
* show project/secret in tables for diff, push, and import output ([fd08dc3](https://github.com/philip-730/senzu/commit/fd08dc3c98769406ef053d27e940961a569bb3c0))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* distinguish first pull from update, simplify push confirmation ([645b80a](https://github.com/philip-730/senzu/commit/645b80a6bb439cb4f0d6f4734ac844045bac1b33))
|
|
15
|
+
* flag untracked local-only keys in diff output ([9c964b3](https://github.com/philip-730/senzu/commit/9c964b363cb14c203706dc70915440ac5295e834))
|
|
16
|
+
* pull preserves local-only keys, import shows accurate diff, init supports flags ([c48da77](https://github.com/philip-730/senzu/commit/c48da7772211828b30fc78afc70e485ccddc4096))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Documentation
|
|
20
|
+
|
|
21
|
+
* update README for pull merge behavior, import diff preview, init flags ([0969cdc](https://github.com/philip-730/senzu/commit/0969cdcc1d24333e25b7028c74d34aefdabe4fdf))
|
|
22
|
+
|
|
23
|
+
## 0.1.0 (2026-03-24)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* make google-cloud-sdk optional in devShell ([c2bcf25](https://github.com/philip-730/senzu/commit/c2bcf25754217a34bea14bc4f9dcd67db2183081))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Bug Fixes
|
|
32
|
+
|
|
33
|
+
* deduplicate uv2nix in flake inputs ([bb64e2b](https://github.com/philip-730/senzu/commit/bb64e2bc5722c1d9dbd2a2990686188d749c22cd))
|
|
34
|
+
* lowercase merged keys in SecretManagerSettingsSource to fix field required errors ([cb070b0](https://github.com/philip-730/senzu/commit/cb070b0398d1d2166d43e75344fb397fa3bb58cb))
|
|
35
|
+
* rename secrets_settings to file_secret_settings for pydantic-settings >=2.4 ([0c6d2af](https://github.com/philip-730/senzu/commit/0c6d2afa2ae9465d13df39291bc4382f1ea90798))
|
|
36
|
+
* replace custom _DotEnv with DotEnvSettingsSource to fix field required errors ([74c825f](https://github.com/philip-730/senzu/commit/74c825f4074fd80babc3b5dcb171e062962ea251))
|
|
37
|
+
* wire editables into hatchling editable build inputs ([2dd99fa](https://github.com/philip-730/senzu/commit/2dd99fa4950d6151f1ed22815925179a5d1382de))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Documentation
|
|
41
|
+
|
|
42
|
+
* add Secret Manager source JSON to nested JSON example ([9fc65bc](https://github.com/philip-730/senzu/commit/9fc65bc14342f94cdb12614ca856dc7f84bdeb81))
|
|
43
|
+
* expand type=raw docs to cover JSON objects, fix nested JSON wording ([aff6417](https://github.com/philip-730/senzu/commit/aff64171a2b78526a0de6136665d3fa00b41aedc))
|
|
44
|
+
* fill in README gaps — status, all-envs behavior, cross-project secrets, nested JSON ([a259bb5](https://github.com/philip-730/senzu/commit/a259bb55bd48d9f96f17c98f10c44f685f476a6d))
|
senzu-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 philip-730
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
senzu-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: senzu
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Secret env sync for GCP teams
|
|
5
|
+
Author: philip-730
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: dotenv,env,gcp,secret-manager,secrets
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: google-cloud-secret-manager>=2
|
|
11
|
+
Requires-Dist: pydantic-settings>=2
|
|
12
|
+
Requires-Dist: python-dotenv>=1
|
|
13
|
+
Requires-Dist: rich>=13
|
|
14
|
+
Requires-Dist: toml>=0.10
|
|
15
|
+
Requires-Dist: typer>=0.12
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest-mock>=3; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
19
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# senzu
|
|
23
|
+
|
|
24
|
+
Stop manually copy-pasting secrets from GCP Secret Manager into `.env` files like an animal. Senzu syncs secrets between GCP Secret Manager and local `.env` files, tracks where every key came from, and won't let you blow up production by pushing stale local changes over remote ones.
|
|
25
|
+
|
|
26
|
+
It's a CLI + Python library for teams using GCP Secret Manager who need their secrets to actually stay in sync — across multiple environments, multiple secrets, multiple people.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Why it's actually sick
|
|
31
|
+
|
|
32
|
+
Most teams end up with a shared `.env` in a private Slack channel, a Notion doc, or some other nightmare. If you're using Secret Manager, you're at least in the right place — but the workflow is still garbage. You open the GCP console, copy values one by one, paste them into a file you hope you don't accidentally commit.
|
|
33
|
+
|
|
34
|
+
Senzu fixes this:
|
|
35
|
+
|
|
36
|
+
- **`senzu pull`** — fetches all your configured secrets into a local `.env` file in one command. Handles JSON and dotenv formats automatically. Works across multiple secrets per environment. If your local file already has keys that aren't in remote yet, they're preserved — remote wins on conflicts, but local-only keys survive. Use `--overwrite` if you want the remote to fully replace your local file.
|
|
37
|
+
|
|
38
|
+
- **`senzu push`** — pushes local changes back to Secret Manager. But here's the thing: it actually checks if someone else changed the remote since you last pulled. If they did, it blocks you.
|
|
39
|
+
- **`senzu diff`** — see exactly what's different between your local file and what's in Secret Manager, without touching anything. Pipe it into CI, use it in code review, whatever.
|
|
40
|
+
|
|
41
|
+
- **Lock file** — after a pull, Senzu writes `senzu.lock` which tracks which key came from which secret and which project. This is what makes push safe. It knows exactly where to send each key back, even if you're pulling from 5 different secrets into one `.env`.
|
|
42
|
+
|
|
43
|
+
- **`senzu import`** — already have a `.env` file and want to get into Secret Manager without touching the GCP console? `senzu import dev --from .env` creates the secret if it doesn't exist, pushes the keys, and writes `senzu.lock` so you're immediately ready to pull/push. If the secret already has data, it merges — your local keys win. Before confirming, it shows you exactly which keys are new, which are changed, and which are unchanged, so you know what you're actually pushing. If nothing has changed, it exits early.
|
|
44
|
+
|
|
45
|
+
- **Multiple environments** — `dev`, `staging`, `prod`, whatever you want. Each one can have its own GCP project, its own secrets, its own local file. `senzu pull dev` or `senzu pull prod`, no config flags needed.
|
|
46
|
+
|
|
47
|
+
- **`SenzuSettings`** — drop-in Pydantic BaseSettings subclass. Automatically reads the right `.env` file based on your `ENV` var, parses nested JSON objects into proper Python dicts/lists, and falls back to reading directly from Secret Manager in Cloud Run by just setting `SENZU_USE_SECRET_MANAGER=true`.
|
|
48
|
+
|
|
49
|
+
- **`senzu generate`** — auto-generates a typed Pydantic settings class from your actual secrets. You never have to manually write `api_key: str` for every field again.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
Senzu isn't on PyPI yet. Install directly from GitHub:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install git+https://github.com/philip-730/senzu
|
|
59
|
+
# or
|
|
60
|
+
uv add git+https://github.com/philip-730/senzu
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Pin to a specific commit or tag if you need stability:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install git+https://github.com/philip-730/senzu@v0.1.0
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
To add it to `requirements.txt`:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
senzu @ git+https://github.com/philip-730/senzu
|
|
73
|
+
# or pinned:
|
|
74
|
+
senzu @ git+https://github.com/philip-730/senzu@v0.1.0
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Requires Python 3.10+. You'll need GCP credentials set up — either `gcloud auth application-default login` locally or a service account in prod.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Setup
|
|
82
|
+
|
|
83
|
+
Run the init wizard in your project root:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
senzu init
|
|
87
|
+
|
|
88
|
+
# or skip the prompts entirely with flags
|
|
89
|
+
senzu init --project my-gcp-project --env dev --file .env.dev --secret app-env
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This creates `senzu.toml` and updates `.gitignore` to exclude your `.env.*` files. You don't want those committed. The `--env` flag controls the environment name (defaults to `dev`) — useful if you want to scaffold a non-dev env first.
|
|
93
|
+
|
|
94
|
+
Or write `senzu.toml` yourself:
|
|
95
|
+
|
|
96
|
+
```toml
|
|
97
|
+
[envs.dev]
|
|
98
|
+
project = "my-gcp-project-dev"
|
|
99
|
+
file = ".env.dev"
|
|
100
|
+
secrets = [
|
|
101
|
+
{ secret = "app-env" },
|
|
102
|
+
{ secret = "db-creds", format = "json" },
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
[envs.prod]
|
|
106
|
+
project = "my-gcp-project-prod"
|
|
107
|
+
file = ".env.prod"
|
|
108
|
+
secrets = [
|
|
109
|
+
{ secret = "app-env-prod" },
|
|
110
|
+
]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Each secret in the `secrets` array is fetched and merged into the local file. If you want the entire secret stored as a single env var rather than expanded into individual keys, use `type = "raw"`. `env_var` is required — it sets the env var name the value is written under:
|
|
114
|
+
|
|
115
|
+
```toml
|
|
116
|
+
secrets = [
|
|
117
|
+
# Scalar value (e.g. a webhook secret string)
|
|
118
|
+
{ secret = "stripe-webhook-secret", type = "raw", env_var = "STRIPE_WEBHOOK_SECRET" },
|
|
119
|
+
|
|
120
|
+
# JSON object stored whole — useful when you want one dict, not individual keys
|
|
121
|
+
{ secret = "firebase-sdk", type = "raw", env_var = "FIREBASE_CREDS" },
|
|
122
|
+
]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For the JSON case, Senzu stores it single-quoted in the `.env` file (`FIREBASE_CREDS='{"type":"service_account",...}'`). Declare the field as `dict` in `SenzuSettings` and it's automatically deserialized.
|
|
126
|
+
|
|
127
|
+
### Cross-project secrets
|
|
128
|
+
|
|
129
|
+
A secret can pull from a different GCP project than the environment default. This is useful for shared infrastructure secrets owned by a central project:
|
|
130
|
+
|
|
131
|
+
```toml
|
|
132
|
+
[envs.prod]
|
|
133
|
+
project = "my-app-prod"
|
|
134
|
+
file = ".env.prod"
|
|
135
|
+
secrets = [
|
|
136
|
+
{ secret = "app-env-prod" },
|
|
137
|
+
{ secret = "datadog-api-key", project = "shared-infra" },
|
|
138
|
+
]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
When multiple secrets share a key name, the last secret listed wins. Senzu will emit a warning so you know it happened.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Usage
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Bootstrap — import an existing .env into Secret Manager for the first time
|
|
149
|
+
senzu import dev --from .env
|
|
150
|
+
senzu import dev --from .env --secret app-env # skip interactive routing, send all keys to this secret
|
|
151
|
+
senzu import dev --from .env --keys DB_URL,DB_PASSWORD # specific keys only
|
|
152
|
+
senzu import dev --from .env --format json # write as JSON instead of dotenv
|
|
153
|
+
|
|
154
|
+
# Pull secrets to local .env files
|
|
155
|
+
senzu pull # all environments defined in senzu.toml
|
|
156
|
+
senzu pull dev # specific environment only
|
|
157
|
+
senzu pull dev --overwrite # fully replace local file with remote (discards local-only keys)
|
|
158
|
+
|
|
159
|
+
# See what's different between local and remote
|
|
160
|
+
senzu diff # all environments
|
|
161
|
+
senzu diff dev # specific environment only
|
|
162
|
+
|
|
163
|
+
# Push local changes back to Secret Manager
|
|
164
|
+
senzu push # all environments (prompts for confirmation per env)
|
|
165
|
+
senzu push dev # specific environment
|
|
166
|
+
senzu push dev --force # skip confirmation even if remote has unretrieved changes
|
|
167
|
+
|
|
168
|
+
# Show all configured environments, their secrets, GCP projects, and local file status
|
|
169
|
+
senzu status
|
|
170
|
+
|
|
171
|
+
# Generate a typed Pydantic settings class from your secrets
|
|
172
|
+
senzu generate dev --out settings.py
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Using in Python
|
|
178
|
+
|
|
179
|
+
If you have a Python app and want type-safe settings without the manual config:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from senzu import SenzuSettings
|
|
183
|
+
|
|
184
|
+
class Settings(SenzuSettings):
|
|
185
|
+
database_url: str
|
|
186
|
+
api_key: str
|
|
187
|
+
firebase_creds: dict # see note on nested JSON below
|
|
188
|
+
|
|
189
|
+
settings = Settings()
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
GCP secrets often store nested JSON objects (service account keys, connection configs, etc.) that can't be written as raw dotenv values. Senzu encodes these as single-quoted strings in the `.env` file:
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
// Secret Manager — app-env secret value
|
|
196
|
+
{
|
|
197
|
+
"database_url": "postgres://...",
|
|
198
|
+
"api_key": "sk-...",
|
|
199
|
+
"firebase_creds": { "type": "service_account", "project_id": "my-app" }
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# .env.dev — what Senzu writes after senzu pull
|
|
205
|
+
DATABASE_URL=postgres://...
|
|
206
|
+
API_KEY=sk-...
|
|
207
|
+
FIREBASE_CREDS='{"type":"service_account","project_id":"my-app"}'
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
When you declare the field as `dict` (or `list`) in `SenzuSettings`, Senzu automatically deserializes it — so `settings.firebase_creds` is already a Python dict, not a string.
|
|
211
|
+
|
|
212
|
+
Senzu reads `ENV` or `SENZU_ENV` to figure out which environment you're in, finds the right `.env` file from `senzu.toml`, and loads it. In Cloud Run or any environment where you don't have a file, set `SENZU_USE_SECRET_MANAGER=true` and it reads directly from Secret Manager.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## The lock file
|
|
217
|
+
|
|
218
|
+
After `senzu pull`, you'll have a `senzu.lock` file. This is how Senzu knows which of your 40 env vars came from which of your 5 secrets. Don't delete it — push won't work without it. Commit it — it contains no secret values, just routing metadata (which key lives in which secret), and your teammates need it to push without doing a redundant pull first.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Auth
|
|
223
|
+
|
|
224
|
+
Senzu uses the standard GCP auth chain via `google-cloud-secret-manager`. Locally, run:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
gcloud auth application-default login
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
In CI/CD or Cloud Run, use a service account with `Secret Manager Secret Accessor` role on the relevant secrets.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Development
|
|
235
|
+
|
|
236
|
+
### With Nix (recommended)
|
|
237
|
+
|
|
238
|
+
The repo uses [uv2nix](https://github.com/pyproject-nix/uv2nix) to provide a fully reproducible dev environment. Dependencies are pinned in `uv.lock`.
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
nix develop
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
This drops you into a shell with the right Python version, all deps installed, and senzu itself available as an editable install — changes to the source are reflected immediately without reinstalling. `gcloud` is also available.
|
|
245
|
+
|
|
246
|
+
To run the CLI directly without entering a shell:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
nix run
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Without Nix
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
uv sync
|
|
256
|
+
uv run senzu --help
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Or with a standard virtualenv:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
python -m venv .venv
|
|
263
|
+
source .venv/bin/activate
|
|
264
|
+
pip install -e '.[dev]'
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Running tests
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
pytest
|
|
271
|
+
# or inside nix develop:
|
|
272
|
+
pytest tests/
|
|
273
|
+
```
|