Merge branch 'jpic' into 'master'
yourlabs/shlax auto build See merge request oss/shlax!1
This commit is contained in:
commit
87ac000e87
@ -1,14 +1,15 @@
|
|||||||
build:
|
build:
|
||||||
|
cache:
|
||||||
|
key: cache
|
||||||
|
paths: [.cache]
|
||||||
image: yourlabs/shlax
|
image: yourlabs/shlax
|
||||||
script: pip install -U .[test] && py.test -svv tests
|
script: pip install -U --user -e . && CACHE_DIR=$(pwd)/.cache ./shlaxfile.py -d
|
||||||
stage: test
|
shlax build push
|
||||||
|
stage: build
|
||||||
pypi:
|
pypi:
|
||||||
image: yourlabs/python
|
image: yourlabs/python
|
||||||
only:
|
only: [tags]
|
||||||
- tags
|
|
||||||
script: pypi-release
|
script: pypi-release
|
||||||
stage: deploy
|
stage: deploy
|
||||||
test:
|
test: {image: yourlabs/python, script: 'pip install -U --user -e .[test] && py.test
|
||||||
image: yourlabs/python
|
-svv tests', stage: build}
|
||||||
script: pip install -U .[test] && py.test -svv tests
|
|
||||||
stage: test
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
- id: shlaxfile-gitlabci
|
- id: shlaxfile-gitlabci
|
||||||
name: Regenerate .gitlab-ci.yml
|
name: Regenerate .gitlab-ci.yml
|
||||||
description: Regenerate gitlabci
|
description: Regenerate gitlabci
|
||||||
entry: pip install -e . && ./shlaxfile.py gitlabci
|
entry: ./shlaxfile.py gitlabci
|
||||||
language: python
|
language: python
|
||||||
|
|||||||
3
setup.py
3
setup.py
@ -7,6 +7,9 @@ setup(
|
|||||||
setup_requires='setupmeta',
|
setup_requires='setupmeta',
|
||||||
install_requires=['cli2'],
|
install_requires=['cli2'],
|
||||||
extras_require=dict(
|
extras_require=dict(
|
||||||
|
full=[
|
||||||
|
'pyyaml',
|
||||||
|
],
|
||||||
test=[
|
test=[
|
||||||
'pytest',
|
'pytest',
|
||||||
'pytest-cov',
|
'pytest-cov',
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
from .commit import Commit
|
|
||||||
from .copy import Copy
|
from .copy import Copy
|
||||||
from .packages import Packages # noqa
|
from .packages import Packages # noqa
|
||||||
from .base import Action # noqa
|
from .base import Action # noqa
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ..output import Output
|
from ..output import Output
|
||||||
@ -164,16 +166,46 @@ class Action:
|
|||||||
def callable(self):
|
def callable(self):
|
||||||
from ..targets import Localhost
|
from ..targets import Localhost
|
||||||
async def cb(*a, **k):
|
async def cb(*a, **k):
|
||||||
return await Localhost(self, quiet=True)(*a, **k)
|
from shlax.cli import cli
|
||||||
|
script = Localhost(self, quiet=True)
|
||||||
|
result = await script(*a, **k)
|
||||||
|
|
||||||
|
success = functools.reduce(
|
||||||
|
lambda a, b: a + b,
|
||||||
|
[1 for c in script.children() if c.status == 'success'] or [0])
|
||||||
|
if success:
|
||||||
|
script.output.success(f'{success} PASS')
|
||||||
|
|
||||||
|
failures = functools.reduce(
|
||||||
|
lambda a, b: a + b,
|
||||||
|
[1 for c in script.children() if c.status == 'fail'] or [0])
|
||||||
|
if failures:
|
||||||
|
script.output.fail(f'{failures} FAIL')
|
||||||
|
cli.exit_code = failures
|
||||||
|
return result
|
||||||
return cb
|
return cb
|
||||||
|
|
||||||
def kwargs_output(self):
|
def kwargs_output(self):
|
||||||
return self.kwargs
|
return self.kwargs
|
||||||
|
|
||||||
def action(self, action, *args, **kwargs):
|
def action(self, action, *args, **kwargs):
|
||||||
|
if isinstance(action, str):
|
||||||
|
import cli2
|
||||||
|
a = cli2.Callable.factory(action).target
|
||||||
|
if not a:
|
||||||
|
a = cli2.Callable.factory(
|
||||||
|
'.'.join(['shlax', action])
|
||||||
|
).target
|
||||||
|
if a:
|
||||||
|
action = a
|
||||||
|
|
||||||
p = action(*args, **kwargs)
|
p = action(*args, **kwargs)
|
||||||
for parent in self.parents():
|
for parent in self.parents():
|
||||||
if hasattr(parent, 'actions'):
|
if hasattr(parent, 'actions'):
|
||||||
break
|
break
|
||||||
p.parent = parent
|
p.parent = parent
|
||||||
|
if 'actions' not in self.__dict__:
|
||||||
|
# "mutate" to Strategy
|
||||||
|
from ..strategies.script import Actions
|
||||||
|
self.actions = Actions(self, [p])
|
||||||
return p
|
return p
|
||||||
|
|||||||
@ -1,87 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from .base import Action
|
|
||||||
|
|
||||||
from ..exceptions import WrongResult
|
|
||||||
|
|
||||||
CI_VARS = (
|
|
||||||
# gitlab
|
|
||||||
'CI_COMMIT_SHORT_SHA',
|
|
||||||
'CI_COMMIT_REF_NAME',
|
|
||||||
'CI_COMMIT_TAG',
|
|
||||||
# CircleCI
|
|
||||||
'CIRCLE_SHA1',
|
|
||||||
'CIRCLE_TAG',
|
|
||||||
'CIRCLE_BRANCH',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Commit(Action):
|
|
||||||
def __init__(self, repo, tags=None, format=None, push=None, registry=None):
|
|
||||||
self.repo = repo
|
|
||||||
self.registry = registry or 'localhost'
|
|
||||||
self.push = push or os.getenv('CI')
|
|
||||||
|
|
||||||
# figure out registry host
|
|
||||||
if '/' in self.repo and not registry:
|
|
||||||
first = self.repo.split('/')[0]
|
|
||||||
if '.' in first or ':' in first:
|
|
||||||
self.registry = self.repo.split('/')[0]
|
|
||||||
|
|
||||||
# docker.io currently has issues with oci format
|
|
||||||
self.format = format or 'oci'
|
|
||||||
if self.registry == 'docker.io':
|
|
||||||
self.format = 'docker'
|
|
||||||
|
|
||||||
self.tags = tags or []
|
|
||||||
|
|
||||||
# figure tags from CI vars
|
|
||||||
if not self.tags:
|
|
||||||
for name in CI_VARS:
|
|
||||||
value = os.getenv(name)
|
|
||||||
if value:
|
|
||||||
self.tags.append(value)
|
|
||||||
|
|
||||||
# filter out tags which resolved to None
|
|
||||||
self.tags = [t for t in self.tags if t is not None]
|
|
||||||
|
|
||||||
# default tag by default ...
|
|
||||||
if not self.tags:
|
|
||||||
self.tags = ['latest']
|
|
||||||
|
|
||||||
async def call(self, *args, ctr=None, **kwargs):
|
|
||||||
self.sha = (await self.parent.parent.exec(
|
|
||||||
'buildah',
|
|
||||||
'commit',
|
|
||||||
'--format=' + self.format,
|
|
||||||
ctr,
|
|
||||||
)).out
|
|
||||||
|
|
||||||
if 'master' in self.tags:
|
|
||||||
self.tags.append('latest')
|
|
||||||
|
|
||||||
if self.tags:
|
|
||||||
tags = ' '.join([f'{self.repo}:{tag}' for tag in self.tags])
|
|
||||||
await script.exec('buildah', 'tag', self.sha, self.repo, tags)
|
|
||||||
|
|
||||||
if self.push:
|
|
||||||
user = os.getenv('DOCKER_USER')
|
|
||||||
passwd = os.getenv('DOCKER_PASS')
|
|
||||||
if user and passwd and os.getenv('CI') and self.registry:
|
|
||||||
await script.exec(
|
|
||||||
'podman',
|
|
||||||
'login',
|
|
||||||
'-u',
|
|
||||||
user,
|
|
||||||
'-p',
|
|
||||||
passwd,
|
|
||||||
self.registry,
|
|
||||||
)
|
|
||||||
|
|
||||||
for tag in self.tags:
|
|
||||||
await script.exec('podman', 'push', f'{self.repo}:{tag}')
|
|
||||||
await script.umount()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'Commit({self.registry}/{self.repo}:{self.tags})'
|
|
||||||
@ -113,7 +113,7 @@ class Packages(Action):
|
|||||||
else:
|
else:
|
||||||
mgr = await self.which(*self.mgrs.keys())
|
mgr = await self.which(*self.mgrs.keys())
|
||||||
if mgr:
|
if mgr:
|
||||||
self.mgr = mgr.split('/')[-1]
|
self.mgr = mgr[0].split('/')[-1]
|
||||||
|
|
||||||
if not self.mgr:
|
if not self.mgr:
|
||||||
raise Exception('Packages does not yet support this distro')
|
raise Exception('Packages does not yet support this distro')
|
||||||
@ -121,6 +121,7 @@ class Packages(Action):
|
|||||||
self.cmds = self.mgrs[self.mgr]
|
self.cmds = self.mgrs[self.mgr]
|
||||||
if not getattr(self, '_packages_upgraded', None):
|
if not getattr(self, '_packages_upgraded', None):
|
||||||
await self.update()
|
await self.update()
|
||||||
|
if self.kwargs.get('upgrade', True):
|
||||||
await self.rexec(self.cmds['upgrade'])
|
await self.rexec(self.cmds['upgrade'])
|
||||||
self._packages_upgraded = True
|
self._packages_upgraded = True
|
||||||
|
|
||||||
@ -141,13 +142,13 @@ class Packages(Action):
|
|||||||
cachedir = os.path.join(self.cache_root, self.mgr)
|
cachedir = os.path.join(self.cache_root, self.mgr)
|
||||||
await self.mount(cachedir, '/var/cache/apk')
|
await self.mount(cachedir, '/var/cache/apk')
|
||||||
# special step to enable apk cache
|
# special step to enable apk cache
|
||||||
await self.rexec('ln -s /var/cache/apk /etc/apk/cache')
|
await self.rexec('ln -sf /var/cache/apk /etc/apk/cache')
|
||||||
return cachedir
|
return cachedir
|
||||||
|
|
||||||
async def dnf_setup(self):
|
async def dnf_setup(self):
|
||||||
cachedir = os.path.join(self.cache_root, self.mgr)
|
cachedir = os.path.join(self.cache_root, self.mgr)
|
||||||
await self.mount(cachedir, f'/var/cache/{self.mgr}')
|
await self.mount(cachedir, f'/var/cache/{self.mgr}')
|
||||||
await self.run('echo keepcache=True >> /etc/dnf/dnf.conf')
|
await self.rexec('echo keepcache=True >> /etc/dnf/dnf.conf')
|
||||||
return cachedir
|
return cachedir
|
||||||
|
|
||||||
async def apt_setup(self):
|
async def apt_setup(self):
|
||||||
|
|||||||
@ -5,25 +5,29 @@ from .base import Action
|
|||||||
|
|
||||||
|
|
||||||
class Pip(Action):
|
class Pip(Action):
|
||||||
packages = dict(
|
|
||||||
apt=['python3-pip'],
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *pip_packages, pip=None, requirements=None):
|
def __init__(self, *pip_packages, pip=None, requirements=None):
|
||||||
self.pip_packages = pip_packages
|
|
||||||
self.requirements = requirements
|
self.requirements = requirements
|
||||||
super().__init__(*pip_packages, pip=pip, requirements=requirements)
|
super().__init__(*pip_packages, pip=pip, requirements=requirements)
|
||||||
|
|
||||||
async def call(self, *args, **kwargs):
|
async def call(self, *args, **kwargs):
|
||||||
self.pip = await self.which('pip3', 'pip', 'pip2')
|
pip = self.kwargs.get('pip', None)
|
||||||
if not self.pip:
|
if not pip:
|
||||||
|
pip = await self.which('pip3', 'pip', 'pip2')
|
||||||
|
if pip:
|
||||||
|
pip = pip[0]
|
||||||
|
else:
|
||||||
from .packages import Packages
|
from .packages import Packages
|
||||||
action = self.action(Packages, 'python3,apk', 'python3-pip,apt', args=args, kwargs=kwargs)
|
action = self.action(
|
||||||
|
Packages,
|
||||||
|
'python3,apk', 'python3-pip,apt',
|
||||||
|
args=args, kwargs=kwargs
|
||||||
|
)
|
||||||
await action(*args, **kwargs)
|
await action(*args, **kwargs)
|
||||||
|
pip = await self.which('pip3', 'pip', 'pip2')
|
||||||
self.pip = await self.which('pip3', 'pip', 'pip2')
|
if not pip:
|
||||||
if not self.pip:
|
|
||||||
raise Exception('Could not install a pip command')
|
raise Exception('Could not install a pip command')
|
||||||
|
else:
|
||||||
|
pip = pip[0]
|
||||||
|
|
||||||
if 'CACHE_DIR' in os.environ:
|
if 'CACHE_DIR' in os.environ:
|
||||||
cache = os.path.join(os.getenv('CACHE_DIR'), 'pip')
|
cache = os.path.join(os.getenv('CACHE_DIR'), 'pip')
|
||||||
@ -33,20 +37,21 @@ class Pip(Action):
|
|||||||
if getattr(self, 'mount', None):
|
if getattr(self, 'mount', None):
|
||||||
# we are in a target which shares a mount command
|
# we are in a target which shares a mount command
|
||||||
await self.mount(cache, '/root/.cache/pip')
|
await self.mount(cache, '/root/.cache/pip')
|
||||||
await self.exec(f'{self.pip} install --upgrade pip')
|
await self.exec(f'{pip} install --upgrade pip')
|
||||||
|
|
||||||
# https://github.com/pypa/pip/issues/5599
|
# https://github.com/pypa/pip/issues/5599
|
||||||
self.pip = 'python3 -m pip'
|
if 'pip' not in self.kwargs:
|
||||||
|
pip = 'python3 -m pip'
|
||||||
|
|
||||||
source = [p for p in self.pip_packages if p.startswith('/')]
|
source = [p for p in self.args if p.startswith('/') or p.startswith('.')]
|
||||||
if source:
|
if source:
|
||||||
await self.exec(
|
await self.exec(
|
||||||
f'{self.pip} install --upgrade --editable {" ".join(source)}'
|
f'{pip} install --upgrade --editable {" ".join(source)}'
|
||||||
)
|
)
|
||||||
|
|
||||||
nonsource = [p for p in self.pip_packages if not p.startswith('/')]
|
nonsource = [p for p in self.args if not p.startswith('/')]
|
||||||
if nonsource:
|
if nonsource:
|
||||||
await self.exec(f'{self.pip} install --upgrade {" ".join(nonsource)}')
|
await self.exec(f'{pip} install --upgrade {" ".join(nonsource)}')
|
||||||
|
|
||||||
if self.requirements:
|
if self.requirements:
|
||||||
await self.exec(f'{self.pip} install --upgrade -r {self.requirements}')
|
await self.exec(f'{pip} install --upgrade -r {self.requirements}')
|
||||||
|
|||||||
@ -1,6 +1,18 @@
|
|||||||
|
from ..targets.buildah import Buildah
|
||||||
|
from ..targets.docker import Docker
|
||||||
|
|
||||||
from .base import Action
|
from .base import Action
|
||||||
|
|
||||||
|
|
||||||
class Run(Action):
|
class Run(Action):
|
||||||
async def call(self, *args, **kwargs):
|
async def call(self, *args, **kwargs):
|
||||||
|
image = self.kwargs.get('image', None)
|
||||||
|
if not image:
|
||||||
return await self.exec(*self.args, **self.kwargs)
|
return await self.exec(*self.args, **self.kwargs)
|
||||||
|
if isinstance(image, Buildah):
|
||||||
|
breakpoint()
|
||||||
|
result = await self.action(image, *args, **kwargs)
|
||||||
|
|
||||||
|
return await Docker(
|
||||||
|
image=image,
|
||||||
|
).exec(*args, **kwargs)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ shlax is a micro-framework to orchestrate commands.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import cli2
|
import cli2
|
||||||
|
import copy
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@ -130,8 +131,10 @@ class ConsoleScript(cli2.ConsoleScript):
|
|||||||
return super().__call__(*args, **kwargs)
|
return super().__call__(*args, **kwargs)
|
||||||
|
|
||||||
def call(self, command):
|
def call(self, command):
|
||||||
|
kwargs = copy.copy(self.parser.funckwargs)
|
||||||
|
kwargs.update(self.parser.options)
|
||||||
try:
|
try:
|
||||||
return super().call(command)
|
return command(*self.parser.funcargs, **kwargs)
|
||||||
except WrongResult as e:
|
except WrongResult as e:
|
||||||
print(e)
|
print(e)
|
||||||
self.exit_code = e.proc.rc
|
self.exit_code = e.proc.rc
|
||||||
|
|||||||
@ -1,16 +1,37 @@
|
|||||||
|
from copy import deepcopy
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from shlax import *
|
from shlax import *
|
||||||
|
|
||||||
|
|
||||||
class GitLabCIConfig(Script):
|
class GitLabCI(Script):
|
||||||
async def call(self, *args, write=True, **kwargs):
|
async def call(self, *args, write=True, **kwargs):
|
||||||
output = yaml.dump(self.kwargs)
|
output = dict()
|
||||||
|
for key, value in self.kwargs.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
output[key] = deepcopy(value)
|
||||||
|
image = output[key].get('image', 'alpine')
|
||||||
|
if hasattr(image, 'image'):
|
||||||
|
output[key]['image'] = image.image.repository + ':$CI_COMMIT_SHORT_SHA'
|
||||||
|
else:
|
||||||
|
output[key] = value
|
||||||
|
|
||||||
|
output = yaml.dump(output)
|
||||||
if kwargs['debug'] is True:
|
if kwargs['debug'] is True:
|
||||||
self.output(output)
|
self.output(output)
|
||||||
if write:
|
if write:
|
||||||
with open('.gitlab-ci.yml', 'w+') as f:
|
with open('.gitlab-ci.yml', 'w+') as f:
|
||||||
f.write(output)
|
f.write(output)
|
||||||
|
|
||||||
|
from shlax.cli import cli
|
||||||
|
for arg in args:
|
||||||
|
job = self.kwargs[arg]
|
||||||
|
_args = []
|
||||||
|
if not isinstance(job['image'], str):
|
||||||
|
image = str(job['image'].image)
|
||||||
|
else:
|
||||||
|
image = job['image']
|
||||||
|
await self.action('Docker', Run(job['script']), image=image)(*_args, **kwargs)
|
||||||
|
|
||||||
def colorized(self):
|
def colorized(self):
|
||||||
return type(self).__name__
|
return type(self).__name__
|
||||||
|
|||||||
@ -55,3 +55,21 @@ class Image:
|
|||||||
# default tag by default ...
|
# default tag by default ...
|
||||||
if not self.tags:
|
if not self.tags:
|
||||||
self.tags = ['latest']
|
self.tags = ['latest']
|
||||||
|
|
||||||
|
async def __call__(self, action, *args, **kwargs):
|
||||||
|
args = list(args)
|
||||||
|
return await action.exec(*args, **self.kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.repository}:{self.tags[-1]}'
|
||||||
|
|
||||||
|
async def push(self, *args, **kwargs):
|
||||||
|
user = os.getenv('DOCKER_USER')
|
||||||
|
passwd = os.getenv('DOCKER_PASS')
|
||||||
|
action = kwargs.get('action', self)
|
||||||
|
if user and passwd:
|
||||||
|
action.output.cmd('buildah login -u ... -p ...' + self.registry)
|
||||||
|
await action.exec('buildah', 'login', '-u', user, '-p', passwd, self.registry or 'docker.io', debug=False)
|
||||||
|
|
||||||
|
for tag in self.tags:
|
||||||
|
await action.exec('buildah', 'push', f'{self.repository}:{tag}')
|
||||||
|
|||||||
@ -25,16 +25,17 @@ 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 __init__(self, prefix=None, regexps=None, debug=False, write=None, flush=None):
|
def __init__(self, prefix=None, regexps=None, debug=False, write=None, flush=None, **kwargs):
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.prefix_length = 0
|
self.prefix_length = 0
|
||||||
self.regexps = regexps or dict()
|
self.regexps = regexps or dict()
|
||||||
self.write = write or sys.stdout.buffer.write
|
self.write = write or sys.stdout.buffer.write
|
||||||
self.flush = flush or sys.stdout.flush
|
self.flush = flush or sys.stdout.flush
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
def __call__(self, line, highlight=True, flush=True):
|
def prefix_line(self):
|
||||||
if self.prefix and self.prefix not in self.prefixes:
|
if self.prefix not in self.prefixes:
|
||||||
self.prefixes[self.prefix] = self.prefix_colors[len(self.prefixes)]
|
self.prefixes[self.prefix] = self.prefix_colors[len(self.prefixes)]
|
||||||
if len(self.prefix) > self.prefix_length:
|
if len(self.prefix) > self.prefix_length:
|
||||||
self.prefix_length = len(self.prefix)
|
self.prefix_length = len(self.prefix)
|
||||||
@ -44,24 +45,24 @@ class Output:
|
|||||||
if prefix_padding:
|
if prefix_padding:
|
||||||
prefix_padding = ' ' + prefix_padding + ' '
|
prefix_padding = ' ' + prefix_padding + ' '
|
||||||
|
|
||||||
self.write((
|
return [
|
||||||
(
|
prefix_color,
|
||||||
prefix_color
|
prefix_padding,
|
||||||
+ prefix_padding
|
self.prefix,
|
||||||
+ self.prefix
|
' ',
|
||||||
+ ' '
|
self.colors['reset'],
|
||||||
+ self.colors['reset']
|
'| '
|
||||||
+ '| '
|
]
|
||||||
if self.prefix
|
|
||||||
else ''
|
def __call__(self, line, highlight=True, flush=True):
|
||||||
)
|
line = [self.highlight(line) if highlight else line]
|
||||||
+ self.highlight(line, highlight)
|
if self.prefix:
|
||||||
+ self.colors['reset']
|
line = self.prefix_line() + line
|
||||||
).encode('utf8'))
|
line = ''.join(line)
|
||||||
|
|
||||||
|
self.write(line.encode('utf8'))
|
||||||
|
|
||||||
if flush:
|
if flush:
|
||||||
if not line.endswith('\n'):
|
|
||||||
self.write(b'\n')
|
|
||||||
self.flush()
|
self.flush()
|
||||||
|
|
||||||
def cmd(self, line):
|
def cmd(self, line):
|
||||||
@ -70,7 +71,8 @@ class Output:
|
|||||||
+ '\x1b[1;38;5;15m'
|
+ '\x1b[1;38;5;15m'
|
||||||
+ ' '
|
+ ' '
|
||||||
+ self.highlight(line, 'bash')
|
+ self.highlight(line, 'bash')
|
||||||
+ self.colors['reset'],
|
+ self.colors['reset']
|
||||||
|
+ '\n',
|
||||||
highlight=False
|
highlight=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -92,16 +94,28 @@ class Output:
|
|||||||
|
|
||||||
for regexp, colors in self.regexps.items():
|
for regexp, colors in self.regexps.items():
|
||||||
line = re.sub(regexp, colors.format(**self.colors), line)
|
line = re.sub(regexp, colors.format(**self.colors), line)
|
||||||
|
line = line + self.colors['reset']
|
||||||
|
|
||||||
return line
|
return line
|
||||||
|
|
||||||
|
def test(self, action):
|
||||||
|
if self.debug is True:
|
||||||
|
self(''.join([
|
||||||
|
self.colors['purplebold'],
|
||||||
|
'! TEST ',
|
||||||
|
self.colors['reset'],
|
||||||
|
action.colorized(),
|
||||||
|
'\n',
|
||||||
|
]))
|
||||||
|
|
||||||
def clean(self, action):
|
def clean(self, action):
|
||||||
if self.debug is True or 'visit' in str(self.debug):
|
if self.debug is True:
|
||||||
self(''.join([
|
self(''.join([
|
||||||
self.colors['bluebold'],
|
self.colors['bluebold'],
|
||||||
'+ CLEAN ',
|
'+ CLEAN ',
|
||||||
self.colors['reset'],
|
self.colors['reset'],
|
||||||
action.colorized(),
|
action.colorized(),
|
||||||
|
'\n',
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def start(self, action):
|
def start(self, action):
|
||||||
@ -111,6 +125,7 @@ class Output:
|
|||||||
'⚠ START ',
|
'⚠ START ',
|
||||||
self.colors['reset'],
|
self.colors['reset'],
|
||||||
action.colorized(),
|
action.colorized(),
|
||||||
|
'\n',
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def success(self, action):
|
def success(self, action):
|
||||||
@ -119,7 +134,8 @@ class Output:
|
|||||||
self.colors['greenbold'],
|
self.colors['greenbold'],
|
||||||
'✔ SUCCESS ',
|
'✔ SUCCESS ',
|
||||||
self.colors['reset'],
|
self.colors['reset'],
|
||||||
action.colorized(),
|
action.colorized() if hasattr(action, 'colorized') else str(action),
|
||||||
|
'\n',
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def fail(self, action, exception=None):
|
def fail(self, action, exception=None):
|
||||||
@ -128,5 +144,6 @@ class Output:
|
|||||||
self.colors['redbold'],
|
self.colors['redbold'],
|
||||||
'✘ FAIL ',
|
'✘ FAIL ',
|
||||||
self.colors['reset'],
|
self.colors['reset'],
|
||||||
action.colorized(),
|
action.colorized() if hasattr(action, 'colorized') else str(action),
|
||||||
|
'\n',
|
||||||
]))
|
]))
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
from .asyn import Async
|
from .asyn import Async
|
||||||
from .script import Script
|
from .script import Script
|
||||||
|
from .pod import Pod, Container
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from .script import Script
|
|||||||
|
|
||||||
class Async(Script):
|
class Async(Script):
|
||||||
async def call(self, *args, **kwargs):
|
async def call(self, *args, **kwargs):
|
||||||
return asyncio.gather(*[
|
return await asyncio.gather(*[
|
||||||
procs.append(action(*args, **kwargs))
|
action(*args, **kwargs)
|
||||||
for action in self.actions
|
for action in self.actions
|
||||||
])
|
])
|
||||||
|
|||||||
27
shlax/strategies/pod.py
Normal file
27
shlax/strategies/pod.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import os
|
||||||
|
from .script import Script
|
||||||
|
|
||||||
|
|
||||||
|
class Container(Script):
|
||||||
|
async def call(self, *args, **kwargs):
|
||||||
|
if not args or 'build' in args:
|
||||||
|
await self.kwargs['build'](**kwargs)
|
||||||
|
self.image = self.kwargs['build'].image
|
||||||
|
|
||||||
|
if not args or 'test' in args:
|
||||||
|
self.output.test(self)
|
||||||
|
await self.action('Docker',
|
||||||
|
*self.kwargs['test'].actions,
|
||||||
|
image=self.image,
|
||||||
|
mount={'.': '/app'},
|
||||||
|
workdir='/app',
|
||||||
|
)(**kwargs)
|
||||||
|
|
||||||
|
if not args or 'push' in args:
|
||||||
|
await self.image.push(action=self)
|
||||||
|
|
||||||
|
#name = kwargs.get('name', os.getcwd()).split('/')[-1]
|
||||||
|
|
||||||
|
|
||||||
|
class Pod(Script):
|
||||||
|
pass
|
||||||
@ -29,4 +29,6 @@ class Script(Action):
|
|||||||
|
|
||||||
async def call(self, *args, **kwargs):
|
async def call(self, *args, **kwargs):
|
||||||
for action in self.actions:
|
for action in self.actions:
|
||||||
await action(*args, **kwargs)
|
result = await action(*args, **kwargs)
|
||||||
|
if action.status != 'success':
|
||||||
|
break
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
from .buildah import Buildah
|
from .buildah import Buildah
|
||||||
|
from .docker import Docker
|
||||||
from .localhost import Localhost
|
from .localhost import Localhost
|
||||||
from .ssh import Ssh
|
from .ssh import Ssh
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
from ..actions.base import Action
|
||||||
|
from ..exceptions import Mistake
|
||||||
from ..proc import Proc
|
from ..proc import Proc
|
||||||
from ..image import Image
|
from ..image import Image
|
||||||
from .localhost import Localhost
|
from .localhost import Localhost
|
||||||
@ -18,16 +20,21 @@ class Buildah(Localhost):
|
|||||||
The build script iterates over visitors and runs the build functions, it
|
The build script iterates over visitors and runs the build functions, it
|
||||||
also provides wrappers around the buildah command.
|
also provides wrappers around the buildah command.
|
||||||
"""
|
"""
|
||||||
contextualize = Localhost.contextualize + ['mnt', 'ctr', 'mount']
|
contextualize = Localhost.contextualize + ['mnt', 'ctr', 'mount', 'image']
|
||||||
|
|
||||||
def __init__(self, base, *args, commit=None, push=False, **kwargs):
|
def __init__(self, base, *args, commit=None, push=False, cmd=None, **kwargs):
|
||||||
|
if isinstance(base, Action):
|
||||||
|
args = [base] + list(args)
|
||||||
|
base = 'alpine' # default selection in case of mistake
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.base = base
|
self.base = base
|
||||||
self.mounts = dict()
|
self.mounts = dict()
|
||||||
self.ctr = None
|
self.ctr = None
|
||||||
self.mnt = None
|
self.mnt = None
|
||||||
self.image = Image(commit) if commit else None
|
self.image = Image(commit) if commit else None
|
||||||
self.push = push or os.getenv('CI')
|
self.config= dict(
|
||||||
|
cmd=cmd or 'sh',
|
||||||
|
)
|
||||||
|
|
||||||
def shargs(self, *args, user=None, buildah=True, **kwargs):
|
def shargs(self, *args, user=None, buildah=True, **kwargs):
|
||||||
if not buildah or args[0].startswith('buildah'):
|
if not buildah or args[0].startswith('buildah'):
|
||||||
@ -52,9 +59,6 @@ class Buildah(Localhost):
|
|||||||
"""Run buildah config."""
|
"""Run buildah config."""
|
||||||
return await self.exec(f'buildah config {line} {self.ctr}', buildah=False)
|
return await self.exec(f'buildah config {line} {self.ctr}', buildah=False)
|
||||||
|
|
||||||
async def mkdir(self, *dirs):
|
|
||||||
return await self.exec(*['mkdir', '-p'] + list(dirs))
|
|
||||||
|
|
||||||
async def copy(self, *args):
|
async def copy(self, *args):
|
||||||
"""Run buildah copy to copy a file from host into container."""
|
"""Run buildah copy to copy a file from host into container."""
|
||||||
src = args[:-1]
|
src = args[:-1]
|
||||||
@ -81,29 +85,14 @@ class Buildah(Localhost):
|
|||||||
await self.exec(f'mount -o bind {src} {target}', buildah=False)
|
await self.exec(f'mount -o bind {src} {target}', buildah=False)
|
||||||
self.mounts[src] = dst
|
self.mounts[src] = dst
|
||||||
|
|
||||||
async def which(self, *cmd):
|
def is_runnable(self):
|
||||||
"""
|
return (
|
||||||
Return the first path to the cmd in the container.
|
|
||||||
|
|
||||||
If cmd argument is a list then it will try all commands.
|
|
||||||
"""
|
|
||||||
paths = (await self.env('PATH')).split(':')
|
|
||||||
for path in paths:
|
|
||||||
for c in cmd:
|
|
||||||
p = os.path.join(self.mnt, path[1:], c)
|
|
||||||
if os.path.exists(p):
|
|
||||||
return p[len(str(self.mnt)):]
|
|
||||||
|
|
||||||
def is_wrapper(self):
|
|
||||||
return not (
|
|
||||||
Proc.test
|
Proc.test
|
||||||
or os.getuid() == 0
|
or os.getuid() == 0
|
||||||
or getattr(self.parent, 'parent', None)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def call(self, *args, **kwargs):
|
async def call(self, *args, **kwargs):
|
||||||
if not self.is_wrapper():
|
if self.is_runnable():
|
||||||
self.ctr = (await self.exec('buildah', 'from', self.base, buildah=False)).out
|
self.ctr = (await self.exec('buildah', 'from', self.base, buildah=False)).out
|
||||||
self.mnt = Path((await self.exec('buildah', 'mount', self.ctr, buildah=False)).out)
|
self.mnt = Path((await self.exec('buildah', 'mount', self.ctr, buildah=False)).out)
|
||||||
result = await super().call(*args, **kwargs)
|
result = await super().call(*args, **kwargs)
|
||||||
@ -124,21 +113,16 @@ class Buildah(Localhost):
|
|||||||
argv += [
|
argv += [
|
||||||
cli.parser.command.name, # script name ?
|
cli.parser.command.name, # script name ?
|
||||||
]
|
]
|
||||||
self.output(' '.join(argv), 'EXECUTION', flush=True)
|
|
||||||
|
|
||||||
proc = await asyncio.create_subprocess_shell(
|
await self.exec(*argv)
|
||||||
shlex.join(argv),
|
|
||||||
stderr=sys.stderr,
|
|
||||||
stdin=sys.stdin,
|
|
||||||
stdout=sys.stdout,
|
|
||||||
)
|
|
||||||
await proc.communicate()
|
|
||||||
cli.exit_code = await proc.wait()
|
|
||||||
|
|
||||||
async def commit(self):
|
async def commit(self):
|
||||||
if not self.image:
|
if not self.image:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
for key, value in self.config.items():
|
||||||
|
await self.exec(f'buildah config --{key} "{value}" {self.ctr}')
|
||||||
|
|
||||||
self.sha = (await self.exec(
|
self.sha = (await self.exec(
|
||||||
'buildah',
|
'buildah',
|
||||||
'commit',
|
'commit',
|
||||||
@ -148,36 +132,22 @@ class Buildah(Localhost):
|
|||||||
)).out
|
)).out
|
||||||
|
|
||||||
if self.image.tags:
|
if self.image.tags:
|
||||||
tags = ' '.join([f'{self.image.repository}:{tag}' for tag in self.image.tags])
|
tags = [f'{self.image.repository}:{tag}' for tag in self.image.tags]
|
||||||
await self.exec('buildah', 'tag', self.sha, self.image.repository, tags, buildah=False)
|
else:
|
||||||
|
tags = [self.image.repository]
|
||||||
|
|
||||||
if self.push:
|
for tag in tags:
|
||||||
user = os.getenv('DOCKER_USER')
|
await self.exec('buildah', 'tag', self.sha, tag, buildah=False)
|
||||||
passwd = os.getenv('DOCKER_PASS')
|
|
||||||
if user and passwd and os.getenv('CI') and self.registry:
|
|
||||||
await self.exec(
|
|
||||||
'podman',
|
|
||||||
'login',
|
|
||||||
'-u',
|
|
||||||
user,
|
|
||||||
'-p',
|
|
||||||
passwd,
|
|
||||||
self.registry,
|
|
||||||
buildah=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
for tag in self.image.tags:
|
|
||||||
await self.exec('podman', 'push', f'{self.image.repository}:{tag}', buildah=False)
|
|
||||||
|
|
||||||
async def clean(self, *args, **kwargs):
|
async def clean(self, *args, **kwargs):
|
||||||
if self.is_wrapper():
|
if self.is_runnable():
|
||||||
return
|
|
||||||
|
|
||||||
for src, dst in self.mounts.items():
|
for src, dst in self.mounts.items():
|
||||||
await self.exec('umount', self.mnt / str(dst)[1:], buildah=False)
|
await self.exec('umount', self.mnt / str(dst)[1:], buildah=False)
|
||||||
|
|
||||||
if self.status == 'success':
|
if self.status == 'success':
|
||||||
await self.commit()
|
await self.commit()
|
||||||
|
if 'push' in args:
|
||||||
|
await self.image.push(action=self)
|
||||||
|
|
||||||
if self.mnt is not None:
|
if self.mnt is not None:
|
||||||
await self.exec('buildah', 'umount', self.ctr, buildah=False)
|
await self.exec('buildah', 'umount', self.ctr, buildah=False)
|
||||||
|
|||||||
76
shlax/targets/docker.py
Normal file
76
shlax/targets/docker.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import asyncio
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ..image import Image
|
||||||
|
from .localhost import Localhost
|
||||||
|
|
||||||
|
|
||||||
|
class Docker(Localhost):
|
||||||
|
contextualize = Localhost.contextualize + ['mnt', 'ctr', 'mount']
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.image = kwargs.get('image', 'alpine')
|
||||||
|
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':
|
||||||
|
return args, kwargs
|
||||||
|
|
||||||
|
extra = []
|
||||||
|
if 'user' in kwargs:
|
||||||
|
extra += ['--user', kwargs.pop('user')]
|
||||||
|
|
||||||
|
args, kwargs = super().shargs(*args, **kwargs)
|
||||||
|
|
||||||
|
if self.context['ctr']:
|
||||||
|
executor = 'exec'
|
||||||
|
extra = [self.context['ctr']]
|
||||||
|
return [self.kwargs.get('docker', 'docker'), executor, '-t'] + extra + list(args), kwargs
|
||||||
|
|
||||||
|
executor = 'run'
|
||||||
|
cwd = os.getcwd()
|
||||||
|
if daemon:
|
||||||
|
extra += ['-d']
|
||||||
|
extra = extra + ['-v', f'{cwd}:{cwd}', '-w', f'{cwd}']
|
||||||
|
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]
|
||||||
|
|
||||||
|
if 'recreate' in args and self.context['ctr']:
|
||||||
|
await self.exec('docker', 'rm', '-f', self.context['ctr'])
|
||||||
|
self.context['ctr'] = None
|
||||||
|
|
||||||
|
if self.context['ctr']:
|
||||||
|
self.context['ctr'] = (await self.exec('docker', 'start', name)).out
|
||||||
|
return await super().call(*args, **kwargs)
|
||||||
|
|
||||||
|
async def copy(self, *args):
|
||||||
|
src = args[:-1]
|
||||||
|
dst = args[-1]
|
||||||
|
await self.mkdir(dst)
|
||||||
|
|
||||||
|
procs = []
|
||||||
|
for s in src:
|
||||||
|
'''
|
||||||
|
if Path(s).is_dir():
|
||||||
|
await self.mkdir(s)
|
||||||
|
args = ['docker', 'copy', self.ctr, s, Path(dst) / s]
|
||||||
|
else:
|
||||||
|
args = ['docker', 'copy', self.ctr, s, dst]
|
||||||
|
'''
|
||||||
|
args = ['docker', 'cp', s, self.context['ctr'] + ':' + dst]
|
||||||
|
procs.append(self.exec(*args))
|
||||||
|
|
||||||
|
return await asyncio.gather(*procs)
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
from shlax.proc import Proc
|
from shlax.proc import Proc
|
||||||
|
|
||||||
@ -29,7 +30,8 @@ class Localhost(Script):
|
|||||||
return args, kwargs
|
return args, kwargs
|
||||||
|
|
||||||
async def exec(self, *args, **kwargs):
|
async def exec(self, *args, **kwargs):
|
||||||
kwargs.setdefault('debug', self.call_kwargs.get('debug', False))
|
if 'debug' not in kwargs:
|
||||||
|
kwargs['debug'] = getattr(self, 'call_kwargs', {}).get('debug', False)
|
||||||
kwargs.setdefault('output', self.output)
|
kwargs.setdefault('output', self.output)
|
||||||
args, kwargs = self.shargs(*args, **kwargs)
|
args, kwargs = self.shargs(*args, **kwargs)
|
||||||
proc = await Proc(*args, **kwargs)()
|
proc = await Proc(*args, **kwargs)()
|
||||||
@ -50,12 +52,20 @@ class Localhost(Script):
|
|||||||
|
|
||||||
If cmd argument is a list then it will try all commands.
|
If cmd argument is a list then it will try all commands.
|
||||||
"""
|
"""
|
||||||
for path in (await self.env('PATH')).split(':'):
|
proc = await self.exec('type ' + ' '.join(cmd), raises=False)
|
||||||
for c in cmd:
|
result = []
|
||||||
p = os.path.join(self.root, path[1:], c)
|
for res in proc.out.split('\n'):
|
||||||
if os.path.exists(p):
|
match = re.match('([^ ]+) is ([^ ]+)$', res.strip())
|
||||||
return p[len(str(self.root)):]
|
if match:
|
||||||
|
result.append(match.group(1))
|
||||||
|
return result
|
||||||
|
|
||||||
async def copy(self, *args):
|
async def copy(self, *args):
|
||||||
args = ['cp', '-ra'] + list(args)
|
args = ['cp', '-ra'] + list(args)
|
||||||
return await self.exec(*args)
|
return await self.exec(*args)
|
||||||
|
|
||||||
|
async def mount(self, *dirs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def mkdir(self, *dirs):
|
||||||
|
return await self.exec(*['mkdir', '-p'] + list(dirs))
|
||||||
|
|||||||
55
shlaxfile.py
55
shlaxfile.py
@ -3,32 +3,53 @@ from shlax.contrib.gitlab import *
|
|||||||
|
|
||||||
PYTEST = 'py.test -svv tests'
|
PYTEST = 'py.test -svv tests'
|
||||||
|
|
||||||
build = Buildah('alpine',
|
test = Script(
|
||||||
Copy('shlax/', 'setup.py', '/app'),
|
Pip('.[test]'),
|
||||||
Pip('/app'),
|
Run(PYTEST),
|
||||||
commit='yourlabs/shlax',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
gitlabci = GitLabCIConfig(
|
build = Buildah(
|
||||||
build=dict(
|
'quay.io/podman/stable',
|
||||||
stage='test',
|
Packages('python38', 'buildah', 'unzip', 'findutils', 'python3-yaml', upgrade=False),
|
||||||
image='yourlabs/shlax',
|
Async(
|
||||||
script='pip install -U .[test] && ' + PYTEST,
|
# python3.8 on centos with pip dance ...
|
||||||
|
Run('''
|
||||||
|
curl -o setuptools.zip https://files.pythonhosted.org/packages/42/3e/2464120172859e5d103e5500315fb5555b1e908c0dacc73d80d35a9480ca/setuptools-45.1.0.zip
|
||||||
|
unzip setuptools.zip
|
||||||
|
mkdir -p /usr/local/lib/python3.8/site-packages/
|
||||||
|
sh -c "cd setuptools-* && python3.8 setup.py install"
|
||||||
|
easy_install-3.8 pip
|
||||||
|
echo python3.8 -m pip > /usr/bin/pip
|
||||||
|
chmod +x /usr/bin/pip
|
||||||
|
'''),
|
||||||
|
Copy('shlax/', 'setup.py', '/app'),
|
||||||
),
|
),
|
||||||
|
Pip('/app[full]'),
|
||||||
|
commit='docker.io/yourlabs/shlax',
|
||||||
|
workdir='/app',
|
||||||
|
)
|
||||||
|
|
||||||
|
shlax = Container(
|
||||||
|
build=build,
|
||||||
|
test=Script(Run('./shlaxfile.py -d test')),
|
||||||
|
)
|
||||||
|
|
||||||
|
gitlabci = GitLabCI(
|
||||||
test=dict(
|
test=dict(
|
||||||
stage='test',
|
stage='build',
|
||||||
|
script='pip install -U --user -e .[test] && ' + PYTEST,
|
||||||
image='yourlabs/python',
|
image='yourlabs/python',
|
||||||
script='pip install -U .[test] && ' + PYTEST,
|
),
|
||||||
|
build=dict(
|
||||||
|
stage='build',
|
||||||
|
image='yourlabs/shlax',
|
||||||
|
script='pip install -U --user -e . && CACHE_DIR=$(pwd)/.cache ./shlaxfile.py -d shlax build push',
|
||||||
|
cache=dict(paths=['.cache'], key='cache'),
|
||||||
),
|
),
|
||||||
pypi=dict(
|
pypi=dict(
|
||||||
stage='deploy',
|
stage='deploy',
|
||||||
|
only=['tags'],
|
||||||
image='yourlabs/python',
|
image='yourlabs/python',
|
||||||
script='pypi-release',
|
script='pypi-release',
|
||||||
only=['tags']
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
test = Script(
|
|
||||||
gitlabci,
|
|
||||||
Run('gitlab-runner exec docker test'),
|
|
||||||
)
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user