Refactor into visitor pattern
This commit is contained in:
parent
d5d924dd06
commit
a866ba5a0d
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
*.pyc
|
||||||
|
__pycache__
|
||||||
|
.cache/
|
||||||
|
.coverage
|
||||||
|
.eggs/
|
||||||
|
.podctl_build_django.sh
|
||||||
|
.podctl_build_podctl.sh
|
||||||
|
.setupmeta.version
|
||||||
|
.testmondata
|
||||||
|
*.egg-info
|
||||||
15
.gitlab-ci.yml
Normal file
15
.gitlab-ci.yml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
qa:
|
||||||
|
stage: test
|
||||||
|
image: yourlabs/python
|
||||||
|
script: flake8 podctl
|
||||||
|
|
||||||
|
test:
|
||||||
|
stage: test
|
||||||
|
image: yourlabs/python
|
||||||
|
script: pip install -e . && py.test -v tests
|
||||||
|
|
||||||
|
pypi:
|
||||||
|
stage: deploy
|
||||||
|
image: yourlabs/python
|
||||||
|
script: pypi-release
|
||||||
|
only: [tags]
|
||||||
65
examples/django.py
Normal file
65
examples/django.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from podctl import *
|
||||||
|
|
||||||
|
|
||||||
|
class Django(Container):
|
||||||
|
tag = 'yourlabs/crudlfap'
|
||||||
|
base = 'alpine'
|
||||||
|
packages = [
|
||||||
|
'bash',
|
||||||
|
'python3',
|
||||||
|
switch(dev='vim'),
|
||||||
|
]
|
||||||
|
env = dict(FOO='bar')
|
||||||
|
annotations = dict(test='foo')
|
||||||
|
labels = dict(foo='test')
|
||||||
|
cmd = 'bash'
|
||||||
|
entrypoint = ['bash', '-v']
|
||||||
|
ports = [1234]
|
||||||
|
user = dict(
|
||||||
|
shell='/bin/bash',
|
||||||
|
name='app',
|
||||||
|
home='/app',
|
||||||
|
id=os.getenv('SUDO_ID', os.getenv('UID')),
|
||||||
|
)
|
||||||
|
volumes = [
|
||||||
|
'/bydir',
|
||||||
|
'byname:/byname',
|
||||||
|
switch(dev='.:/app'),
|
||||||
|
]
|
||||||
|
build = [
|
||||||
|
'sudo pip3 install --upgrade pip',
|
||||||
|
'pip3 install --user -e /app',
|
||||||
|
]
|
||||||
|
workdir = '/app'
|
||||||
|
|
||||||
|
django = Container(
|
||||||
|
Base('alpine'),
|
||||||
|
Packages('bash', switch(dev='vim')),
|
||||||
|
User(
|
||||||
|
uid=1000,
|
||||||
|
home='/app',
|
||||||
|
directories=('log', 'spooler', 'static')
|
||||||
|
),
|
||||||
|
switch(default=Mount('.', '/app'), production=Copy('.', '/app')),
|
||||||
|
Npm('build'),
|
||||||
|
Env('PATH', 'PATH=/app/node_modules/.bin:$PATH'),
|
||||||
|
Pip('requirements.txt'),
|
||||||
|
Env('PATH', 'PATH=$HOME/.local/bin:$PATH'),
|
||||||
|
switch(dev=Run('''
|
||||||
|
manage.py collectstatic --noinput --clear
|
||||||
|
find frontend/static/dist/css -type f | xargs gzip -f -k -9
|
||||||
|
find frontend/static/dist/js -type f | xargs gzip -f -k -9
|
||||||
|
''')),
|
||||||
|
Expose(8000),
|
||||||
|
Tag('equisafe'),
|
||||||
|
)
|
||||||
|
|
||||||
|
pod = Pod(
|
||||||
|
Service('django', django, restart='unless-stopped'),
|
||||||
|
Service('db',
|
||||||
|
Container(Tag('postgresql:latest')),
|
||||||
|
restart='unless-stopped'
|
||||||
|
),
|
||||||
|
)
|
||||||
17
pod.py
Normal file
17
pod.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Basic pod to contain the podctl command.
|
||||||
|
|
||||||
|
For advanced examples, check the examples sub-directory of the git repository.
|
||||||
|
"""
|
||||||
|
from podctl import *
|
||||||
|
|
||||||
|
|
||||||
|
podctl = Container(
|
||||||
|
Base('alpine'),
|
||||||
|
Packages('bash', 'python3'),
|
||||||
|
User('app', 1000, '/app'),
|
||||||
|
Copy(['setup.py', 'podctl'], '/app'),
|
||||||
|
Pip('/app'),
|
||||||
|
Config(cmd='podctl'),
|
||||||
|
Tag('yourlabs/podctl'),
|
||||||
|
)
|
||||||
@ -1,2 +1,3 @@
|
|||||||
from .container import Container, switch
|
from .container import Container # noqa
|
||||||
from .pod import Pod
|
from .pod import Pod # noqa
|
||||||
|
from .visitors import * # noqa
|
||||||
|
|||||||
222
podctl/build.py
222
podctl/build.py
@ -1,9 +1,3 @@
|
|||||||
from glob import glob
|
|
||||||
import inspect
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script
|
||||||
|
|
||||||
|
|
||||||
@ -15,14 +9,16 @@ class BuildScript(Script):
|
|||||||
self.container = container
|
self.container = container
|
||||||
|
|
||||||
for var in self.export:
|
for var in self.export:
|
||||||
if var in self.container:
|
self.append(f'{var}="{container.variable(var)}"')
|
||||||
self.append(f'{var}="{self.container[var]}"')
|
|
||||||
|
|
||||||
self.append('''
|
self.append('''
|
||||||
mounts=()
|
mounts=()
|
||||||
umounts() {
|
umounts() {
|
||||||
for i in "${mounts[@]}"; do
|
for i in "${mounts[@]}"; do
|
||||||
umount $i
|
umount $i
|
||||||
|
echo $mounts
|
||||||
|
mounts=("${mounts[@]/$i}")
|
||||||
|
echo $mounts
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
trap umounts 0
|
trap umounts 0
|
||||||
@ -31,215 +27,27 @@ class BuildScript(Script):
|
|||||||
mounts=("$mnt" "${mounts[@]}")
|
mounts=("$mnt" "${mounts[@]}")
|
||||||
''')
|
''')
|
||||||
|
|
||||||
if self.container.get('packages'):
|
|
||||||
self.container.packages_install(self)
|
|
||||||
|
|
||||||
def config(self, line):
|
def config(self, line):
|
||||||
self.append(f'buildah config {line} $ctr')
|
self.append(f'buildah config {line} $ctr')
|
||||||
|
|
||||||
|
def _run(self, cmd):
|
||||||
|
user = self.container.variable('username')
|
||||||
|
if cmd.startswith('sudo '):
|
||||||
|
return f'buildah run --user root $ctr -- {cmd[5:]}'
|
||||||
|
elif user and self.container.variable('user_created'):
|
||||||
|
return f'buildah run --user {user} $ctr -- {cmd}'
|
||||||
|
else:
|
||||||
|
return f'buildah run $ctr -- {cmd}'
|
||||||
|
|
||||||
def run(self, cmd):
|
def run(self, cmd):
|
||||||
self.append('buildah run $ctr -- ' + cmd)
|
self.append(self._run(cmd))
|
||||||
|
|
||||||
def copy(self, src, dst):
|
def copy(self, src, dst):
|
||||||
self.append(f'buildah copy $ctr {src} {dst}')
|
self.append(f'buildah copy $ctr {src} {dst}')
|
||||||
|
|
||||||
def mount(self, src, dst):
|
def mount(self, src, dst):
|
||||||
self.run('mkdir -p ' + dst)
|
self.run('sudo mkdir -p ' + dst)
|
||||||
self.append('mkdir -p ' + src)
|
self.append('mkdir -p ' + src)
|
||||||
self.append(f'mount -o bind {src} $mnt{dst}')
|
self.append(f'mount -o bind {src} $mnt{dst}')
|
||||||
# for unmount trap
|
# for unmount trap
|
||||||
self.append('mounts=("$mnt%s" "${mounts[@]}")' % dst)
|
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
|
|
||||||
''')
|
|
||||||
|
|||||||
@ -4,12 +4,13 @@ docker & docker-compose frustrated me, podctl unfrustrates me.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import cli2
|
import cli2
|
||||||
|
import importlib
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
|
||||||
|
|
||||||
|
from .container import Container
|
||||||
from .pod import Pod
|
from .pod import Pod
|
||||||
|
from .service import Service
|
||||||
|
|
||||||
|
|
||||||
class BuildStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol):
|
class BuildStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol):
|
||||||
@ -32,17 +33,20 @@ class BuildStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol):
|
|||||||
@cli2.option('debug', help='Print debug output', color=cli2.GREEN, alias='d')
|
@cli2.option('debug', help='Print debug output', color=cli2.GREEN, alias='d')
|
||||||
async def build(service=None, **kwargs):
|
async def build(service=None, **kwargs):
|
||||||
procs = []
|
procs = []
|
||||||
for name, container in console_script.pod.items():
|
for name, service in console_script.pod.services.items():
|
||||||
if not container.base:
|
container = service.container
|
||||||
|
if not container.variable('base'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
script = f'.podctl_build_{name}.sh'
|
script = f'.podctl_build_{name}.sh'
|
||||||
with open(script, 'w+') as f:
|
with open(script, 'w+') as f:
|
||||||
f.write(str(container.script_build()))
|
f.write(str(container.script('build')))
|
||||||
|
|
||||||
loop = asyncio.events.get_event_loop()
|
loop = asyncio.events.get_event_loop()
|
||||||
protocol_factory = lambda: BuildStreamProtocol(
|
|
||||||
container=container,
|
def protocol_factory():
|
||||||
|
return BuildStreamProtocol(
|
||||||
|
service,
|
||||||
limit=asyncio.streams._DEFAULT_LIMIT,
|
limit=asyncio.streams._DEFAULT_LIMIT,
|
||||||
loop=loop,
|
loop=loop,
|
||||||
)
|
)
|
||||||
@ -60,37 +64,6 @@ async def build(service=None, **kwargs):
|
|||||||
await proc.communicate()
|
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):
|
class ConsoleScript(cli2.ConsoleScript):
|
||||||
def __setitem__(self, name, cb):
|
def __setitem__(self, name, cb):
|
||||||
if name != 'help':
|
if name != 'help':
|
||||||
@ -114,8 +87,25 @@ class ConsoleScript(cli2.ConsoleScript):
|
|||||||
if command.name != 'help':
|
if command.name != 'help':
|
||||||
self.path = self.parser.options['file']
|
self.path = self.parser.options['file']
|
||||||
self.home = self.parser.options['home']
|
self.home = self.parser.options['home']
|
||||||
with open(self.path) as f:
|
self.containers = dict()
|
||||||
self.pod = Pod.factory(self.path)
|
self.pods = dict()
|
||||||
|
self.pod = None
|
||||||
|
spec = importlib.util.spec_from_file_location('pod', self.path)
|
||||||
|
pod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(pod)
|
||||||
|
for name, value in pod.__dict__.items():
|
||||||
|
if isinstance(value, Container):
|
||||||
|
self.containers[name] = value
|
||||||
|
elif isinstance(value, Pod):
|
||||||
|
self.pods[name] = value
|
||||||
|
|
||||||
|
if 'pod' in self.pods:
|
||||||
|
self.pod = self.pods['pod']
|
||||||
|
if not self.pod:
|
||||||
|
self.pod = Pod(*[
|
||||||
|
Service(name, value, restart='no')
|
||||||
|
for name, value in self.containers.items()
|
||||||
|
])
|
||||||
return super().call(command)
|
return super().call(command)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,109 +1,8 @@
|
|||||||
import collections
|
|
||||||
import copy
|
|
||||||
from glob import glob
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from .build import BuildScript
|
from .build import BuildScript
|
||||||
|
from .visitable import Visitable
|
||||||
|
|
||||||
|
|
||||||
PACKAGE_MANAGERS = dict(
|
class Container(Visitable):
|
||||||
apk=dict(
|
default_scripts = dict(
|
||||||
update='apk update',
|
build=BuildScript,
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
import collections
|
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
|
|
||||||
class Pod(collections.UserDict):
|
class Pod:
|
||||||
@classmethod
|
def __init__(self, *services, **scripts):
|
||||||
def factory(cls, path):
|
self.scripts = scripts
|
||||||
spec = importlib.util.spec_from_file_location('pod', path)
|
self.services = {s.name: s for s in services}
|
||||||
pod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(pod)
|
|
||||||
return pod.pod
|
|
||||||
|
|||||||
5
podctl/service.py
Normal file
5
podctl/service.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class Service:
|
||||||
|
def __init__(self, name, container, restart=None):
|
||||||
|
self.name = name
|
||||||
|
self.container = container
|
||||||
|
self.restart = restart
|
||||||
32
podctl/visitable.py
Normal file
32
podctl/visitable.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from copy import copy
|
||||||
|
|
||||||
|
|
||||||
|
class Visitable:
|
||||||
|
default_scripts = dict()
|
||||||
|
|
||||||
|
def __init__(self, *visitors, **scripts):
|
||||||
|
self.visitors = list(visitors)
|
||||||
|
self.scripts = scripts or {
|
||||||
|
k: v(self) for k, v in self.default_scripts.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def script(self, name):
|
||||||
|
script = copy(self.scripts[name])
|
||||||
|
|
||||||
|
for prefix in ('init_', 'pre_', '', 'post_'):
|
||||||
|
method = prefix + name
|
||||||
|
for visitor in self.visitors:
|
||||||
|
if hasattr(visitor, method):
|
||||||
|
getattr(visitor, method)(script)
|
||||||
|
|
||||||
|
return script
|
||||||
|
|
||||||
|
def visitor(self, name):
|
||||||
|
for visitor in self.visitors:
|
||||||
|
if name.lower() == type(visitor).__name__.lower():
|
||||||
|
return visitor
|
||||||
|
|
||||||
|
def variable(self, name):
|
||||||
|
for visitor in self.visitors:
|
||||||
|
if getattr(visitor, name, None) is not None:
|
||||||
|
return getattr(visitor, name)
|
||||||
8
podctl/visitors/__init__.py
Normal file
8
podctl/visitors/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from .base import Base # noqa
|
||||||
|
from .config import Config # noqa
|
||||||
|
from .copy import Copy # noqa
|
||||||
|
from .packages import Packages # noqa
|
||||||
|
from .pip import Pip # noqa
|
||||||
|
from .run import Run # noqa
|
||||||
|
from .tag import Tag # noqa
|
||||||
|
from .user import User # noqa
|
||||||
5
podctl/visitors/base.py
Normal file
5
podctl/visitors/base.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class Base:
|
||||||
|
def __init__(self, base):
|
||||||
|
self.base = base
|
||||||
7
podctl/visitors/config.py
Normal file
7
podctl/visitors/config.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
class Config:
|
||||||
|
def __init__(self, **values):
|
||||||
|
self.values = values
|
||||||
|
|
||||||
|
def post_build(self, script):
|
||||||
|
for key, value in self.values.items():
|
||||||
|
script.config(f'--{key} {value}')
|
||||||
26
podctl/visitors/copy.py
Normal file
26
podctl/visitors/copy.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
class Copy:
|
||||||
|
def __init__(self, src, dst):
|
||||||
|
self.src = src
|
||||||
|
self.dst = dst
|
||||||
|
|
||||||
|
def init_build(self, script):
|
||||||
|
count = self.dst.count(':')
|
||||||
|
self.mode = None
|
||||||
|
self.owner = None
|
||||||
|
if count == 2:
|
||||||
|
self.dst, self.mode, self.owner = self.dst.split(':')
|
||||||
|
elif count == 1:
|
||||||
|
self.dst, self.mode = self.dst.split(':')
|
||||||
|
self.owner = script.variable('user')
|
||||||
|
|
||||||
|
def build(self, script):
|
||||||
|
if isinstance(self.src, list):
|
||||||
|
script.run(f'sudo mkdir -p {self.dst}')
|
||||||
|
for item in self.src:
|
||||||
|
script.append(f'cp -a {item} $mnt{self.dst}')
|
||||||
|
|
||||||
|
if self.mode:
|
||||||
|
script.run(f'sudo chmod {self.mode} $mnt{self.dst}')
|
||||||
|
|
||||||
|
if self.owner:
|
||||||
|
script.run(f'sudo chown -R {self.owner} $mnt{self.dst}')
|
||||||
54
podctl/visitors/packages.py
Normal file
54
podctl/visitors/packages.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
class Packages:
|
||||||
|
mgrs = dict(
|
||||||
|
apk=dict(
|
||||||
|
update='sudo apk update',
|
||||||
|
upgrade='sudo apk upgrade',
|
||||||
|
install='sudo apk add',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *packages):
|
||||||
|
self.packages = list(packages)
|
||||||
|
|
||||||
|
def pre_build(self, script):
|
||||||
|
for mgr, cmds in self.mgrs.items():
|
||||||
|
cmd = [
|
||||||
|
'podman',
|
||||||
|
'run',
|
||||||
|
script.container.variable('base'),
|
||||||
|
'which',
|
||||||
|
mgr
|
||||||
|
]
|
||||||
|
print('+ ' + ' '.join(cmd))
|
||||||
|
try:
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
self.mgr = mgr
|
||||||
|
self.cmds = cmds
|
||||||
|
break
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def build(self, script):
|
||||||
|
cache = f'.cache/{self.mgr}'
|
||||||
|
script.mount(
|
||||||
|
'$(pwd)/' + cache,
|
||||||
|
f'/var/cache/{self.mgr}'
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.mgr == 'apk':
|
||||||
|
# special step to enable apk cache
|
||||||
|
script.run('ln -s /var/cache/apk /etc/apk/cache')
|
||||||
|
script.append(f'''
|
||||||
|
old="$(find .cache/apk/ -name APKINDEX.* -mtime +3)"
|
||||||
|
if [ -n "$old" ] || ! ls .cache/apk/APKINDEX.*; then
|
||||||
|
{script._run(self.cmds['update'])}
|
||||||
|
else
|
||||||
|
echo Cache recent enough, skipping index update.
|
||||||
|
fi
|
||||||
|
''')
|
||||||
|
|
||||||
|
script.run(self.cmds['upgrade'])
|
||||||
|
script.run(' '.join([self.cmds['install']] + self.packages))
|
||||||
25
podctl/visitors/pip.py
Normal file
25
podctl/visitors/pip.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
class Pip:
|
||||||
|
def __init__(self, *pip_packages):
|
||||||
|
self.pip_packages = pip_packages
|
||||||
|
|
||||||
|
def build(self, script):
|
||||||
|
script.append(f'''
|
||||||
|
if {script._run("bash -c 'type pip3'")}; then
|
||||||
|
_pip=pip3
|
||||||
|
elif {script._run("bash -c 'type pip'")}; then
|
||||||
|
_pip=pip
|
||||||
|
elif {script._run("bash -c 'type pip2'")}; then
|
||||||
|
_pip=pip2
|
||||||
|
fi
|
||||||
|
''')
|
||||||
|
script.mount('.cache/pip', '/root/.cache/pip')
|
||||||
|
script.run('sudo $_pip install --upgrade pip')
|
||||||
|
source = [p for p in self.pip_packages if p.startswith('/')]
|
||||||
|
if source:
|
||||||
|
script.run(
|
||||||
|
f'sudo $_pip install --upgrade --editable {" ".join(source)}'
|
||||||
|
)
|
||||||
|
|
||||||
|
nonsource = [p for p in self.pip_packages if not p.startswith('/')]
|
||||||
|
if nonsource:
|
||||||
|
script.run(f'sudo $_pip install --upgrade {" ".join(source)}')
|
||||||
7
podctl/visitors/run.py
Normal file
7
podctl/visitors/run.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
class Run:
|
||||||
|
def __init__(self, *commands):
|
||||||
|
self.commands = commands
|
||||||
|
|
||||||
|
def build(self, script):
|
||||||
|
for command in self.commands:
|
||||||
|
script.run(command)
|
||||||
6
podctl/visitors/tag.py
Normal file
6
podctl/visitors/tag.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
class Tag:
|
||||||
|
def __init__(self, tag):
|
||||||
|
self.tag = tag
|
||||||
|
|
||||||
|
def post_build(self, script):
|
||||||
|
script.append(f'umounts && trap - 0 && buildah commit $ctr {self.tag}')
|
||||||
37
podctl/visitors/user.py
Normal file
37
podctl/visitors/user.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from .packages import Packages
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
"""Secure the image with a user"""
|
||||||
|
|
||||||
|
def __init__(self, username, uid, home):
|
||||||
|
self.username = username
|
||||||
|
self.uid = uid
|
||||||
|
self.home = home
|
||||||
|
self.user_created = False
|
||||||
|
|
||||||
|
def init_build(self, script):
|
||||||
|
"""Inject the Packages visitor if necessary."""
|
||||||
|
packages = script.container.visitor('packages')
|
||||||
|
if not packages:
|
||||||
|
index = script.container.visitors.index(self)
|
||||||
|
script.container.visitors.insert(index, Packages())
|
||||||
|
|
||||||
|
def pre_build(self, script):
|
||||||
|
"""Inject the shadow package for the usermod command"""
|
||||||
|
if script.container.variable('mgr') == 'apk':
|
||||||
|
script.container.variable('packages').append('shadow')
|
||||||
|
|
||||||
|
def build(self, script):
|
||||||
|
script.append(f'''
|
||||||
|
if buildah run $ctr -- id {self.uid}; then
|
||||||
|
i=$(buildah run $ctr -- id -n {self.uid})
|
||||||
|
buildah run $ctr -- usermod --home-dir {self.home} --no-log-init {self.uid} $i
|
||||||
|
else
|
||||||
|
buildah run $ctr -- useradd --home-dir {self.home} --uid {self.uid} {self.username}
|
||||||
|
fi
|
||||||
|
''') # noqa
|
||||||
|
self.user_created = True
|
||||||
|
|
||||||
|
def post_build(self, script):
|
||||||
|
script.config(f'--user {self.username}')
|
||||||
Binary file not shown.
@ -4,9 +4,16 @@ import sys
|
|||||||
|
|
||||||
from podctl.container import Container
|
from podctl.container import Container
|
||||||
from podctl.build import BuildScript
|
from podctl.build import BuildScript
|
||||||
|
from podctl.visitors import (
|
||||||
|
Base,
|
||||||
|
Copy,
|
||||||
|
Packages,
|
||||||
|
User,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def script_test(name, result):
|
def script_test(name, *visitors):
|
||||||
|
result = str(Container(*visitors).script('build'))
|
||||||
path = os.path.join(
|
path = os.path.join(
|
||||||
os.path.dirname(__file__),
|
os.path.dirname(__file__),
|
||||||
f'test_{name}.sh',
|
f'test_{name}.sh',
|
||||||
@ -15,7 +22,7 @@ def script_test(name, result):
|
|||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
with open(path, 'w+') as f:
|
with open(path, 'w+') as f:
|
||||||
f.write(result)
|
f.write(result)
|
||||||
raise Exception('Fixture created test_build_packages.sh')
|
raise Exception(f'Fixture created test_{name}.sh')
|
||||||
with open(path, 'r') as f:
|
with open(path, 'r') as f:
|
||||||
expected = f.read()
|
expected = f.read()
|
||||||
result = difflib.unified_diff(
|
result = difflib.unified_diff(
|
||||||
@ -28,13 +35,43 @@ def script_test(name, result):
|
|||||||
|
|
||||||
|
|
||||||
def test_build_empty():
|
def test_build_empty():
|
||||||
result = str(BuildScript(Container()))
|
script_test(
|
||||||
script_test('build_empty', result)
|
'build_empty',
|
||||||
|
Base('alpine'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_build_packages():
|
def test_build_packages():
|
||||||
|
script_test(
|
||||||
|
'build_packages',
|
||||||
|
Base('alpine'),
|
||||||
|
Packages('bash'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_user():
|
||||||
|
script_test(
|
||||||
|
'build_user',
|
||||||
|
Base('alpine'),
|
||||||
|
User('app', 1000, '/app'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_copy():
|
||||||
|
script_test(
|
||||||
|
'build_copy',
|
||||||
|
Base('alpine'),
|
||||||
|
Copy(os.path.dirname(__file__), '/app'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_build_files():
|
||||||
result = str(BuildScript(Container(
|
result = str(BuildScript(Container(
|
||||||
base='alpine',
|
base='alpine',
|
||||||
packages=['bash'],
|
files=[
|
||||||
|
Directory('/app', '0500').add('setup.py', 'podctl'),
|
||||||
|
]
|
||||||
)))
|
)))
|
||||||
script_test('build_packages', result)
|
'''
|
||||||
|
|||||||
18
tests/test_build_copy.sh
Normal file
18
tests/test_build_copy.sh
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#/usr/bin/env bash
|
||||||
|
base="alpine"
|
||||||
|
repo="None"
|
||||||
|
tag="None"
|
||||||
|
image="None"
|
||||||
|
mounts=()
|
||||||
|
umounts() {
|
||||||
|
for i in "${mounts[@]}"; do
|
||||||
|
umount $i
|
||||||
|
echo $mounts
|
||||||
|
mounts=("${mounts[@]/$i}")
|
||||||
|
echo $mounts
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap umounts 0
|
||||||
|
ctr=$(buildah from $base)
|
||||||
|
mnt=$(buildah mount $ctr)
|
||||||
|
mounts=("$mnt" "${mounts[@]}")
|
||||||
@ -1,8 +1,15 @@
|
|||||||
#/usr/bin/env bash
|
#/usr/bin/env bash
|
||||||
|
base="alpine"
|
||||||
|
repo="None"
|
||||||
|
tag="None"
|
||||||
|
image="None"
|
||||||
mounts=()
|
mounts=()
|
||||||
umounts() {
|
umounts() {
|
||||||
for i in "${mounts[@]}"; do
|
for i in "${mounts[@]}"; do
|
||||||
umount $i
|
umount $i
|
||||||
|
echo $mounts
|
||||||
|
mounts=("${mounts[@]/$i}")
|
||||||
|
echo $mounts
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
trap umounts 0
|
trap umounts 0
|
||||||
|
|||||||
@ -1,22 +1,31 @@
|
|||||||
#/usr/bin/env bash
|
#/usr/bin/env bash
|
||||||
base="alpine"
|
base="alpine"
|
||||||
|
repo="None"
|
||||||
|
tag="None"
|
||||||
|
image="None"
|
||||||
mounts=()
|
mounts=()
|
||||||
umounts() {
|
umounts() {
|
||||||
for i in "${mounts[@]}"; do
|
for i in "${mounts[@]}"; do
|
||||||
umount $i
|
umount $i
|
||||||
|
echo $mounts
|
||||||
|
mounts=("${mounts[@]/$i}")
|
||||||
|
echo $mounts
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
trap umounts 0
|
trap umounts 0
|
||||||
ctr=$(buildah from $base)
|
ctr=$(buildah from $base)
|
||||||
mnt=$(buildah mount $ctr)
|
mnt=$(buildah mount $ctr)
|
||||||
mounts=("$mnt" "${mounts[@]}")
|
mounts=("$mnt" "${mounts[@]}")
|
||||||
buildah run $ctr -- mkdir -p /var/cache/apk
|
buildah run --user root $ctr -- mkdir -p /var/cache/apk
|
||||||
mkdir -p $(pwd)/.cache/apk
|
mkdir -p $(pwd)/.cache/apk
|
||||||
mount -o bind $(pwd)/.cache/apk $mnt/var/cache/apk
|
mount -o bind $(pwd)/.cache/apk $mnt/var/cache/apk
|
||||||
mounts=("$mnt/var/cache/apk" "${mounts[@]}")
|
mounts=("$mnt/var/cache/apk" "${mounts[@]}")
|
||||||
buildah run $ctr -- ln -s /var/cache/apk /etc/apk/cache
|
buildah run $ctr -- ln -s /var/cache/apk /etc/apk/cache
|
||||||
if [ -n "$(find .cache/apk/ -name APKINDEX.* -mtime +3)" ]; then
|
old="$(find .cache/apk/ -name APKINDEX.* -mtime +3)"
|
||||||
buildah run $ctr -- apk update
|
if [ -n "$old" ] || ! ls .cache/apk/APKINDEX.*; then
|
||||||
|
buildah run --user root $ctr -- apk update
|
||||||
|
else
|
||||||
|
echo Cache recent enough, skipping index update.
|
||||||
fi
|
fi
|
||||||
buildah run $ctr -- apk upgrade
|
buildah run --user root $ctr -- apk upgrade
|
||||||
buildah run $ctr -- apk add bash
|
buildah run --user root $ctr -- apk add bash
|
||||||
38
tests/test_build_user.sh
Normal file
38
tests/test_build_user.sh
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#/usr/bin/env bash
|
||||||
|
base="alpine"
|
||||||
|
repo="None"
|
||||||
|
tag="None"
|
||||||
|
image="None"
|
||||||
|
mounts=()
|
||||||
|
umounts() {
|
||||||
|
for i in "${mounts[@]}"; do
|
||||||
|
umount $i
|
||||||
|
echo $mounts
|
||||||
|
mounts=("${mounts[@]/$i}")
|
||||||
|
echo $mounts
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap umounts 0
|
||||||
|
ctr=$(buildah from $base)
|
||||||
|
mnt=$(buildah mount $ctr)
|
||||||
|
mounts=("$mnt" "${mounts[@]}")
|
||||||
|
buildah run --user root $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
|
||||||
|
old="$(find .cache/apk/ -name APKINDEX.* -mtime +3)"
|
||||||
|
if [ -n "$old" ] || ! ls .cache/apk/APKINDEX.*; then
|
||||||
|
buildah run --user root $ctr -- apk update
|
||||||
|
else
|
||||||
|
echo Cache recent enough, skipping index update.
|
||||||
|
fi
|
||||||
|
buildah run --user root $ctr -- apk upgrade
|
||||||
|
buildah run --user root $ctr -- apk add shadow
|
||||||
|
if buildah run $ctr -- id 1000; then
|
||||||
|
i=$(buildah run $ctr -- id -n 1000)
|
||||||
|
buildah run $ctr -- usermod --home-dir /app --no-log-init 1000 $i
|
||||||
|
else
|
||||||
|
buildah run $ctr -- useradd --home-dir /app --uid 1000 app
|
||||||
|
fi
|
||||||
|
buildah config --user app $ctr
|
||||||
@ -1,56 +0,0 @@
|
|||||||
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))
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
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']
|
|
||||||
91
tests/test_visitable.py
Normal file
91
tests/test_visitable.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from podctl.script import Script
|
||||||
|
from podctl.visitable import Visitable
|
||||||
|
|
||||||
|
|
||||||
|
class Visitor0:
|
||||||
|
def __init__(self, name=None):
|
||||||
|
self.name = name or 'visit0'
|
||||||
|
|
||||||
|
|
||||||
|
class Visitor1:
|
||||||
|
def pre_build(self, script):
|
||||||
|
script.append('pre_build')
|
||||||
|
def build(self, script):
|
||||||
|
script.append('build')
|
||||||
|
def post_build(self, script):
|
||||||
|
script.append('post_build')
|
||||||
|
|
||||||
|
|
||||||
|
def test_visitable_visitor():
|
||||||
|
visitable = Visitable(Visitor0(), Visitor1(), build=Script())
|
||||||
|
script = visitable.script('build')
|
||||||
|
assert 'pre_build' in script
|
||||||
|
assert 'build' in script
|
||||||
|
assert 'post_build' in script
|
||||||
|
|
||||||
|
|
||||||
|
def test_visitable_visitor():
|
||||||
|
x = Visitor0()
|
||||||
|
assert Visitable(x).visitor('visitor0') is x
|
||||||
|
|
||||||
|
|
||||||
|
def test_visitable_variable():
|
||||||
|
assert Visitable(Visitor0('foo')).variable('name') == 'foo'
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#def test_visitable_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))
|
||||||
Loading…
x
Reference in New Issue
Block a user