Compare commits
7
Commits
jpic
...
completion
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb4b41e2ff | ||
|
|
16f1bef125 | ||
|
|
2db971234a | ||
|
|
41ec8db301 | ||
|
|
33c37f8e44 | ||
|
|
f5ab14d383 | ||
|
|
97255866f8 |
@@ -0,0 +1,10 @@
|
||||
all:
|
||||
@echo -e "\n\033[1;36m --> Installing the module ...\033[0m\n"
|
||||
pip install --user -e .
|
||||
@echo -ne "\n\033[1;36m"; \
|
||||
read -p " --> Install autocompletion ?[Y|n] " RESP; \
|
||||
echo -e "\n\033[0m"; \
|
||||
case "$$RESP" in \
|
||||
y*|Y*|"")sudo cp -v completion.bash /usr/share/bash-completion/completions/shlax;; \
|
||||
*);; \
|
||||
esac;
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
_action(){
|
||||
COMPREPLY=()
|
||||
cur=${COMP_WORDS[COMP_CWORD]}
|
||||
if [[ "$COMP_CWORD" -eq 1 ]] ; then
|
||||
COMPREPLY=($(compgen -W "$(ls shlax/repo/*.py | sed s/^.*\\/\// | cut -d "." -f 1)" "${cur}"))
|
||||
else
|
||||
action=$(grep "^[^ #)]\w* =" shlax/repo/${COMP_WORDS[1]}.py | cut -d " " -f 1)
|
||||
COMPREPLY=($(compgen -W "$action" "${cur}"))
|
||||
fi
|
||||
}
|
||||
|
||||
complete -F _action shlax
|
||||
@@ -5,7 +5,7 @@ setup(
|
||||
name='shlax',
|
||||
versioning='dev',
|
||||
setup_requires='setupmeta',
|
||||
install_requires=['cli2'],
|
||||
install_requires=['cli2>=1.1.6'],
|
||||
extras_require=dict(
|
||||
full=[
|
||||
'pyyaml',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from .copy import Copy
|
||||
from .packages import Packages # noqa
|
||||
from .base import Action # noqa
|
||||
from .htpasswd import Htpasswd
|
||||
from .run import Run # noqa
|
||||
from .pip import Pip
|
||||
from .service import Service
|
||||
|
||||
+17
-3
@@ -1,3 +1,4 @@
|
||||
from copy import deepcopy
|
||||
import functools
|
||||
import inspect
|
||||
import importlib
|
||||
@@ -25,9 +26,17 @@ class Action:
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, *args, doc=None, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.call_args = []
|
||||
self.call_kwargs = {}
|
||||
self._doc = doc
|
||||
self.menu = {
|
||||
name: value
|
||||
for name, value in kwargs.items()
|
||||
if isinstance(value, Action)
|
||||
}
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
@@ -106,8 +115,8 @@ class Action:
|
||||
return Output(**kwargs)
|
||||
|
||||
async def __call__(self, *args, **kwargs):
|
||||
self.call_args = args
|
||||
self.call_kwargs = kwargs
|
||||
self.call_args = list(self.call_args) + list(args)
|
||||
self.call_kwargs.update(kwargs)
|
||||
self.output = self.output_factory(*args, **kwargs)
|
||||
self.output_start()
|
||||
self.status = 'running'
|
||||
@@ -209,3 +218,8 @@ class Action:
|
||||
from ..strategies.script import Actions
|
||||
self.actions = Actions(self, [p])
|
||||
return p
|
||||
|
||||
def bind(self, *args):
|
||||
clone = deepcopy(self)
|
||||
clone.call_args = args
|
||||
return clone
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import hashlib
|
||||
import secrets
|
||||
import string
|
||||
|
||||
from .base import Action
|
||||
|
||||
|
||||
class Htpasswd(Action):
|
||||
def __init__(self, path, user, *args, **kwargs):
|
||||
self.path = path
|
||||
self.user = user
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def call(self, *args, **kwargs):
|
||||
found = False
|
||||
htpasswd = await self.exec('cat', self.path, raises=False)
|
||||
if htpasswd.rc == 0:
|
||||
for line in htpasswd.out.split('\n'):
|
||||
if line.startswith(self.user + ':'):
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
self.password = ''.join(secrets.choice(
|
||||
string.ascii_letters + string.digits
|
||||
) for i in range(20))
|
||||
hashed = hashlib.sha1(self.password.encode('utf8'))
|
||||
line = f'{self.user}:\\$sha1\\${hashed.hexdigest()}'
|
||||
await self.exec(f'echo {line} >> {self.path}')
|
||||
+34
-94
@@ -10,6 +10,8 @@ import asyncio
|
||||
import cli2
|
||||
import copy
|
||||
import inspect
|
||||
import importlib
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -18,106 +20,35 @@ from .shlaxfile import Shlaxfile
|
||||
from .targets import Localhost
|
||||
|
||||
|
||||
async def runall(*args, **kwargs):
|
||||
for name, action in cli.shlaxfile.actions.items():
|
||||
await Localhost(action)(*args, **kwargs)
|
||||
|
||||
|
||||
@cli2.option('debug', alias='d', help='Display debug output.')
|
||||
async def test(*args, **kwargs):
|
||||
"""Run podctl test over a bunch of paths."""
|
||||
report = []
|
||||
|
||||
for arg in args:
|
||||
candidates = [
|
||||
os.path.join(os.getcwd(), arg, 'pod.py'),
|
||||
os.path.join(os.getcwd(), arg, 'pod_test.py'),
|
||||
]
|
||||
for candidate in candidates:
|
||||
if not os.path.exists(candidate):
|
||||
continue
|
||||
podfile = Podfile.factory(candidate)
|
||||
|
||||
# disable push
|
||||
for name, container in podfile.containers.items():
|
||||
commit = container.visitor('commit')
|
||||
if commit:
|
||||
commit.push = False
|
||||
|
||||
output.print(
|
||||
'\n\x1b[1;38;5;160;48;5;118m BUILD START \x1b[0m'
|
||||
+ ' ' + podfile.path + '\n'
|
||||
)
|
||||
|
||||
old_exit_code = console_script.exit_code
|
||||
console_script.exit_code = 0
|
||||
try:
|
||||
await podfile.pod.script('build')()
|
||||
except Exception as e:
|
||||
report.append(('build ' + candidate, False))
|
||||
continue
|
||||
|
||||
if console_script.exit_code != 0:
|
||||
report.append(('build ' + candidate, False))
|
||||
continue
|
||||
console_script.exit_code = old_exit_code
|
||||
|
||||
for name, test in podfile.tests.items():
|
||||
name = '::'.join([podfile.path, name])
|
||||
output.print(
|
||||
'\n\x1b[1;38;5;160;48;5;118m TEST START \x1b[0m'
|
||||
+ ' ' + name + '\n'
|
||||
)
|
||||
|
||||
try:
|
||||
await test(podfile.pod)
|
||||
except Exception as e:
|
||||
report.append((name, False))
|
||||
output.print('\x1b[1;38;5;15;48;5;196m TEST FAIL \x1b[0m' + name)
|
||||
else:
|
||||
report.append((name, True))
|
||||
output.print('\x1b[1;38;5;200;48;5;44m TEST SUCCESS \x1b[0m' + name)
|
||||
output.print('\n')
|
||||
|
||||
print('\n')
|
||||
|
||||
for name, success in report:
|
||||
if success:
|
||||
output.print('\n\x1b[1;38;5;200;48;5;44m TEST SUCCESS \x1b[0m' + name)
|
||||
else:
|
||||
output.print('\n\x1b[1;38;5;15;48;5;196m TEST FAIL \x1b[0m' + name)
|
||||
|
||||
print('\n')
|
||||
|
||||
success = [*filter(lambda i: i[1], report)]
|
||||
failures = [*filter(lambda i: not i[1], report)]
|
||||
|
||||
output.print(
|
||||
'\n\x1b[1;38;5;200;48;5;44m TEST TOTAL: \x1b[0m'
|
||||
+ str(len(report))
|
||||
)
|
||||
if success:
|
||||
output.print(
|
||||
'\n\x1b[1;38;5;200;48;5;44m TEST SUCCESS: \x1b[0m'
|
||||
+ str(len(success))
|
||||
)
|
||||
if failures:
|
||||
output.print(
|
||||
'\n\x1b[1;38;5;15;48;5;196m TEST FAIL: \x1b[0m'
|
||||
+ str(len(failures))
|
||||
)
|
||||
|
||||
if failures:
|
||||
console_script.exit_code = 1
|
||||
|
||||
|
||||
class ConsoleScript(cli2.ConsoleScript):
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.shlaxfile = None
|
||||
shlaxfile = sys.argv.pop(1) if len(sys.argv) > 1 else ''
|
||||
if os.path.exists(shlaxfile.split('::')[0]):
|
||||
if shlaxfile:
|
||||
if not os.path.exists(shlaxfile):
|
||||
try: # missing shlaxfile, what are we gonna do !!
|
||||
mod = importlib.import_module('shlax.repo.' + shlaxfile)
|
||||
except ImportError:
|
||||
print('Could not find ' + shlaxfile)
|
||||
self.exit_code = 1
|
||||
return
|
||||
shlaxfile = mod.__file__
|
||||
self._doc = inspect.getdoc(mod)
|
||||
|
||||
self.shlaxfile = Shlaxfile()
|
||||
self.shlaxfile.parse(shlaxfile)
|
||||
if 'main' in self.shlaxfile.actions:
|
||||
action = self.shlaxfile.actions['main']
|
||||
for name, child in self.shlaxfile.actions['main'].menu.items():
|
||||
self[name] = cli2.Callable(
|
||||
name,
|
||||
child.callable(),
|
||||
options={
|
||||
k: cli2.Option(name=k, **v)
|
||||
for k, v in action.options.items()
|
||||
},
|
||||
color=getattr(action, 'color', cli2.YELLOW),
|
||||
)
|
||||
for name, action in self.shlaxfile.actions.items():
|
||||
self[name] = cli2.Callable(
|
||||
name,
|
||||
@@ -127,7 +58,16 @@ class ConsoleScript(cli2.ConsoleScript):
|
||||
for k, v in action.options.items()
|
||||
},
|
||||
color=getattr(action, 'color', cli2.YELLOW),
|
||||
doc=inspect.getdoc(getattr(action, name, None)) or action._doc,
|
||||
)
|
||||
else:
|
||||
from shlax import repo
|
||||
path = repo.__path__._path[0]
|
||||
for shlaxfile in glob.glob(os.path.join(path, '*.py')):
|
||||
name = shlaxfile.split('/')[-1].split('.')[0]
|
||||
mod = importlib.import_module('shlax.repo.' + name)
|
||||
self[name] = cli2.Callable(name, mod)
|
||||
|
||||
return super().__call__(*args, **kwargs)
|
||||
|
||||
def call(self, command):
|
||||
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env shlax
|
||||
"""
|
||||
Manage a traefik container maintained by Shlax community.
|
||||
"""
|
||||
|
||||
from shlax import *
|
||||
|
||||
main = Docker(
|
||||
name='traefik',
|
||||
image='traefik:v2.0.0',
|
||||
networks=['web'],
|
||||
command=[
|
||||
'--entrypoints.web.address=:80',
|
||||
'--providers.docker',
|
||||
'--api',
|
||||
],
|
||||
ports=[
|
||||
'80:80',
|
||||
'443:443',
|
||||
],
|
||||
volumes=[
|
||||
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
||||
'/etc/traefik/acme/:/etc/traefik/acme/',
|
||||
'/etc/traefik/htpasswd:/htpasswd:ro',
|
||||
],
|
||||
labels=[
|
||||
'traefik.http.routers.traefik.rule=Host(`{{ url.split("/")[2] }}`)',
|
||||
'traefik.http.routers.traefik.service=api@internal',
|
||||
'traefik.http.routers.traefik.entrypoints=web',
|
||||
],
|
||||
doc='Current traefik instance',
|
||||
)
|
||||
|
||||
install = Script(
|
||||
Htpasswd('./htpasswd', 'root'),
|
||||
main.bind('up'),
|
||||
doc='Deploy a Traefik instance',
|
||||
)
|
||||
|
||||
up = main.bind('up')
|
||||
rm = main.bind('rm')
|
||||
@@ -20,6 +20,7 @@ class Shlaxfile:
|
||||
self.actions[name] = value
|
||||
elif callable(value) and getattr(value, '__name__', '').startswith('test_'):
|
||||
self.tests[value.__name__] = value
|
||||
|
||||
self.paths.append(path)
|
||||
|
||||
@property
|
||||
|
||||
+20
-3
@@ -1,14 +1,31 @@
|
||||
import os
|
||||
from .script import Script
|
||||
from ..image import Image
|
||||
|
||||
|
||||
class Container(Script):
|
||||
"""
|
||||
Wolcome to crazy container control cli
|
||||
|
||||
Such wow
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('start', dict())
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def call(self, *args, **kwargs):
|
||||
if not args or 'build' in args:
|
||||
if step('build'):
|
||||
await self.kwargs['build'](**kwargs)
|
||||
self.image = self.kwargs['build'].image
|
||||
else:
|
||||
self.image = kwargs.get('image', 'alpine')
|
||||
if isinstance(self.image, str):
|
||||
self.image = Image(self.image)
|
||||
|
||||
if not args or 'test' in args:
|
||||
if step('install'):
|
||||
await self.install(*args, **kwargs)
|
||||
|
||||
if step('test'):
|
||||
self.output.test(self)
|
||||
await self.action('Docker',
|
||||
*self.kwargs['test'].actions,
|
||||
@@ -17,7 +34,7 @@ class Container(Script):
|
||||
workdir='/app',
|
||||
)(**kwargs)
|
||||
|
||||
if not args or 'push' in args:
|
||||
if step('push'):
|
||||
await self.image.push(action=self)
|
||||
|
||||
#name = kwargs.get('name', os.getcwd()).split('/')[-1]
|
||||
|
||||
@@ -14,16 +14,17 @@ class Actions(list):
|
||||
self.append(action)
|
||||
|
||||
def append(self, value):
|
||||
value = copy.deepcopy(value)
|
||||
value.parent = self.owner
|
||||
value.status = 'pending'
|
||||
super().append(value)
|
||||
action = copy.deepcopy(value)
|
||||
action.parent = self.owner
|
||||
action.status = 'pending'
|
||||
super().append(action)
|
||||
|
||||
|
||||
class Script(Action):
|
||||
contextualize = ['shargs', 'exec', 'rexec', 'env', 'which', 'copy']
|
||||
|
||||
def __init__(self, *actions, **kwargs):
|
||||
self.home = kwargs.pop('home', os.getcwd())
|
||||
super().__init__(**kwargs)
|
||||
self.actions = Actions(self, actions)
|
||||
|
||||
@@ -32,3 +33,9 @@ class Script(Action):
|
||||
result = await action(*args, **kwargs)
|
||||
if action.status != 'success':
|
||||
break
|
||||
|
||||
def pollute(self, gbls):
|
||||
for name, script in self.kwargs.items():
|
||||
if not isinstance(script, Script):
|
||||
continue
|
||||
gbls[name] = script
|
||||
|
||||
+40
-17
@@ -11,10 +11,12 @@ class Docker(Localhost):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.image = kwargs.get('image', 'alpine')
|
||||
self.name = kwargs.get('name', os.getcwd().split('/')[-1])
|
||||
|
||||
if not isinstance(self.image, Image):
|
||||
self.image = Image(self.image)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
self.context['ctr'] = None
|
||||
|
||||
def shargs(self, *args, daemon=False, **kwargs):
|
||||
if args[0] == 'docker':
|
||||
@@ -26,9 +28,9 @@ class Docker(Localhost):
|
||||
|
||||
args, kwargs = super().shargs(*args, **kwargs)
|
||||
|
||||
if self.context['ctr']:
|
||||
if self.name:
|
||||
executor = 'exec'
|
||||
extra = [self.context['ctr']]
|
||||
extra = [self.name]
|
||||
return [self.kwargs.get('docker', 'docker'), executor, '-t'] + extra + list(args), kwargs
|
||||
|
||||
executor = 'run'
|
||||
@@ -39,23 +41,44 @@ class Docker(Localhost):
|
||||
return [self.kwargs.get('docker', 'docker'), executor, '-t'] + extra + [str(self.image)] + list(args), kwargs
|
||||
|
||||
async def call(self, *args, **kwargs):
|
||||
name = kwargs.get('name', os.getcwd()).split('/')[-1]
|
||||
self.context['ctr'] = (
|
||||
await self.exec(
|
||||
'docker', 'ps', '-aq', '--filter',
|
||||
'name=' + name,
|
||||
raises=False
|
||||
)
|
||||
).out.split('\n')[0]
|
||||
def step(step):
|
||||
return not args or step in args
|
||||
|
||||
if 'recreate' in args and self.context['ctr']:
|
||||
await self.exec('docker', 'rm', '-f', self.context['ctr'])
|
||||
self.context['ctr'] = None
|
||||
# self.name = (
|
||||
# await self.exec(
|
||||
# 'docker', 'ps', '-aq', '--filter',
|
||||
# 'name=' + self.name,
|
||||
# raises=False
|
||||
# )
|
||||
# ).out.split('\n')[0]
|
||||
|
||||
if self.context['ctr']:
|
||||
self.context['ctr'] = (await self.exec('docker', 'start', name)).out
|
||||
if step('rm'):
|
||||
await self.rm(*args, **kwargs)
|
||||
|
||||
if step('down') and self.name:
|
||||
await self.exec('docker', 'down', '-f', self.name)
|
||||
|
||||
if step('up'):
|
||||
await self.up(*args, **kwargs)
|
||||
return await super().call(*args, **kwargs)
|
||||
|
||||
async def rm(self, *args, **kwargs):
|
||||
return await self.exec('docker', 'rm', '-f', self.name)
|
||||
|
||||
async def down(self, *args, **kwargs):
|
||||
"""Remove instance, except persistent data if any"""
|
||||
if self.name:
|
||||
self.name = (await self.exec('docker', 'start', self.name)).out
|
||||
else:
|
||||
self.name = (await self.exec('docker', 'run', '-d', '--name', self.name)).out
|
||||
|
||||
async def up(self, *args, **kwargs):
|
||||
"""Perform start or run"""
|
||||
if self.name:
|
||||
self.name = (await self.exec('docker', 'start', self.name)).out
|
||||
else:
|
||||
self.id = (await self.exec('docker', 'run', '-d', '--name', self.name)).out
|
||||
|
||||
async def copy(self, *args):
|
||||
src = args[:-1]
|
||||
dst = args[-1]
|
||||
@@ -70,7 +93,7 @@ class Docker(Localhost):
|
||||
else:
|
||||
args = ['docker', 'copy', self.ctr, s, dst]
|
||||
'''
|
||||
args = ['docker', 'cp', s, self.context['ctr'] + ':' + dst]
|
||||
args = ['docker', 'cp', s, self.name + ':' + dst]
|
||||
procs.append(self.exec(*args))
|
||||
|
||||
return await asyncio.gather(*procs)
|
||||
|
||||
@@ -8,6 +8,11 @@ from ..strategies.script import Script
|
||||
|
||||
class Localhost(Script):
|
||||
root = '/'
|
||||
contextualize = Script.contextualize + ['home']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.home = kwargs.pop('home', os.getcwd())
|
||||
|
||||
def shargs(self, *args, **kwargs):
|
||||
user = kwargs.pop('user', None)
|
||||
@@ -46,6 +51,9 @@ class Localhost(Script):
|
||||
async def env(self, name):
|
||||
return (await self.exec('echo $' + name)).out
|
||||
|
||||
async def exists(self, *paths):
|
||||
proc = await self.exec('type ' + ' '.join(cmd), raises=False)
|
||||
|
||||
async def which(self, *cmd):
|
||||
"""
|
||||
Return the first path to the cmd in the container.
|
||||
@@ -61,7 +69,10 @@ class Localhost(Script):
|
||||
return result
|
||||
|
||||
async def copy(self, *args):
|
||||
args = ['cp', '-ra'] + list(args)
|
||||
if args[-1].startswith('./'):
|
||||
args = list(args)
|
||||
args[-1] = self.home + '/' + args[-1][2:]
|
||||
args = ['cp', '-rua'] + list(args)
|
||||
return await self.exec(*args)
|
||||
|
||||
async def mount(self, *dirs):
|
||||
|
||||
Reference in New Issue
Block a user