From b044dd015ed47599f2f9923dce583aae75983037 Mon Sep 17 00:00:00 2001 From: jpic Date: Sat, 30 May 2020 18:32:00 +0200 Subject: [PATCH] Add Package.upgrade option --- README.md | 51 +++++++++++++++++++++++++++++++++------ shlax/actions/packages.py | 8 +++--- shlax/cli.py | 17 +++++++++++-- shlax/output.py | 34 ++++++++++++-------------- shlax/shortcuts.py | 5 ++++ shlax/targets/base.py | 17 ++++++------- shlax/targets/buildah.py | 9 ++++--- 7 files changed, 96 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 605d61f..5bd1a8f 100644 --- a/README.md +++ b/README.md @@ -187,12 +187,13 @@ class Docker(Target): return await self.parent.exec(*['docker', 'exec', self.name] + args) ``` -Don't worry about `self.parent` being set, it is enforced to `Localhost` if -unset so that we always have something that actually spawns a process in the -chain ;) +This also means that you always need a parent with an exec implementation, +there are two: -The result of that design is that the following use cases are open for -business: +- Localhost, executes on localhost +- Stub, for testing + +The result of that design is that the following use cases are available: ```python # This action installs my favorite package on any distro @@ -215,14 +216,48 @@ Ssh(host='yourhost')(build) # Or on a server behingh a bastion: # ssh yourbastion ssh yourhost build exec apt install python3 -Ssh(host='bastion')(Ssh(host='yourhost')(build)) +Localhost()(Ssh(host='bastion')(Ssh(host='yourhost')(build)) # That's going to do the same -Ssh( +Localhost(Ssh( Ssh( build, host='yourhost' ), host='bastion' -)() +))() +``` + +## CLI + +You should build your CLI with your favorite CLI framework. Nonetheless, shlax +provides a ConsoleScript built on cli2 (a personnal experiment, still pre-alpha +stage) that will expose any callable you define in a script, for example: + +```python +#!/usr/bin/env shlax + +from shlax.shortcuts import * + +webpack = Container( + build=Buildah( + Packages('npm') + ) +) + +django = Container( + build=Buildah( + Packages('python') + ) +) + +pod = Pod( + django=django, + webpack=webpack, +) +``` + +Running this file will output: + +``` ``` diff --git a/shlax/actions/packages.py b/shlax/actions/packages.py index 2294f80..cbdb385 100644 --- a/shlax/actions/packages.py +++ b/shlax/actions/packages.py @@ -52,8 +52,9 @@ class Packages: installed = [] - def __init__(self, *packages): + def __init__(self, *packages, upgrade=True): self.packages = [] + self.upgrade = upgrade for package in packages: line = dedent(package).strip().replace('\n', ' ') self.packages += line.split(' ') @@ -116,7 +117,8 @@ class Packages: self.cmds = self.mgrs[self.mgr] await self.update(target) - await target.rexec(self.cmds['upgrade']) + if self.upgrade: + await target.rexec(self.cmds['upgrade']) packages = [] for package in self.packages: @@ -160,4 +162,4 @@ class Packages: return self.cache_root + '/pacman' def __repr__(self): - return f'Packages({self.packages})' + return f'Packages({self.packages}, upgrade={self.upgrade})' diff --git a/shlax/cli.py b/shlax/cli.py index 9f6a6f0..0dfa364 100644 --- a/shlax/cli.py +++ b/shlax/cli.py @@ -5,6 +5,7 @@ Shlax executes mostly in 3 ways: - With the name of a module in shlax.repo: a community maintained shlaxfile """ import ast +import asyncio import cli2 import glob import inspect @@ -49,7 +50,19 @@ class ConsoleScript(cli2.ConsoleScript): mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) for member in members: - from shlax.targets.localhost import Localhost - self[member] = cli2.Callable(member, Localhost(getattr(mod, member))) + subject = getattr(mod, member) + if callable(subject): + self[member] = cli2.Callable(member, subject) + else: + importable = cli2.Importable(member, subject) + self[member] = cli2.Group(member) + for cb in importable.get_callables(): + self[member][cb.name] = cb + + def call(self, command): + if command.name == 'help': + return super().call(command) + from shlax.targets.localhost import Localhost + asyncio.run(Localhost()(command.target)) cli = ConsoleScript(__doc__) diff --git a/shlax/output.py b/shlax/output.py index 19adeb8..04ef6f2 100644 --- a/shlax/output.py +++ b/shlax/output.py @@ -25,15 +25,14 @@ class Output: def colorize(self, code, content): return self.color(code) + content + self.color() - def colorized(self): - if hasattr(self.subject, 'colorized'): - return self.subject.colorized(self.colors) + def colorized(self, action): + if hasattr(action, 'colorized'): + return action.colorized(self.colors) else: - return str(self.subject) + return str(action) def __init__( self, - subject=None, prefix=None, regexps=None, debug='cmd,visit,out', @@ -41,7 +40,6 @@ class Output: flush=None, **kwargs ): - self.subject = subject self.prefix = prefix self.debug = debug self.prefix_length = 0 @@ -114,59 +112,59 @@ class Output: return line - def test(self): + def test(self, action): self(''.join([ self.colors['purplebold'], '! TEST ', self.colors['reset'], - self.colorized(), + self.colorized(action), '\n', ])) - def clean(self): + def clean(self, action): if self.debug: self(''.join([ self.colors['bluebold'], '+ CLEAN ', self.colors['reset'], - self.colorized(), + self.colorized(action), '\n', ])) - def start(self): + def start(self, action): if self.debug is True or 'visit' in str(self.debug): self(''.join([ self.colors['orangebold'], '⚠ START ', self.colors['reset'], - self.colorized(), + self.colorized(action), '\n', ])) - def success(self): + def success(self, action): if self.debug is True or 'visit' in str(self.debug): self(''.join([ self.colors['greenbold'], '✔ SUCCESS ', self.colors['reset'], - self.colorized(), + self.colorized(action), '\n', ])) - def fail(self, exception=None): + def fail(self, action, exception=None): if self.debug is True or 'visit' in str(self.debug): self(''.join([ self.colors['redbold'], '✘ FAIL ', self.colors['reset'], - self.colorized(), + self.colorized(action), '\n', ])) - def results(self): + def results(self, action): success = 0 fail = 0 - for result in self.subject.results: + for result in action.results: if result.status == 'success': success += 1 if result.status == 'failure': diff --git a/shlax/shortcuts.py b/shlax/shortcuts.py index 82ff556..efa02f4 100644 --- a/shlax/shortcuts.py +++ b/shlax/shortcuts.py @@ -5,3 +5,8 @@ from .targets.stub import Stub from .actions.packages import Packages from .actions.run import Run +from .actions.pip import Pip +from .actions.parallel import Parallel + +from .container import Container +from .pod import Pod diff --git a/shlax/targets/base.py b/shlax/targets/base.py index d737a15..1ca6402 100644 --- a/shlax/targets/base.py +++ b/shlax/targets/base.py @@ -7,11 +7,10 @@ from ..result import Result, Results class Target: - def __init__(self, *actions, **options): + def __init__(self, *actions): self.actions = actions - self.options = options self.results = [] - self.output = Output(self, **self.options) + self.output = Output() self.parent = None @property @@ -32,13 +31,11 @@ class Target: for action in actions or self.actions: result = Result(self, action) - - self.output = Output(action, **self.options) - self.output.start() + self.output.start(action) try: await action(target=self) except Exception as e: - self.output.fail(e) + self.output.fail(action, e) result.status = 'failure' result.exception = e if actions: @@ -47,7 +44,7 @@ class Target: else: break else: - self.output.success() + self.output.success(action) result.status = 'success' finally: self.caller.results.append(result) @@ -55,7 +52,7 @@ class Target: clean = getattr(action, 'clean', None) if clean: action.result = result - self.output.clean() + self.output.clean(action) await clean(self) async def rexec(self, *args, **kwargs): @@ -77,4 +74,4 @@ class Target: return result async def exec(self, *args, **kwargs): - raise NotImplemented() + raise Exception(f'{self} should run in Localhost() or Stub()') diff --git a/shlax/targets/buildah.py b/shlax/targets/buildah.py index d7cfb78..179fd69 100644 --- a/shlax/targets/buildah.py +++ b/shlax/targets/buildah.py @@ -12,8 +12,7 @@ class Buildah(Target): def __init__(self, *actions, base=None, commit=None, - cmd=None, - **options): + cmd=None): self.base = base or 'alpine' self.image = Image(commit) if commit else None @@ -28,7 +27,7 @@ class Buildah(Target): # Always consider localhost as parent for now self.parent = Target() - super().__init__(*actions, **options) + super().__init__(*actions) def is_runnable(self): return Proc.test or os.getuid() == 0 @@ -39,7 +38,9 @@ class Buildah(Target): return 'Buildah image builder' async def __call__(self, *actions, target=None): - self.parent = target + if target: + self.parent = target + if not self.is_runnable(): os.execvp('buildah', ['buildah', 'unshare'] + sys.argv) # program has been replaced