Build script suppotrs packages
This commit is contained in:
commit
d5d924dd06
2
podctl/__init__.py
Normal file
2
podctl/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from .container import Container, switch
|
||||||
|
from .pod import Pod
|
||||||
245
podctl/build.py
Normal file
245
podctl/build.py
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
from glob import glob
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .script import Script
|
||||||
|
|
||||||
|
|
||||||
|
class BuildScript(Script):
|
||||||
|
export = ('base', 'repo', 'tag', 'image')
|
||||||
|
|
||||||
|
def __init__(self, container):
|
||||||
|
super().__init__()
|
||||||
|
self.container = container
|
||||||
|
|
||||||
|
for var in self.export:
|
||||||
|
if var in self.container:
|
||||||
|
self.append(f'{var}="{self.container[var]}"')
|
||||||
|
|
||||||
|
self.append('''
|
||||||
|
mounts=()
|
||||||
|
umounts() {
|
||||||
|
for i in "${mounts[@]}"; do
|
||||||
|
umount $i
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap umounts 0
|
||||||
|
ctr=$(buildah from $base)
|
||||||
|
mnt=$(buildah mount $ctr)
|
||||||
|
mounts=("$mnt" "${mounts[@]}")
|
||||||
|
''')
|
||||||
|
|
||||||
|
if self.container.get('packages'):
|
||||||
|
self.container.packages_install(self)
|
||||||
|
|
||||||
|
def config(self, line):
|
||||||
|
self.append(f'buildah config {line} $ctr')
|
||||||
|
|
||||||
|
def run(self, cmd):
|
||||||
|
self.append('buildah run $ctr -- ' + cmd)
|
||||||
|
|
||||||
|
def copy(self, src, dst):
|
||||||
|
self.append(f'buildah copy $ctr {src} {dst}')
|
||||||
|
|
||||||
|
def mount(self, src, dst):
|
||||||
|
self.run('mkdir -p ' + dst)
|
||||||
|
self.append('mkdir -p ' + src)
|
||||||
|
self.append(f'mount -o bind {src} $mnt{dst}')
|
||||||
|
# for unmount trap
|
||||||
|
self.append('mounts=("$mnt%s" "${mounts[@]}")' % dst)
|
||||||
|
|
||||||
|
|
||||||
|
class Plugins(dict):
|
||||||
|
def __init__(self, *plugins):
|
||||||
|
default_plugins = [
|
||||||
|
PkgPlugin(),
|
||||||
|
UserPlugin(),
|
||||||
|
FsPlugin(),
|
||||||
|
BuildPlugin(),
|
||||||
|
ConfigPlugin(),
|
||||||
|
]
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
|
for plugin in plugins or default_plugins:
|
||||||
|
self.add(plugin)
|
||||||
|
|
||||||
|
def add(self, plugin):
|
||||||
|
plugin.plugins = self
|
||||||
|
self[plugin.name] = plugin
|
||||||
|
|
||||||
|
def __call__(self, method, *args, **kwargs):
|
||||||
|
hook = f'pre_{method}'
|
||||||
|
for plugin in self.values():
|
||||||
|
plugin(hook, *args, **kwargs)
|
||||||
|
|
||||||
|
for plugin in self.values():
|
||||||
|
if hasattr(plugin, method):
|
||||||
|
plugin(method, *args, **kwargs)
|
||||||
|
|
||||||
|
hook = f'post_{method}'
|
||||||
|
for plugin in self.values():
|
||||||
|
plugin(hook, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin:
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return type(self).__name__.replace('Plugin', '').lower()
|
||||||
|
|
||||||
|
def bubble(self, hook, *args, **kwargs):
|
||||||
|
for plugin in self.plugins.values():
|
||||||
|
if plugin is self:
|
||||||
|
continue
|
||||||
|
plugin(hook, _bubble=False, *args, **kwargs)
|
||||||
|
|
||||||
|
def __call__(self, method, *args, **kwargs):
|
||||||
|
_bubble = kwargs.pop('_bubble', True)
|
||||||
|
if _bubble:
|
||||||
|
self.bubble(f'pre_{self.name}_{method}', *args, **kwargs)
|
||||||
|
|
||||||
|
if hasattr(self, method):
|
||||||
|
meth = getattr(self, method)
|
||||||
|
argspec = inspect.getargspec(meth)
|
||||||
|
if argspec.varargs and argspec.keywords:
|
||||||
|
meth(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
numargs = len(argspec.args) - 1 - len(argspec.defaults or [])
|
||||||
|
args = args[:numargs]
|
||||||
|
kwargs = {
|
||||||
|
k: v
|
||||||
|
for k, v in kwargs.items()
|
||||||
|
if k in argspec.args
|
||||||
|
}
|
||||||
|
meth(*args, **kwargs)
|
||||||
|
|
||||||
|
if _bubble:
|
||||||
|
self.bubble(f'post_{self.name}_{method}', *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class BuildPlugin(Plugin):
|
||||||
|
def build(self, script):
|
||||||
|
user = script.service.get('user', None)
|
||||||
|
|
||||||
|
for cmd in script.service['build']:
|
||||||
|
if cmd.startswith('sudo'):
|
||||||
|
if user:
|
||||||
|
script.config(f'--user root')
|
||||||
|
script.run(cmd[5:])
|
||||||
|
else:
|
||||||
|
script.config(f'--user {script.service["user"]}')
|
||||||
|
script.run(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
class FsPlugin(Plugin):
|
||||||
|
def build(self, script):
|
||||||
|
for key, value in script.service.items():
|
||||||
|
if not key.startswith('/'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
parts = key.split(':')
|
||||||
|
dst = parts.pop(0)
|
||||||
|
mode = parts.pop(0) if parts else '0500'
|
||||||
|
script.run(f'mkdir -p {dst}')
|
||||||
|
script.run(f'chmod {mode} $mnt{dst}')
|
||||||
|
|
||||||
|
if value and isinstance(value, list):
|
||||||
|
for item in value:
|
||||||
|
if isinstance(item, str):
|
||||||
|
script.run(f'cp -a {item} $mnt{dst}')
|
||||||
|
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PkgPlugin(Plugin):
|
||||||
|
mgrs = dict(
|
||||||
|
apk=dict(
|
||||||
|
update='apk update',
|
||||||
|
upgrade='apk upgrade',
|
||||||
|
install='apk add',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def pre_build(self, script):
|
||||||
|
for mgr, cmds in self.mgrs.items():
|
||||||
|
cmd = [
|
||||||
|
'podman',
|
||||||
|
'run',
|
||||||
|
script.service['base'],
|
||||||
|
'which',
|
||||||
|
mgr
|
||||||
|
]
|
||||||
|
print('+ ' + ' '.join(cmd))
|
||||||
|
try:
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
script.service.mgr = mgr
|
||||||
|
script.service.cmds = cmds
|
||||||
|
break
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def build(self, script):
|
||||||
|
cache = f'.cache/{script.service.mgr}'
|
||||||
|
script.mount(
|
||||||
|
'$(pwd)/' + cache,
|
||||||
|
f'/var/cache/{script.service.mgr}'
|
||||||
|
)
|
||||||
|
|
||||||
|
cached = False
|
||||||
|
if script.service.mgr == 'apk':
|
||||||
|
# special step to enable apk cache
|
||||||
|
script.run('ln -s /var/cache/apk /etc/apk/cache')
|
||||||
|
for index in glob(cache + '/APKINDEX*'):
|
||||||
|
if time.time() - os.stat(index).st_mtime < 3600:
|
||||||
|
cached = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not cached:
|
||||||
|
script.run(script.service.cmds['update'])
|
||||||
|
|
||||||
|
script.run(script.service.cmds['upgrade'])
|
||||||
|
script.run(' '.join([
|
||||||
|
script.service.cmds['install'],
|
||||||
|
' '.join(script.service.get('packages', []))
|
||||||
|
]))
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigPlugin(Plugin):
|
||||||
|
def post_build(self, script):
|
||||||
|
for value in script.service['ports']:
|
||||||
|
script.config(f'--port {value}')
|
||||||
|
|
||||||
|
for key, value in script.service['env'].items():
|
||||||
|
script.config(f'--env {key}={value}')
|
||||||
|
|
||||||
|
for key, value in script.service['labels'].items():
|
||||||
|
script.config(f'--label {key}={value}')
|
||||||
|
|
||||||
|
for key, value in script.service['annotations'].items():
|
||||||
|
script.config(f'--annotation {key}={value}')
|
||||||
|
|
||||||
|
for volume in script.service['volumes']:
|
||||||
|
if ':' in volume:
|
||||||
|
continue # it's a runtime volume
|
||||||
|
script.config(f'--volume {volume}')
|
||||||
|
|
||||||
|
if 'workdir' in script.service:
|
||||||
|
script.config(f'--workingdir {script.service["workdir"]}')
|
||||||
|
|
||||||
|
|
||||||
|
class UserPlugin(Plugin):
|
||||||
|
def pre_pkg_build(self, script):
|
||||||
|
if script.service.mgr == 'apk':
|
||||||
|
script.service['packages'].append('shadow')
|
||||||
|
|
||||||
|
def build(self, script):
|
||||||
|
script.append(f'''
|
||||||
|
if buildah run $ctr -- id {script.service['user']['id']}; then
|
||||||
|
i=$(buildah run $ctr -- id -n {script.service['user']['id']})
|
||||||
|
buildah run $ctr -- usermod -d {script.service['user']['home']} -l {script.service['user']['id']} $i
|
||||||
|
else
|
||||||
|
buildah run $ctr -- useradd -d {script.service['user']['home']} {script.service['user']['id']}
|
||||||
|
fi
|
||||||
|
''')
|
||||||
122
podctl/console_script.py
Normal file
122
podctl/console_script.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
'''
|
||||||
|
docker & docker-compose frustrated me, podctl unfrustrates me.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import cli2
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
from .pod import Pod
|
||||||
|
|
||||||
|
|
||||||
|
class BuildStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol):
|
||||||
|
def __init__(self, service, *args, **kwargs):
|
||||||
|
self.service = service
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def pipe_data_received(self, fd, data):
|
||||||
|
if fd in (1, 2):
|
||||||
|
for line in data.split(b'\n'):
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
sys.stdout.buffer.write(
|
||||||
|
self.service.name.encode('utf8') + b' | ' + line + b'\n'
|
||||||
|
)
|
||||||
|
sys.stdout.flush()
|
||||||
|
super().pipe_data_received(fd, data)
|
||||||
|
|
||||||
|
|
||||||
|
@cli2.option('debug', help='Print debug output', color=cli2.GREEN, alias='d')
|
||||||
|
async def build(service=None, **kwargs):
|
||||||
|
procs = []
|
||||||
|
for name, container in console_script.pod.items():
|
||||||
|
if not container.base:
|
||||||
|
continue
|
||||||
|
|
||||||
|
script = f'.podctl_build_{name}.sh'
|
||||||
|
with open(script, 'w+') as f:
|
||||||
|
f.write(str(container.script_build()))
|
||||||
|
|
||||||
|
loop = asyncio.events.get_event_loop()
|
||||||
|
protocol_factory = lambda: BuildStreamProtocol(
|
||||||
|
container=container,
|
||||||
|
limit=asyncio.streams._DEFAULT_LIMIT,
|
||||||
|
loop=loop,
|
||||||
|
)
|
||||||
|
transport, protocol = await loop.subprocess_shell(
|
||||||
|
protocol_factory,
|
||||||
|
f'buildah unshare bash -eux {script}',
|
||||||
|
)
|
||||||
|
procs.append(asyncio.subprocess.Process(
|
||||||
|
transport,
|
||||||
|
protocol,
|
||||||
|
loop,
|
||||||
|
))
|
||||||
|
|
||||||
|
for proc in procs:
|
||||||
|
await proc.communicate()
|
||||||
|
|
||||||
|
|
||||||
|
@cli2.option('debug', help='Print debug output', color=cli2.GREEN, alias='d')
|
||||||
|
async def up(service=None, **kwargs):
|
||||||
|
procs = []
|
||||||
|
for name, service in console_script.pod.services.items():
|
||||||
|
if 'base' not in service:
|
||||||
|
continue
|
||||||
|
|
||||||
|
script = f'.podctl_up_{name}.sh'
|
||||||
|
with open(script, 'w+') as f:
|
||||||
|
f.write(str(service.build()))
|
||||||
|
|
||||||
|
loop = asyncio.events.get_event_loop()
|
||||||
|
protocol_factory = lambda: BuildStreamProtocol(
|
||||||
|
service=service,
|
||||||
|
limit=asyncio.streams._DEFAULT_LIMIT,
|
||||||
|
loop=loop,
|
||||||
|
)
|
||||||
|
transport, protocol = await loop.subprocess_shell(
|
||||||
|
protocol_factory,
|
||||||
|
f'bash -eux {script}',
|
||||||
|
)
|
||||||
|
procs.append(asyncio.subprocess.Process(
|
||||||
|
transport,
|
||||||
|
protocol,
|
||||||
|
loop,
|
||||||
|
))
|
||||||
|
|
||||||
|
for proc in procs:
|
||||||
|
await proc.communicate()
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleScript(cli2.ConsoleScript):
|
||||||
|
def __setitem__(self, name, cb):
|
||||||
|
if name != 'help':
|
||||||
|
cli2.option(
|
||||||
|
'file',
|
||||||
|
alias='f',
|
||||||
|
help='Path to pod definition (default: pod.py)',
|
||||||
|
color=cli2.YELLOW,
|
||||||
|
default='pod.py',
|
||||||
|
)(cb.target)
|
||||||
|
cli2.option(
|
||||||
|
'home',
|
||||||
|
alias='h',
|
||||||
|
help=f'Pod home (default is cwd: {os.getcwd()})',
|
||||||
|
color=cli2.YELLOW,
|
||||||
|
default=os.getcwd(),
|
||||||
|
)(cb.target)
|
||||||
|
super().__setitem__(name, cb)
|
||||||
|
|
||||||
|
def call(self, command):
|
||||||
|
if command.name != 'help':
|
||||||
|
self.path = self.parser.options['file']
|
||||||
|
self.home = self.parser.options['home']
|
||||||
|
with open(self.path) as f:
|
||||||
|
self.pod = Pod.factory(self.path)
|
||||||
|
return super().call(command)
|
||||||
|
|
||||||
|
|
||||||
|
console_script = ConsoleScript(__doc__).add_module('podctl.console_script')
|
||||||
109
podctl/container.py
Normal file
109
podctl/container.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import collections
|
||||||
|
import copy
|
||||||
|
from glob import glob
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from .build import BuildScript
|
||||||
|
|
||||||
|
|
||||||
|
PACKAGE_MANAGERS = dict(
|
||||||
|
apk=dict(
|
||||||
|
update='apk update',
|
||||||
|
upgrade='apk upgrade',
|
||||||
|
install='apk add',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Container(collections.UserDict):
|
||||||
|
cfg = dict()
|
||||||
|
|
||||||
|
def __init__(self, profile=None, **cfg):
|
||||||
|
newcfg = copy.deepcopy(self.cfg)
|
||||||
|
newcfg.update(cfg)
|
||||||
|
super().__init__(**newcfg)
|
||||||
|
self.profile = profile or 'default'
|
||||||
|
|
||||||
|
def __getitem__(self, name, type=None):
|
||||||
|
try:
|
||||||
|
result = super().__getitem__(name)
|
||||||
|
except KeyError:
|
||||||
|
if hasattr(self, name + '_get'):
|
||||||
|
result = self[name] = getattr(self, name + '_get')()
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
if isinstance(result, (dict, list, tuple, switch)):
|
||||||
|
return self.switch_value(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def switch_value(self, value):
|
||||||
|
_switch = lambda v: v.value(self) if isinstance(v, switch) else v
|
||||||
|
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return {
|
||||||
|
k: self.switch_value(v)
|
||||||
|
for k, v in value.items()
|
||||||
|
if self.switch_value(v) is not None
|
||||||
|
}
|
||||||
|
elif isinstance(value, (list, tuple)):
|
||||||
|
return [
|
||||||
|
self.switch_value(i)
|
||||||
|
for i in value
|
||||||
|
if self.switch_value(i) is not None
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
return _switch(value)
|
||||||
|
|
||||||
|
def script_build(self):
|
||||||
|
return BuildScript(self)
|
||||||
|
|
||||||
|
def package_manager_get(self):
|
||||||
|
for mgr in PACKAGE_MANAGERS.keys():
|
||||||
|
cmd = ['podman', 'run', self['base'], 'which', mgr]
|
||||||
|
try:
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
return mgr
|
||||||
|
break
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
continue
|
||||||
|
raise Exception('Package manager not supported yet')
|
||||||
|
|
||||||
|
def package_manager_cmd(self, cmd):
|
||||||
|
return PACKAGE_MANAGERS[self['package_manager']][cmd]
|
||||||
|
|
||||||
|
def packages_install(self, script):
|
||||||
|
cache = f'.cache/{self["package_manager"]}'
|
||||||
|
script.mount(
|
||||||
|
'$(pwd)/' + cache,
|
||||||
|
f'/var/cache/{self["package_manager"]}'
|
||||||
|
)
|
||||||
|
|
||||||
|
cached = False
|
||||||
|
if self['package_manager'] == 'apk':
|
||||||
|
# special step to enable apk cache
|
||||||
|
script.run('ln -s /var/cache/apk /etc/apk/cache')
|
||||||
|
script.append(f'''
|
||||||
|
if [ -n "$(find .cache/apk/ -name APKINDEX.* -mtime +3)" ]; then
|
||||||
|
buildah run $ctr -- {self.package_manager_cmd("update")}
|
||||||
|
fi
|
||||||
|
''')
|
||||||
|
|
||||||
|
script.run(self.package_manager_cmd('upgrade'))
|
||||||
|
script.run(' '.join([
|
||||||
|
self.package_manager_cmd('install'),
|
||||||
|
' '.join(self.get('packages', []))
|
||||||
|
]))
|
||||||
|
|
||||||
|
|
||||||
|
class switch:
|
||||||
|
def __init__(self, **values):
|
||||||
|
"""Instanciate a switch to vary values based on container profile."""
|
||||||
|
self.values = values
|
||||||
|
|
||||||
|
def value(self, container):
|
||||||
|
"""Return value from container profile or default."""
|
||||||
|
return self.values.get(
|
||||||
|
container.profile,
|
||||||
|
self.values.get('default', None)
|
||||||
|
)
|
||||||
11
podctl/pod.py
Normal file
11
podctl/pod.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import collections
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
|
||||||
|
class Pod(collections.UserDict):
|
||||||
|
@classmethod
|
||||||
|
def factory(cls, path):
|
||||||
|
spec = importlib.util.spec_from_file_location('pod', path)
|
||||||
|
pod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(pod)
|
||||||
|
return pod.pod
|
||||||
17
podctl/script.py
Normal file
17
podctl/script.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
|
class Script(list):
|
||||||
|
def __init__(self, shebang=None):
|
||||||
|
super().__init__()
|
||||||
|
self.append(shebang or '#/usr/bin/env bash')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if not getattr(self, '_postconfig', False):
|
||||||
|
if hasattr(self, 'post_config'):
|
||||||
|
self.post_config()
|
||||||
|
self._postconfig = True
|
||||||
|
return '\n'.join([
|
||||||
|
textwrap.dedent(line.lstrip('\n')).strip()
|
||||||
|
for line in self
|
||||||
|
])
|
||||||
28
setup.py
Normal file
28
setup.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='podctl',
|
||||||
|
versioning='dev',
|
||||||
|
setup_requires='setupmeta',
|
||||||
|
install_requires=['cli2'],
|
||||||
|
extras_require=dict(
|
||||||
|
test=[
|
||||||
|
'freezegun',
|
||||||
|
'pytest',
|
||||||
|
'pytest-cov',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
author='James Pic',
|
||||||
|
author_email='jamespic@gmail.com',
|
||||||
|
url='https://yourlabs.io/oss/podctl',
|
||||||
|
include_package_data=True,
|
||||||
|
license='MIT',
|
||||||
|
keywords='cli',
|
||||||
|
python_requires='>=3',
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'podctl = podctl.console_script:console_script',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
Binary file not shown.
40
tests/test_build.py
Normal file
40
tests/test_build.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import difflib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from podctl.container import Container
|
||||||
|
from podctl.build import BuildScript
|
||||||
|
|
||||||
|
|
||||||
|
def script_test(name, result):
|
||||||
|
path = os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
f'test_{name}.sh',
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.path.exists(path):
|
||||||
|
with open(path, 'w+') as f:
|
||||||
|
f.write(result)
|
||||||
|
raise Exception('Fixture created test_build_packages.sh')
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
expected = f.read()
|
||||||
|
result = difflib.unified_diff(
|
||||||
|
expected,
|
||||||
|
result,
|
||||||
|
fromfile='expected',
|
||||||
|
tofile='result'
|
||||||
|
)
|
||||||
|
assert not list(result), sys.stdout.writelines(result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_empty():
|
||||||
|
result = str(BuildScript(Container()))
|
||||||
|
script_test('build_empty', result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_packages():
|
||||||
|
result = str(BuildScript(Container(
|
||||||
|
base='alpine',
|
||||||
|
packages=['bash'],
|
||||||
|
)))
|
||||||
|
script_test('build_packages', result)
|
||||||
11
tests/test_build_empty.sh
Normal file
11
tests/test_build_empty.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#/usr/bin/env bash
|
||||||
|
mounts=()
|
||||||
|
umounts() {
|
||||||
|
for i in "${mounts[@]}"; do
|
||||||
|
umount $i
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap umounts 0
|
||||||
|
ctr=$(buildah from $base)
|
||||||
|
mnt=$(buildah mount $ctr)
|
||||||
|
mounts=("$mnt" "${mounts[@]}")
|
||||||
22
tests/test_build_packages.sh
Normal file
22
tests/test_build_packages.sh
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#/usr/bin/env bash
|
||||||
|
base="alpine"
|
||||||
|
mounts=()
|
||||||
|
umounts() {
|
||||||
|
for i in "${mounts[@]}"; do
|
||||||
|
umount $i
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap umounts 0
|
||||||
|
ctr=$(buildah from $base)
|
||||||
|
mnt=$(buildah mount $ctr)
|
||||||
|
mounts=("$mnt" "${mounts[@]}")
|
||||||
|
buildah run $ctr -- mkdir -p /var/cache/apk
|
||||||
|
mkdir -p $(pwd)/.cache/apk
|
||||||
|
mount -o bind $(pwd)/.cache/apk $mnt/var/cache/apk
|
||||||
|
mounts=("$mnt/var/cache/apk" "${mounts[@]}")
|
||||||
|
buildah run $ctr -- ln -s /var/cache/apk /etc/apk/cache
|
||||||
|
if [ -n "$(find .cache/apk/ -name APKINDEX.* -mtime +3)" ]; then
|
||||||
|
buildah run $ctr -- apk update
|
||||||
|
fi
|
||||||
|
buildah run $ctr -- apk upgrade
|
||||||
|
buildah run $ctr -- apk add bash
|
||||||
56
tests/test_container.py
Normal file
56
tests/test_container.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from .container import Container, switch
|
||||||
|
|
||||||
|
|
||||||
|
def test_container_configuration():
|
||||||
|
'''Attributes should be passable to constructor or as class attributes'''
|
||||||
|
assert Container(a='b')['a'] == 'b'
|
||||||
|
class Test(Container):
|
||||||
|
cfg = dict(a='b')
|
||||||
|
assert Test()['a'] == 'b'
|
||||||
|
|
||||||
|
|
||||||
|
def test_switch_simple():
|
||||||
|
assert Container(a=switch(default='expected'))['a'] == 'expected'
|
||||||
|
assert Container(a=switch(noise='noise'))['a'] == None
|
||||||
|
fixture = Container(
|
||||||
|
'test',
|
||||||
|
a=switch(default='noise', test='expected')
|
||||||
|
)
|
||||||
|
assert fixture['a'] == 'expected'
|
||||||
|
assert [*fixture.values()][0] == 'expected'
|
||||||
|
assert [*fixture.items()][0][1] == 'expected'
|
||||||
|
|
||||||
|
|
||||||
|
def test_switch_iterable():
|
||||||
|
class TContainer(Container):
|
||||||
|
cfg = dict(
|
||||||
|
a=switch(dev='test')
|
||||||
|
)
|
||||||
|
assert TContainer()['a'] is None
|
||||||
|
assert TContainer('dev')['a'] == 'test'
|
||||||
|
assert TContainer('dev', a=[switch(dev='y')])['a'] == ['y']
|
||||||
|
assert TContainer('dev', a=[switch(default='y')])['a'] == ['y']
|
||||||
|
|
||||||
|
|
||||||
|
def test_switch_value_list():
|
||||||
|
assert Container('test').switch_value(
|
||||||
|
[switch(default='noise', test=False)]
|
||||||
|
) == [False]
|
||||||
|
|
||||||
|
assert Container('none').switch_value(
|
||||||
|
[switch(noise='noise')]
|
||||||
|
) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_switch_value_dict():
|
||||||
|
assert Container('foo').switch_value(
|
||||||
|
dict(i=switch(default='expected', noise='noise'))
|
||||||
|
) == dict(i='expected')
|
||||||
|
|
||||||
|
assert Container('test').switch_value(
|
||||||
|
dict(i=switch(default='noise', test='expected'))
|
||||||
|
) == dict(i='expected')
|
||||||
|
|
||||||
|
assert Container('none').switch_value(
|
||||||
|
dict(i=switch(noise='noise'), j=dict(e=switch(none=1)))
|
||||||
|
) == dict(j=dict(e=1))
|
||||||
10
tests/test_pod.py
Normal file
10
tests/test_pod.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pod import Pod
|
||||||
|
|
||||||
|
|
||||||
|
def test_pod_file():
|
||||||
|
path = Path(os.path.dirname(__file__)) / '..' / 'pod.py'
|
||||||
|
pod = Pod.factory(path)
|
||||||
|
assert pod['podctl']
|
||||||
Loading…
x
Reference in New Issue
Block a user