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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pullup
3
- Version: 0.0.7
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 `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:
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 `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:
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)
@@ -1,4 +1,4 @@
1
- __version__ = "0.0.7"
1
+ __version__ = "0.0.8"
2
2
 
3
3
  # The names a caller reaches for. The modules stay importable for everything else.
4
4
  from .env import EnvError, EnvStore, venv_env
@@ -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 whatever is actually set locally.'
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 nuke
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
- "Delete the server and the tunnel. Irreversible, so it asks for a token you have to read."
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
- if input(f'This deletes the server and the tunnel. Type {{typ}} to proceed: ') != typ:
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 == 'nuke': nuke()
303
- else: print('usage: deploy.py env | compose | deploy | status | nuke')
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', 'nbdev_prepare', 'nbdev_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', 'nbdev_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', 'nbdev_release_gh',
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', 'nbdev_pypi',
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': 'build', 'name': 'Rust build', 'doc': 'cargo build, test, clippy.',
75
+ {'id': 'rust', 'job': 'rust', 'name': 'Rust build', 'doc': 'cargo build, test, clippy.',
76
76
  'options': []},
77
- {'id': 'go', 'job': 'build', 'name': 'Go build', 'doc': 'go build, test, vet.', 'options': []},
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