pullup 0.0.7__tar.gz → 0.0.8__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.
- {pullup-0.0.7 → pullup-0.0.8}/PKG-INFO +2 -2
- {pullup-0.0.7 → pullup-0.0.8}/README.md +1 -1
- {pullup-0.0.7 → pullup-0.0.8}/pullup/__init__.py +1 -1
- {pullup-0.0.7 → pullup-0.0.8}/pullup/deploy.py +32 -11
- {pullup-0.0.7 → pullup-0.0.8}/pullup/project.py +4 -4
- {pullup-0.0.7 → pullup-0.0.8}/pullup/wfbuild.py +2 -2
- {pullup-0.0.7 → pullup-0.0.8}/.gitignore +0 -0
- {pullup-0.0.7 → pullup-0.0.8}/LICENSE +0 -0
- {pullup-0.0.7 → pullup-0.0.8}/pullup/_modidx.py +0 -0
- {pullup-0.0.7 → pullup-0.0.8}/pullup/drive.py +0 -0
- {pullup-0.0.7 → pullup-0.0.8}/pullup/env.py +0 -0
- {pullup-0.0.7 → pullup-0.0.8}/pullup/infra.py +0 -0
- {pullup-0.0.7 → pullup-0.0.8}/pullup/pipeline.py +0 -0
- {pullup-0.0.7 → pullup-0.0.8}/pullup/stack.py +0 -0
- {pullup-0.0.7 → pullup-0.0.8}/pullup/workflows.py +0 -0
- {pullup-0.0.7 → pullup-0.0.8}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pullup
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: build, release, and deploy projects
|
|
5
5
|
Project-URL: Repository, https://github.com/vedicreader/pullup
|
|
6
6
|
Project-URL: Documentation, https://vedicreader.github.io/pullup
|
|
@@ -44,7 +44,7 @@ This also installs gheasy, dockeasy, vpseasy and cfeasy. None of them is importe
|
|
|
44
44
|
|
|
45
45
|
## What kind of project is this
|
|
46
46
|
|
|
47
|
-
A release flow is the ordered list of commands that publishes one kind of project. An nbdev library is released with `
|
|
47
|
+
A release flow is the ordered list of commands that publishes one kind of project. An nbdev library is released with `nbdev-prepare`, `nbdev-bump-version`, `nbdev-release-gh` and `nbdev-pypi`. A plain Python package and a Rust crate each have a different list. [`Project`](https://vedicreader.github.io/pullup/project.html#project) looks at the files in a folder, picks the flow that fits, and returns its steps. The folder here holds only a `pyproject.toml` with a `[tool.nbdev]` table, so it is an nbdev project:
|
|
48
48
|
|
|
49
49
|
``` python
|
|
50
50
|
project = Project(root)
|
|
@@ -17,7 +17,7 @@ This also installs gheasy, dockeasy, vpseasy and cfeasy. None of them is importe
|
|
|
17
17
|
|
|
18
18
|
## What kind of project is this
|
|
19
19
|
|
|
20
|
-
A release flow is the ordered list of commands that publishes one kind of project. An nbdev library is released with `
|
|
20
|
+
A release flow is the ordered list of commands that publishes one kind of project. An nbdev library is released with `nbdev-prepare`, `nbdev-bump-version`, `nbdev-release-gh` and `nbdev-pypi`. A plain Python package and a Rust crate each have a different list. [`Project`](https://vedicreader.github.io/pullup/project.html#project) looks at the files in a folder, picks the flow that fits, and returns its steps. The folder here holds only a `pyproject.toml` with a `[tool.nbdev]` table, so it is an nbdev project:
|
|
21
21
|
|
|
22
22
|
``` python
|
|
23
23
|
project = Project(root)
|
|
@@ -88,8 +88,8 @@ def _load_env():
|
|
|
88
88
|
return dict(os.environ) | (parse_env(fn=str(envf)) if envf.exists() else {{}})
|
|
89
89
|
|
|
90
90
|
def env2push():
|
|
91
|
-
'Schema defaults overlaid with
|
|
92
|
-
return ENV_KEYS | filter_keys(_load_env(), in_(ENV_KEYS))
|
|
91
|
+
'Schema defaults overlaid with the local values, leaving out keys with no value so GitHub never stores "None".'
|
|
92
|
+
return {{k: v for k, v in (ENV_KEYS | filter_keys(_load_env(), in_(ENV_KEYS))).items() if v not in (None, 'None', '')}}
|
|
93
93
|
|
|
94
94
|
def init_gheasy():
|
|
95
95
|
cfg = GheasyConfig.load(ROOT)
|
|
@@ -181,13 +181,14 @@ runs whatever this file says.
|
|
|
181
181
|
python deploy.py compose
|
|
182
182
|
python deploy.py deploy
|
|
183
183
|
python deploy.py status
|
|
184
|
-
python deploy.py
|
|
184
|
+
python deploy.py rollback [tag]
|
|
185
|
+
python deploy.py nuke [--tunnel]
|
|
185
186
|
"""
|
|
186
187
|
import os, sys, secrets
|
|
187
188
|
from fastcore.all import Path
|
|
188
189
|
from dockeasy import detect_app, env_set, env_get
|
|
189
190
|
from cfeasy import CF
|
|
190
|
-
from vpseasy import caddy_stack, hetzner_deploy, Hetzner
|
|
191
|
+
from vpseasy import caddy_stack, hetzner_deploy, Hetzner, wait_http, rollback as vps_rollback
|
|
191
192
|
|
|
192
193
|
root = Path(__file__).resolve().parent
|
|
193
194
|
app, domain, subdomain = {app!r}, {domain!r}, {subdomain!r}
|
|
@@ -269,6 +270,22 @@ def deploy(force=None):
|
|
|
269
270
|
env_set('HETZNER_IP', r.ip, path=root/'.env')
|
|
270
271
|
if r.key: env_set('HETZNER_KEY', r.key, path=root/'.env')
|
|
271
272
|
print(f'deployed: https://{{host}} at {{r.ip}}')
|
|
273
|
+
after_deploy(cf)
|
|
274
|
+
|
|
275
|
+
def after_deploy(cf):
|
|
276
|
+
\'\'\'Wait until the site answers, then purge Cloudflare's cache for the host so visitors get the new pages.
|
|
277
|
+
vpseasy's deploy already keeps the replaced image for `rollback` and prunes dangling images.
|
|
278
|
+
Empty this function to skip both steps.\'\'\'
|
|
279
|
+
if not wait_http(f'https://{{host}}/', ua=f'{{app}}-deploy', tries=20): return print('site not answering yet; cache not purged')
|
|
280
|
+
try: cf.purge(cf.zone_id(domain), prefixes=[f'{{host}}/'], fallback_all=False); print('cache purged:', host)
|
|
281
|
+
except Exception as e: print(f'cache not purged: {{e}}')
|
|
282
|
+
|
|
283
|
+
def rollback(to=None):
|
|
284
|
+
"Run the image before the last deploy, or the kept image tagged `to`."
|
|
285
|
+
ip = env_get('HETZNER_IP', path=root/'.env')
|
|
286
|
+
if not ip: return print('no server yet')
|
|
287
|
+
vps_rollback(ip, to, path=srv, user=env_get('SERVER_USER', path=root/'.env', default='deploy'),
|
|
288
|
+
key=env_get('HETZNER_KEY', path=root/'.env', default=None))
|
|
272
289
|
|
|
273
290
|
def status():
|
|
274
291
|
"Which servers exist on this token, and which tunnels."
|
|
@@ -278,14 +295,17 @@ def status():
|
|
|
278
295
|
for t in cf.tunnels(): print(f'tunnel {{t.get("name","")!r:22}} {{t.get("id","")}}')
|
|
279
296
|
print(f'this project deploys as {{name}} -> {{host}}')
|
|
280
297
|
|
|
281
|
-
def nuke():
|
|
282
|
-
|
|
298
|
+
def nuke(tunnel=False):
|
|
299
|
+
\'\'\'Delete the server, and with `tunnel` the tunnel too. Irreversible, so it asks for a token you have to read.
|
|
300
|
+
The tunnel serves the site from whichever box runs its connector, so after a move to a new server keep it.\'\'\'
|
|
283
301
|
typ = secrets.token_urlsafe(8)
|
|
284
|
-
|
|
302
|
+
what = 'the server and the tunnel' if tunnel else 'the server'
|
|
303
|
+
if input(f'This deletes {{what}}. Type {{typ}} to proceed: ') != typ:
|
|
285
304
|
return print('aborted')
|
|
286
305
|
name = env_get('SERVER_NAME', path=root/'.env', default=app)
|
|
287
306
|
Hetzner().delete(name)
|
|
288
307
|
print(f'server {{name}} deleted')
|
|
308
|
+
if not tunnel: return print('tunnel kept (python deploy.py nuke --tunnel deletes it)')
|
|
289
309
|
try:
|
|
290
310
|
cf = CF(token=env_get('CLOUDFLARE_API_TOKEN', path=root/'.env'))
|
|
291
311
|
cf.delete_tunnel(cf.tunnel_id(tunnel_name))
|
|
@@ -299,8 +319,9 @@ if __name__ == '__main__':
|
|
|
299
319
|
elif cmd == 'env': mk_env()
|
|
300
320
|
elif cmd == 'deploy': deploy(force=args[1] if len(args) > 1 else None)
|
|
301
321
|
elif cmd == 'status': status()
|
|
302
|
-
elif cmd == '
|
|
303
|
-
|
|
322
|
+
elif cmd == 'rollback': rollback(args[1] if len(args) > 1 else None)
|
|
323
|
+
elif cmd == 'nuke': nuke(tunnel='--tunnel' in args)
|
|
324
|
+
else: print('usage: deploy.py env | compose | deploy | status | rollback [tag] | nuke [--tunnel]')
|
|
304
325
|
'''
|
|
305
326
|
|
|
306
327
|
# %% ../nbs/03_deploy.ipynb #d11ca8b9
|
|
@@ -313,9 +334,9 @@ class Deploy(Pipeline):
|
|
|
313
334
|
def target(self):
|
|
314
335
|
"Where this deploy goes, which secrets are still unset, and which generated files exist."
|
|
315
336
|
store = self.env if isinstance(self.env, EnvStore) else EnvStore()
|
|
316
|
-
def value(key, default=''): return env_value(store, key, default)
|
|
337
|
+
def value(key, default=''): return env_value(store, key) or env_value(store, key.lower(), default)
|
|
317
338
|
missing = [k for k, v in DEPLOY_KEYS.items()
|
|
318
|
-
if v is None and not env_value(store, k, secret=True)]
|
|
339
|
+
if v is None and not (env_value(store, k, secret=True) or env_value(store, k.lower(), secret=True))]
|
|
319
340
|
return {'app': value('APP') or self.root.name, 'domain': value('DOMAIN'),
|
|
320
341
|
'server': value('SERVER_NAME'), 'user': value('SERVER_USER', 'deploy'),
|
|
321
342
|
'port': value('PORT', '5001'), 'missing': missing,
|
|
@@ -64,13 +64,13 @@ def app_project(root):
|
|
|
64
64
|
|
|
65
65
|
# %% ../nbs/01_project.ipynb #3b3306e1
|
|
66
66
|
NBDEV_STEPS = [
|
|
67
|
-
Step('prepare', '
|
|
67
|
+
Step('prepare', 'nbdev-prepare', 'nbdev-prepare',
|
|
68
68
|
'Export the notebooks, run the tests, clean the metadata, rebuild the README.'),
|
|
69
|
-
Step('bump', 'bump version', '
|
|
69
|
+
Step('bump', 'bump version', 'nbdev-bump-version',
|
|
70
70
|
'Raise the version in settings.ini and commit it.'),
|
|
71
|
-
Step('gh', 'GitHub release', '
|
|
71
|
+
Step('gh', 'GitHub release', 'nbdev-release-gh',
|
|
72
72
|
'Tag the version and create the GitHub release from CHANGELOG.', ['GITHUB_TOKEN']),
|
|
73
|
-
Step('pypi', 'PyPI release', '
|
|
73
|
+
Step('pypi', 'PyPI release', 'nbdev-pypi',
|
|
74
74
|
'Build the wheel and upload it to PyPI.', ['TWINE_USERNAME', 'TWINE_PASSWORD']),
|
|
75
75
|
]
|
|
76
76
|
|
|
@@ -72,9 +72,9 @@ PRESETS = [
|
|
|
72
72
|
'doc': 'Defaults to the project name.'}]},
|
|
73
73
|
{'id': 'node', 'job': 'frontend', 'name': 'Node build', 'doc': 'npm ci, build, test.',
|
|
74
74
|
'options': []},
|
|
75
|
-
{'id': 'rust', 'job': '
|
|
75
|
+
{'id': 'rust', 'job': 'rust', 'name': 'Rust build', 'doc': 'cargo build, test, clippy.',
|
|
76
76
|
'options': []},
|
|
77
|
-
{'id': 'go', 'job': '
|
|
77
|
+
{'id': 'go', 'job': 'go', 'name': 'Go build', 'doc': 'go build, test, vet.', 'options': []},
|
|
78
78
|
{'id': 'deploy', 'job': 'deploy', 'name': 'Deploy to a VPS',
|
|
79
79
|
'doc': 'Every key in env_schema as a secret or a variable, the deploy key, then your '
|
|
80
80
|
'deploy command. The shape lego uses.',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|