Add Package.upgrade option
This commit is contained in:
parent
852f8551af
commit
407240e2a2
51
README.md
51
README.md
@ -187,12 +187,13 @@ class Docker(Target):
|
|||||||
return await self.parent.exec(*['docker', 'exec', self.name] + args)
|
return await self.parent.exec(*['docker', 'exec', self.name] + args)
|
||||||
```
|
```
|
||||||
|
|
||||||
Don't worry about `self.parent` being set, it is enforced to `Localhost` if
|
This also means that you always need a parent with an exec implementation,
|
||||||
unset so that we always have something that actually spawns a process in the
|
there are two:
|
||||||
chain ;)
|
|
||||||
|
|
||||||
The result of that design is that the following use cases are open for
|
- Localhost, executes on localhost
|
||||||
business:
|
- Stub, for testing
|
||||||
|
|
||||||
|
The result of that design is that the following use cases are available:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# This action installs my favorite package on any distro
|
# This action installs my favorite package on any distro
|
||||||
@ -215,14 +216,48 @@ Ssh(host='yourhost')(build)
|
|||||||
|
|
||||||
# Or on a server behingh a bastion:
|
# Or on a server behingh a bastion:
|
||||||
# ssh yourbastion ssh yourhost build exec apt install python3
|
# 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
|
# That's going to do the same
|
||||||
Ssh(
|
Localhost(Ssh(
|
||||||
Ssh(
|
Ssh(
|
||||||
build,
|
build,
|
||||||
host='yourhost'
|
host='yourhost'
|
||||||
),
|
),
|
||||||
host='bastion'
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
```
|
```
|
||||||
|
|||||||
@ -52,8 +52,9 @@ class Packages:
|
|||||||
|
|
||||||
installed = []
|
installed = []
|
||||||
|
|
||||||
def __init__(self, *packages):
|
def __init__(self, *packages, upgrade=True):
|
||||||
self.packages = []
|
self.packages = []
|
||||||
|
self.upgrade = upgrade
|
||||||
for package in packages:
|
for package in packages:
|
||||||
line = dedent(package).strip().replace('\n', ' ')
|
line = dedent(package).strip().replace('\n', ' ')
|
||||||
self.packages += line.split(' ')
|
self.packages += line.split(' ')
|
||||||
@ -116,7 +117,8 @@ class Packages:
|
|||||||
|
|
||||||
self.cmds = self.mgrs[self.mgr]
|
self.cmds = self.mgrs[self.mgr]
|
||||||
await self.update(target)
|
await self.update(target)
|
||||||
await target.rexec(self.cmds['upgrade'])
|
if self.upgrade:
|
||||||
|
await target.rexec(self.cmds['upgrade'])
|
||||||
|
|
||||||
packages = []
|
packages = []
|
||||||
for package in self.packages:
|
for package in self.packages:
|
||||||
@ -160,4 +162,4 @@ class Packages:
|
|||||||
return self.cache_root + '/pacman'
|
return self.cache_root + '/pacman'
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'Packages({self.packages})'
|
return f'Packages({self.packages}, upgrade={self.upgrade})'
|
||||||
|
|||||||
17
shlax/cli.py
17
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
|
- With the name of a module in shlax.repo: a community maintained shlaxfile
|
||||||
"""
|
"""
|
||||||
import ast
|
import ast
|
||||||
|
import asyncio
|
||||||
import cli2
|
import cli2
|
||||||
import glob
|
import glob
|
||||||
import inspect
|
import inspect
|
||||||
@ -49,7 +50,19 @@ class ConsoleScript(cli2.ConsoleScript):
|
|||||||
mod = importlib.util.module_from_spec(spec)
|
mod = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(mod)
|
spec.loader.exec_module(mod)
|
||||||
for member in members:
|
for member in members:
|
||||||
from shlax.targets.localhost import Localhost
|
subject = getattr(mod, member)
|
||||||
self[member] = cli2.Callable(member, Localhost(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__)
|
cli = ConsoleScript(__doc__)
|
||||||
|
|||||||
@ -25,15 +25,14 @@ class Output:
|
|||||||
def colorize(self, code, content):
|
def colorize(self, code, content):
|
||||||
return self.color(code) + content + self.color()
|
return self.color(code) + content + self.color()
|
||||||
|
|
||||||
def colorized(self):
|
def colorized(self, action):
|
||||||
if hasattr(self.subject, 'colorized'):
|
if hasattr(action, 'colorized'):
|
||||||
return self.subject.colorized(self.colors)
|
return action.colorized(self.colors)
|
||||||
else:
|
else:
|
||||||
return str(self.subject)
|
return str(action)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
subject=None,
|
|
||||||
prefix=None,
|
prefix=None,
|
||||||
regexps=None,
|
regexps=None,
|
||||||
debug='cmd,visit,out',
|
debug='cmd,visit,out',
|
||||||
@ -41,7 +40,6 @@ class Output:
|
|||||||
flush=None,
|
flush=None,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
self.subject = subject
|
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.prefix_length = 0
|
self.prefix_length = 0
|
||||||
@ -114,59 +112,59 @@ class Output:
|
|||||||
|
|
||||||
return line
|
return line
|
||||||
|
|
||||||
def test(self):
|
def test(self, action):
|
||||||
self(''.join([
|
self(''.join([
|
||||||
self.colors['purplebold'],
|
self.colors['purplebold'],
|
||||||
'! TEST ',
|
'! TEST ',
|
||||||
self.colors['reset'],
|
self.colors['reset'],
|
||||||
self.colorized(),
|
self.colorized(action),
|
||||||
'\n',
|
'\n',
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def clean(self):
|
def clean(self, action):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
self(''.join([
|
self(''.join([
|
||||||
self.colors['bluebold'],
|
self.colors['bluebold'],
|
||||||
'+ CLEAN ',
|
'+ CLEAN ',
|
||||||
self.colors['reset'],
|
self.colors['reset'],
|
||||||
self.colorized(),
|
self.colorized(action),
|
||||||
'\n',
|
'\n',
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def start(self):
|
def start(self, action):
|
||||||
if self.debug is True or 'visit' in str(self.debug):
|
if self.debug is True or 'visit' in str(self.debug):
|
||||||
self(''.join([
|
self(''.join([
|
||||||
self.colors['orangebold'],
|
self.colors['orangebold'],
|
||||||
'⚠ START ',
|
'⚠ START ',
|
||||||
self.colors['reset'],
|
self.colors['reset'],
|
||||||
self.colorized(),
|
self.colorized(action),
|
||||||
'\n',
|
'\n',
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def success(self):
|
def success(self, action):
|
||||||
if self.debug is True or 'visit' in str(self.debug):
|
if self.debug is True or 'visit' in str(self.debug):
|
||||||
self(''.join([
|
self(''.join([
|
||||||
self.colors['greenbold'],
|
self.colors['greenbold'],
|
||||||
'✔ SUCCESS ',
|
'✔ SUCCESS ',
|
||||||
self.colors['reset'],
|
self.colors['reset'],
|
||||||
self.colorized(),
|
self.colorized(action),
|
||||||
'\n',
|
'\n',
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def fail(self, exception=None):
|
def fail(self, action, exception=None):
|
||||||
if self.debug is True or 'visit' in str(self.debug):
|
if self.debug is True or 'visit' in str(self.debug):
|
||||||
self(''.join([
|
self(''.join([
|
||||||
self.colors['redbold'],
|
self.colors['redbold'],
|
||||||
'✘ FAIL ',
|
'✘ FAIL ',
|
||||||
self.colors['reset'],
|
self.colors['reset'],
|
||||||
self.colorized(),
|
self.colorized(action),
|
||||||
'\n',
|
'\n',
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def results(self):
|
def results(self, action):
|
||||||
success = 0
|
success = 0
|
||||||
fail = 0
|
fail = 0
|
||||||
for result in self.subject.results:
|
for result in action.results:
|
||||||
if result.status == 'success':
|
if result.status == 'success':
|
||||||
success += 1
|
success += 1
|
||||||
if result.status == 'failure':
|
if result.status == 'failure':
|
||||||
|
|||||||
@ -5,3 +5,8 @@ from .targets.stub import Stub
|
|||||||
|
|
||||||
from .actions.packages import Packages
|
from .actions.packages import Packages
|
||||||
from .actions.run import Run
|
from .actions.run import Run
|
||||||
|
from .actions.pip import Pip
|
||||||
|
from .actions.parallel import Parallel
|
||||||
|
|
||||||
|
from .container import Container
|
||||||
|
from .pod import Pod
|
||||||
|
|||||||
@ -7,11 +7,10 @@ from ..result import Result, Results
|
|||||||
|
|
||||||
|
|
||||||
class Target:
|
class Target:
|
||||||
def __init__(self, *actions, **options):
|
def __init__(self, *actions):
|
||||||
self.actions = actions
|
self.actions = actions
|
||||||
self.options = options
|
|
||||||
self.results = []
|
self.results = []
|
||||||
self.output = Output(self, **self.options)
|
self.output = Output()
|
||||||
self.parent = None
|
self.parent = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -32,13 +31,11 @@ class Target:
|
|||||||
|
|
||||||
for action in actions or self.actions:
|
for action in actions or self.actions:
|
||||||
result = Result(self, action)
|
result = Result(self, action)
|
||||||
|
self.output.start(action)
|
||||||
self.output = Output(action, **self.options)
|
|
||||||
self.output.start()
|
|
||||||
try:
|
try:
|
||||||
await action(target=self)
|
await action(target=self)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.output.fail(e)
|
self.output.fail(action, e)
|
||||||
result.status = 'failure'
|
result.status = 'failure'
|
||||||
result.exception = e
|
result.exception = e
|
||||||
if actions:
|
if actions:
|
||||||
@ -47,7 +44,7 @@ class Target:
|
|||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.output.success()
|
self.output.success(action)
|
||||||
result.status = 'success'
|
result.status = 'success'
|
||||||
finally:
|
finally:
|
||||||
self.caller.results.append(result)
|
self.caller.results.append(result)
|
||||||
@ -55,7 +52,7 @@ class Target:
|
|||||||
clean = getattr(action, 'clean', None)
|
clean = getattr(action, 'clean', None)
|
||||||
if clean:
|
if clean:
|
||||||
action.result = result
|
action.result = result
|
||||||
self.output.clean()
|
self.output.clean(action)
|
||||||
await clean(self)
|
await clean(self)
|
||||||
|
|
||||||
async def rexec(self, *args, **kwargs):
|
async def rexec(self, *args, **kwargs):
|
||||||
@ -77,4 +74,4 @@ class Target:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
async def exec(self, *args, **kwargs):
|
async def exec(self, *args, **kwargs):
|
||||||
raise NotImplemented()
|
raise Exception(f'{self} should run in Localhost() or Stub()')
|
||||||
|
|||||||
@ -12,8 +12,7 @@ class Buildah(Target):
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
*actions,
|
*actions,
|
||||||
base=None, commit=None,
|
base=None, commit=None,
|
||||||
cmd=None,
|
cmd=None):
|
||||||
**options):
|
|
||||||
self.base = base or 'alpine'
|
self.base = base or 'alpine'
|
||||||
self.image = Image(commit) if commit else None
|
self.image = Image(commit) if commit else None
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ class Buildah(Target):
|
|||||||
# Always consider localhost as parent for now
|
# Always consider localhost as parent for now
|
||||||
self.parent = Target()
|
self.parent = Target()
|
||||||
|
|
||||||
super().__init__(*actions, **options)
|
super().__init__(*actions)
|
||||||
|
|
||||||
def is_runnable(self):
|
def is_runnable(self):
|
||||||
return Proc.test or os.getuid() == 0
|
return Proc.test or os.getuid() == 0
|
||||||
@ -39,7 +38,9 @@ class Buildah(Target):
|
|||||||
return 'Buildah image builder'
|
return 'Buildah image builder'
|
||||||
|
|
||||||
async def __call__(self, *actions, target=None):
|
async def __call__(self, *actions, target=None):
|
||||||
self.parent = target
|
if target:
|
||||||
|
self.parent = target
|
||||||
|
|
||||||
if not self.is_runnable():
|
if not self.is_runnable():
|
||||||
os.execvp('buildah', ['buildah', 'unshare'] + sys.argv)
|
os.execvp('buildah', ['buildah', 'unshare'] + sys.argv)
|
||||||
# program has been replaced
|
# program has been replaced
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user