pullup 0.0.7__tar.gz → 0.0.9__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.9}/PKG-INFO +5 -5
- {pullup-0.0.7 → pullup-0.0.9}/README.md +1 -1
- {pullup-0.0.7 → pullup-0.0.9}/pullup/__init__.py +1 -1
- {pullup-0.0.7 → pullup-0.0.9}/pullup/_modidx.py +1 -1
- {pullup-0.0.7 → pullup-0.0.9}/pullup/deploy.py +32 -11
- {pullup-0.0.7 → pullup-0.0.9}/pullup/env.py +2 -2
- {pullup-0.0.7 → pullup-0.0.9}/pullup/project.py +4 -4
- {pullup-0.0.7 → pullup-0.0.9}/pullup/wfbuild.py +2 -2
- {pullup-0.0.7 → pullup-0.0.9}/pullup/workflows.py +19 -16
- {pullup-0.0.7 → pullup-0.0.9}/pyproject.toml +2 -2
- {pullup-0.0.7 → pullup-0.0.9}/.gitignore +0 -0
- {pullup-0.0.7 → pullup-0.0.9}/LICENSE +0 -0
- {pullup-0.0.7 → pullup-0.0.9}/pullup/drive.py +0 -0
- {pullup-0.0.7 → pullup-0.0.9}/pullup/infra.py +0 -0
- {pullup-0.0.7 → pullup-0.0.9}/pullup/pipeline.py +0 -0
- {pullup-0.0.7 → pullup-0.0.9}/pullup/stack.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pullup
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
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
|
|
@@ -14,15 +14,15 @@ Classifier: Natural Language :: English
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
15
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
16
|
Requires-Python: >=3.11
|
|
17
|
-
Requires-Dist: cfeasy>=0.0.
|
|
18
|
-
Requires-Dist: dockeasy>=0.0.
|
|
17
|
+
Requires-Dist: cfeasy>=0.0.6
|
|
18
|
+
Requires-Dist: dockeasy>=0.0.10
|
|
19
19
|
Requires-Dist: fastcore>=1.8.17
|
|
20
20
|
Requires-Dist: gheasy>=0.0.10
|
|
21
21
|
Requires-Dist: keyring>=25.0.0
|
|
22
22
|
Requires-Dist: ptymini>=0.0.5
|
|
23
23
|
Requires-Dist: python-dotenv>=1.0.0
|
|
24
24
|
Requires-Dist: pyyaml>=6.0
|
|
25
|
-
Requires-Dist: vpseasy>=0.0.
|
|
25
|
+
Requires-Dist: vpseasy>=0.0.24
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
|
|
28
28
|
# 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)
|
|
@@ -179,7 +179,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
179
179
|
'pullup.workflows._fastship': ('workflows.html#_fastship', 'pullup/workflows.py'),
|
|
180
180
|
'pullup.workflows._from_line': ('workflows.html#_from_line', 'pullup/workflows.py'),
|
|
181
181
|
'pullup.workflows._gh_authed': ('workflows.html#_gh_authed', 'pullup/workflows.py'),
|
|
182
|
-
'pullup.workflows._gheasy': ('workflows.html#_gheasy', 'pullup/workflows.py'),
|
|
183
182
|
'pullup.workflows._opened': ('workflows.html#_opened', 'pullup/workflows.py'),
|
|
184
183
|
'pullup.workflows._read': ('workflows.html#_read', 'pullup/workflows.py'),
|
|
185
184
|
'pullup.workflows._run_row': ('workflows.html#_run_row', 'pullup/workflows.py'),
|
|
@@ -187,4 +186,5 @@ d = { 'settings': { 'branch': 'main',
|
|
|
187
186
|
'pullup.workflows._wf_file': ('workflows.html#_wf_file', 'pullup/workflows.py'),
|
|
188
187
|
'pullup.workflows._wf_name': ('workflows.html#_wf_name', 'pullup/workflows.py'),
|
|
189
188
|
'pullup.workflows._yaml': ('workflows.html#_yaml', 'pullup/workflows.py'),
|
|
189
|
+
'pullup.workflows.gheasy_core': ('workflows.html#gheasy_core', 'pullup/workflows.py'),
|
|
190
190
|
'pullup.workflows.levels': ('workflows.html#levels', 'pullup/workflows.py')}}}
|
|
@@ -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,
|
|
@@ -99,7 +99,7 @@ class EnvStore:
|
|
|
99
99
|
return {'key': key, 'secret': bool(secret)}
|
|
100
100
|
def unset(self, key):
|
|
101
101
|
"Remove a value from the keychain, the env file and `os.environ`, and say which of the first two held it."
|
|
102
|
-
from dockeasy.core import
|
|
102
|
+
from dockeasy.core import FASTOPS_ENV
|
|
103
103
|
from dotenv import unset_key
|
|
104
104
|
key, gone = str(key), []
|
|
105
105
|
try:
|
|
@@ -108,7 +108,7 @@ class EnvStore:
|
|
|
108
108
|
keyring.delete_password(self.service, key)
|
|
109
109
|
gone.append('keychain')
|
|
110
110
|
except Exception: pass
|
|
111
|
-
path = str(self.path or
|
|
111
|
+
path = str(self.path or FASTOPS_ENV)
|
|
112
112
|
try:
|
|
113
113
|
removed, _ = unset_key(path, key)
|
|
114
114
|
if removed: gone.append('env file')
|
|
@@ -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.',
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
# %% auto #0
|
|
7
|
-
__all__ = ['WORKFLOW_DIR', 'WorkflowError', 'levels', 'Workflows']
|
|
7
|
+
__all__ = ['WORKFLOW_DIR', 'WorkflowError', 'gheasy_core', 'levels', 'Workflows']
|
|
8
8
|
|
|
9
9
|
# %% ../nbs/05_workflows.ipynb #13d36d32
|
|
10
10
|
import re
|
|
@@ -29,13 +29,16 @@ class WorkflowError(RuntimeError):
|
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
31
|
# %% ../nbs/05_workflows.ipynb #21f04074
|
|
32
|
-
def
|
|
32
|
+
def gheasy_core():
|
|
33
|
+
'Return the `gheasy.core` module, raising `WorkflowError` with the install hint when gheasy is missing.'
|
|
33
34
|
try:
|
|
34
35
|
import gheasy.core as core
|
|
35
36
|
return core
|
|
36
37
|
except ImportError as e:
|
|
37
38
|
raise WorkflowError(needs_extra('this needs gheasy')) from e
|
|
38
39
|
|
|
40
|
+
_gheasy = gheasy_core
|
|
41
|
+
|
|
39
42
|
def _opened(core, name, was=None):
|
|
40
43
|
"gheasy's helper under whichever spelling it has: public from 0.0.10, underscored before it."
|
|
41
44
|
for n in (name, was or f'_{name}'):
|
|
@@ -46,8 +49,8 @@ def _opened(core, name, was=None):
|
|
|
46
49
|
def _yaml():
|
|
47
50
|
"gheasy's ruamel instance for reading a workflow, or plain pyyaml where gheasy is not installed."
|
|
48
51
|
try:
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
from gheasy.workflow import yaml_instance
|
|
53
|
+
return yaml_instance()
|
|
51
54
|
except ImportError: pass
|
|
52
55
|
import yaml
|
|
53
56
|
class _Plain:
|
|
@@ -146,12 +149,12 @@ class Workflows:
|
|
|
146
149
|
def repo(self):
|
|
147
150
|
"`owner/name` from the origin remote, or `''` when there is no GitHub remote."
|
|
148
151
|
try:
|
|
149
|
-
core =
|
|
152
|
+
core = gheasy_core()
|
|
150
153
|
owner, name = _opened(core, 'repo_slug', '_get_repo_slug')(str(self.root))
|
|
151
154
|
return f'{owner}/{name}'
|
|
152
155
|
except Exception: return ''
|
|
153
156
|
def _api(self):
|
|
154
|
-
core =
|
|
157
|
+
core = gheasy_core()
|
|
155
158
|
token = _opened(core, 'gh_token', '_resolve_gh_token')()
|
|
156
159
|
if not token:
|
|
157
160
|
raise WorkflowError('set GITHUB_TOKEN — add it in Environment below, or export it before starting Leela')
|
|
@@ -161,7 +164,7 @@ class Workflows:
|
|
|
161
164
|
"Whether the GitHub features can be used, and if not, whether gheasy, a remote or a token is missing."
|
|
162
165
|
row = {'gheasy': True, 'dockeasy': installed('dockeasy'), 'repo': '', 'token': False,
|
|
163
166
|
'gh_cli': bool(which('gh')), 'gh_auth': _gh_authed(), 'why': ''}
|
|
164
|
-
try: core =
|
|
167
|
+
try: core = gheasy_core()
|
|
165
168
|
except WorkflowError as e: return row | {'gheasy': False, 'why': str(e)}
|
|
166
169
|
row['repo'] = self.repo()
|
|
167
170
|
row['token'] = bool(_opened(core, 'gh_token', '_resolve_gh_token')())
|
|
@@ -190,7 +193,7 @@ class Workflows:
|
|
|
190
193
|
"Start a run of a workflow that declares `workflow_dispatch`."
|
|
191
194
|
owner, name, api = self._api()
|
|
192
195
|
if not str(workflow).strip(): raise WorkflowError('choose a workflow to run')
|
|
193
|
-
ref = str(ref or '').strip() or
|
|
196
|
+
ref = str(ref or '').strip() or gheasy_core()._git_branch(str(self.root))
|
|
194
197
|
api.actions.create_workflow_dispatch(workflow_id=str(workflow), ref=ref, inputs=dict(inputs or {}))
|
|
195
198
|
return {'workflow': str(workflow), 'ref': ref}
|
|
196
199
|
def cancel(self, run_id):
|
|
@@ -200,19 +203,19 @@ class Workflows:
|
|
|
200
203
|
return {'cancelled': int(run_id)}
|
|
201
204
|
def config(self):
|
|
202
205
|
"The project's `.gheasy/config.json`, or None when gheasy has not been set up here."
|
|
203
|
-
try: core =
|
|
206
|
+
try: core = gheasy_core()
|
|
204
207
|
except WorkflowError: return None
|
|
205
208
|
if not core.cfg_path(str(self.root)).exists(): return None
|
|
206
209
|
return core.GheasyConfig.load(str(self.root)).to_dict()
|
|
207
210
|
def generate(self):
|
|
208
211
|
"The workflow gheasy would write for this project, as text, without writing it."
|
|
209
|
-
core =
|
|
212
|
+
core = gheasy_core()
|
|
210
213
|
if not core.cfg_path(str(self.root)).exists():
|
|
211
214
|
raise WorkflowError('this project has no .gheasy/config.json to generate from')
|
|
212
215
|
return core.mk_workflow(core.GheasyConfig.load(str(self.root)))
|
|
213
216
|
def write_generated(self):
|
|
214
217
|
"Write gheasy's generated workflow with `gh_workflow`, as the gheasy CLI does; nothing is committed."
|
|
215
|
-
core =
|
|
218
|
+
core = gheasy_core()
|
|
216
219
|
if not core.cfg_path(str(self.root)).exists():
|
|
217
220
|
raise WorkflowError('this project has no .gheasy/config.json to generate from')
|
|
218
221
|
core.gh_workflow(path=str(self.root))
|
|
@@ -307,13 +310,13 @@ class Workflows:
|
|
|
307
310
|
return wfb.build()
|
|
308
311
|
def build(self, spec):
|
|
309
312
|
"The YAML for a workflow-builder spec, composed with gheasy."
|
|
310
|
-
|
|
313
|
+
gheasy_core()
|
|
311
314
|
from .wfbuild import compose
|
|
312
315
|
cfg = self.config() or {}
|
|
313
316
|
return compose(spec, schema=self.schema(), app=str(cfg.get('app') or self.root.name))
|
|
314
317
|
def write_spec(self, spec, file=''):
|
|
315
318
|
"Write the YAML for a spec into `.github/workflows` and save the spec beside it."
|
|
316
|
-
|
|
319
|
+
gheasy_core()
|
|
317
320
|
from .wfbuild import save_spec
|
|
318
321
|
name = _wf_name(str(file or spec.get('file') or f"{spec.get('name') or 'ci'}.yml"))
|
|
319
322
|
text = self.build(spec)
|
|
@@ -334,11 +337,11 @@ class Workflows:
|
|
|
334
337
|
return {'spec': spec, 'stale': stale}
|
|
335
338
|
def preview(self, choice):
|
|
336
339
|
"The YAML a catalogue entry would write, without writing it."
|
|
337
|
-
|
|
340
|
+
gheasy_core()
|
|
338
341
|
return self._build(str(choice)).to_yaml()
|
|
339
342
|
def add(self, choice, file=''):
|
|
340
343
|
"Write one catalogue entry into `.github/workflows`; raises if the file exists."
|
|
341
|
-
|
|
344
|
+
gheasy_core()
|
|
342
345
|
rows = {r['id']: r for r in self.catalogue()['workflows']}
|
|
343
346
|
row = rows.get(str(choice))
|
|
344
347
|
if row is None: raise WorkflowError(f'unknown workflow: {choice}')
|
|
@@ -381,7 +384,7 @@ class Workflows:
|
|
|
381
384
|
return {'secrets': secrets, 'variables': variables}
|
|
382
385
|
def push_env(self, values, keys=(), dry_run=False):
|
|
383
386
|
"Push values for the declared keys to GitHub through gheasy, one result row per key."
|
|
384
|
-
core =
|
|
387
|
+
core = gheasy_core()
|
|
385
388
|
if not which('gh'):
|
|
386
389
|
raise WorkflowError('pushing secrets uses the GitHub CLI: install `gh` and run `gh auth login`')
|
|
387
390
|
schema = self.schema()
|
|
@@ -12,8 +12,8 @@ license = {text = "Apache-2.0"}
|
|
|
12
12
|
authors = [{name = "Karthik Rajgopal", email = "karthik.rajgopal@hotmail.com"}]
|
|
13
13
|
keywords = ['nbdev', 'jupyter', 'notebook', 'python']
|
|
14
14
|
classifiers = ["Natural Language :: English", "Intended Audience :: Developers", "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only"]
|
|
15
|
-
dependencies = ['fastcore>=1.8.17', 'ptymini>=0.0.5', 'pyyaml>=6.0', 'dockeasy>=0.0.
|
|
16
|
-
'gheasy>=0.0.10', 'vpseasy>=0.0.
|
|
15
|
+
dependencies = ['fastcore>=1.8.17', 'ptymini>=0.0.5', 'pyyaml>=6.0', 'dockeasy>=0.0.10',
|
|
16
|
+
'gheasy>=0.0.10', 'vpseasy>=0.0.24', 'cfeasy>=0.0.6', 'python-dotenv>=1.0.0',
|
|
17
17
|
'keyring>=25.0.0']
|
|
18
18
|
|
|
19
19
|
[project.urls]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|