From 3413fd8981cd3973b7ffe5cf88cc85bb2c1f3a09 Mon Sep 17 00:00:00 2001 From: jpic Date: Wed, 12 Feb 2020 19:34:30 +0100 Subject: [PATCH] Now to rewrite the test framework and good to go ! --- podctl/build.py | 7 ++-- podctl/console_script.py | 14 ++++++-- podctl/container.py | 50 +++++++++++++++++++++++++++- podctl/exceptions.py | 9 ++++- podctl/pod.py | 65 ++++++++++++++++++------------------- podctl/proc.py | 9 ++--- podctl/script.py | 48 ++++++++++++++++++--------- podctl/scripts.py | 50 ++++++++++++++++++++++++++++ podctl/visitable.py | 9 ++--- podctl/visitors/base.py | 3 ++ podctl/visitors/commit.py | 20 ++---------- podctl/visitors/dumbinit.py | 7 ++-- podctl/visitors/packages.py | 3 ++ 13 files changed, 212 insertions(+), 82 deletions(-) create mode 100644 podctl/scripts.py diff --git a/podctl/build.py b/podctl/build.py index f0b10e3..3703d88 100644 --- a/podctl/build.py +++ b/podctl/build.py @@ -22,11 +22,11 @@ class Build(Script): async def config(self, line): """Run buildah config.""" - return await self.append(f'buildah config {line} {self.ctr}') + return await self.exec(f'buildah config {line} {self.ctr}') async def copy(self, src, dst): """Run buildah copy to copy a file from host into container.""" - return await self.append(f'buildah copy {self.ctr} {src} {dst}') + return await self.exec(f'buildah copy {self.ctr} {src} {dst}') async def cexec(self, *args, user=None, **kwargs): """Execute a command in the container.""" @@ -75,3 +75,6 @@ class Build(Script): p = os.path.join(self.mnt, path[1:], c) if os.path.exists(p): return p[len(str(self.mnt)):] + + def __repr__(self): + return f'Build' diff --git a/podctl/console_script.py b/podctl/console_script.py index cf4ff91..fc55495 100644 --- a/podctl/console_script.py +++ b/podctl/console_script.py @@ -33,6 +33,10 @@ class ConsoleScript(cli2.ConsoleScript): self.funckwargs['cmd'] = self.forward_args + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.options = dict() + def __call__(self, *args, **kwargs): import inspect from podctl.podfile import Podfile @@ -43,18 +47,22 @@ class ConsoleScript(cli2.ConsoleScript): self[name] = cli2.Callable( name, cb, - options=script.options, + options={o.name: o for o in script.options}, + color=getattr(script, 'color', cli2.YELLOW), ) return super().__call__(*args, **kwargs) def call(self, command): + self.options = self.parser.options + try: return super().call(command) except Mistake as e: print(e) sys.exit(1) - except WrongResult: - sys.exit(1) + except WrongResult as e: + print(e) + sys.exit(e.proc.rc) console_script = ConsoleScript(__doc__).add_module('podctl.console_script') diff --git a/podctl/container.py b/podctl/container.py index ce5b775..dd4a938 100644 --- a/podctl/container.py +++ b/podctl/container.py @@ -1,5 +1,53 @@ +from .build import Build +from .exceptions import WrongResult from .visitable import Visitable class Container(Visitable): - pass + default_scripts = dict( + build=Build(), + ) + + @property + def container_name(self): + return '-'.join([self.pod.name, self.name]) + + async def down(self, script): + try: + await script.exec('podman', 'inspect', self.container_name) + except WrongResult: + pass + else: + try: + from podctl.console_script import console_script + argv = console_script.parser.nonoptions + except AttributeError: + argv = [] + argv = argv + [self.container_name] + await script.exec('podman', 'rm', '-f', *argv) + + async def run(self, script): + await script.exec( + 'podman', 'run', + '--name', self.container_name, + ':'.join((self.variable('repo'), self.variable('tags')[0])), + ) + + async def up(self, script): + try: + await script.exec('podman', 'inspect', self.container_name) + except WrongResult as ee: + tag = ':'.join(( + self.variable('repo'), + self.variable('tags')[0], + )) + print(f'{self.name} | Container creating') + await script.exec( + 'podman', 'run', '-d', '--name', self.container_name, + tag, + ) + print(f'{self.name} | Container created') + else: + print(f'{self.name} | Container starting') + await script.exec('podman', 'start', self.container_name) + print(f'{self.name} | Container started') diff --git a/podctl/exceptions.py b/podctl/exceptions.py index 1873e11..099faf7 100644 --- a/podctl/exceptions.py +++ b/podctl/exceptions.py @@ -7,4 +7,11 @@ class Mistake(PodctlException): class WrongResult(PodctlException): - pass + def __init__(self, proc): + self.proc = proc + super().__init__('\n'.join([i for i in [ + f'Command failed ! Exit with {proc.rc}' + '+ ' + proc.cmd, + proc.out, + proc.err, + ]])) diff --git a/podctl/pod.py b/podctl/pod.py index bbf7028..86f14b6 100644 --- a/podctl/pod.py +++ b/podctl/pod.py @@ -1,47 +1,44 @@ -import asyncio -import copy import os from .build import Build from .container import Container -from .exceptions import WrongResult -from .script import Script +from .scripts import * from .visitable import Visitable -class Up(Script): - async def run(self, *args, **kwargs): - for key, value in kwargs.items(): - setattr(self, key, value) - - pod = kwargs.get('pod') - - try: - pod.info = (await self.exec( - 'podman', 'pod', 'inspect', pod.name - )).json - print(f'Pod {pod.name} ready') - except WrongResult: - print(f'Pod {pod.name} creating') - await self.exec( - 'podman', 'pod', 'create', '--name', self.pod.name, - ) - print(f'Pod {pod.name} created') - pod.info = (await self.exec( - 'podman', 'pod', 'inspect', pod.name - )).json - - return await super().run(*args, **kwargs) - - class Pod(Visitable): default_scripts = dict( build=Build(), - run=Script('run', 'Run a container command'), up=Up('up', 'Start the stack'), - test=Script('test', 'Run tests inside containers'), + down=Down('down', 'Destroy the stack'), + run=Run('run', 'Run a command in container(s)'), + name=Name( + 'name', + 'Output the pod name for usage with podman', + ), ) + async def down(self, script): + try: + await script.exec('podman', 'pod', 'inspect', self.name) + except WrongResult: + pass + else: + await script.exec('podman', 'pod', 'rm', self.name) + + async def up(self, script): + try: + await script.exec( + 'podman', 'pod', 'inspect', self.name + ) + print(f'{self.name} | Pod ready') + except WrongResult: + print(f'{self.name} | Pod creating') + await script.exec( + 'podman', 'pod', 'create', '--name', self.name, + ) + print(f'{self.name} | Pod created') + @property def name(self): return os.getenv('POD', os.getcwd().split('/')[-1]) @@ -53,7 +50,9 @@ class Pod(Visitable): def script(self, name): async def cb(*args, **kwargs): asyncio.events.get_event_loop() - script = copy.deepcopy(self.scripts[name]) kwargs['pod'] = self - return await script.run(*args, **kwargs) + return await self.scripts[name].run(*args, **kwargs) return cb + + def __repr__(self): + return self.name diff --git a/podctl/proc.py b/podctl/proc.py index c5a5a54..04f789e 100644 --- a/podctl/proc.py +++ b/podctl/proc.py @@ -21,7 +21,7 @@ class PrefixStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol): def pipe_data_received(self, fd, data): from .console_script import console_script - debug = console_script.parser.options.get('debug', False) + debug = console_script.options.get('debug', False) if (debug is True or 'out' in str(debug)) and fd in (1, 2): for line in data.split(b'\n'): @@ -34,6 +34,7 @@ class PrefixStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol): sys.stdout.flush() super().pipe_data_received(fd, data) + def protocol_factory(prefix): def _p(): return PrefixStreamProtocol( @@ -77,8 +78,8 @@ class Proc: raise Exception('Already called: ' + self.cmd) from .console_script import console_script - debug = console_script.parser.options.get('debug', False) - if debug is True or 'proc' in str(debug): + debug = console_script.options.get('debug', False) + if debug is True or 'cmd' in str(debug): if self.prefix: print(f'{self.prefix} | + {self.cmd}') else: @@ -109,7 +110,7 @@ class Proc: if not self.communicated: await self.communicate() if self.raises and self.proc.returncode: - raise WrongResult() + raise WrongResult(self) return self @property diff --git a/podctl/script.py b/podctl/script.py index 7ef6cf7..698af3a 100644 --- a/podctl/script.py +++ b/podctl/script.py @@ -1,4 +1,5 @@ import asyncio +import copy import cli2 import textwrap @@ -11,10 +12,11 @@ class Script: 'debug', alias='d', color=cli2.GREEN, + default='visit', help=''' - Display debug output. - Supports values: proc,out,visit - ''' + Display debug output. Supports values (combinable): cmd,out,visit + '''.strip(), + immediate=True, ), ] @@ -24,7 +26,7 @@ class Script: async def exec(self, *args, **kwargs): """Execute a command on the host.""" - if getattr(self, 'container', None): + if getattr(self, 'container', None) and getattr(self.container, 'name', None): kwargs.setdefault('prefix', self.container.name) proc = await Proc(*args, **kwargs)() if kwargs.get('wait', True): @@ -33,8 +35,9 @@ class Script: async def __call__(self, visitable, *args, **kwargs): from .console_script import console_script - debug = console_script.parser.options.get('debug', False) + debug = console_script.options.get('debug', False) + self.args = args for key, value in kwargs.items(): setattr(self, key, value) @@ -44,7 +47,14 @@ class Script: async def clean(): for visitor in visitable.visitors: if hasattr(visitor, 'clean_' + self.name): - result = getattr(visitor, 'clean_' + self.name)(self) + method = 'clean_' + self.name + result = getattr(visitor, method)(self) + if debug is True or 'visit' in str(debug): + print( + getattr(visitable, 'name', '') + ' | ', + '.'.join([type(visitor).__name__, method]), + ' '.join(f'{k}={v}' for k, v in visitor.__dict__.items()) + ) if result: await result @@ -56,7 +66,7 @@ class Script: if debug is True or 'visit' in str(debug): print( - visitable.name + ' | ', + getattr(visitable, 'name', '') + ' | ', '.'.join([type(visitor).__name__, method]), ' '.join(f'{k}={v}' for k, v in visitor.__dict__.items()) ) @@ -69,20 +79,28 @@ class Script: raise async def run(self, *args, **kwargs): - pod = kwargs.get('pod') + for key, value in kwargs.items(): + setattr(self, key, value) if args: - containers = [c for c in pod.containers if c.name in args] + containers = [c for c in self.pod.containers if c.name in args] else: - containers = pod.containers + containers = self.pod.containers - procs = [] - for container in containers: - procs.append(self( + procs = [ + copy.deepcopy(self)( + self.pod, + *args, + **kwargs, + ) + ] + procs += [ + copy.deepcopy(self)( container, *args, container=container, **kwargs, - )) - + ) + for container in containers + ] return await asyncio.gather(*procs) diff --git a/podctl/scripts.py b/podctl/scripts.py new file mode 100644 index 0000000..4146928 --- /dev/null +++ b/podctl/scripts.py @@ -0,0 +1,50 @@ +import asyncio +import cli2 +import copy +import os +import sys + +from .build import Build +from .exceptions import WrongResult +from .proc import Proc +from .script import Script + + +class Name(Script): + color = cli2.GREEN + + async def run(self, *args, **kwargs): + print(kwargs.get('pod').name) + + +class Down(Script): + color = cli2.RED + + +class Up(Script): + pass + + +class Run(Script): + async def run(self, *args, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + + try: + await self.exec( + 'podman', 'pod', 'inspect', self.pod.name + ) + print(f'{self.pod.name} | Pod ready') + except WrongResult: + print(f'{self.pod.name} | Pod creating') + await self.exec( + 'podman', 'pod', 'create', '--name', self.pod.name, + ) + print(f'{self.pod.name} | Pod created') + + return await asyncio.gather(*[ + copy.deepcopy(self)( + self.pod, + ) + for container in self.pod.containers + ]) diff --git a/podctl/visitable.py b/podctl/visitable.py index e03bb07..e66f886 100644 --- a/podctl/visitable.py +++ b/podctl/visitable.py @@ -1,5 +1,5 @@ import asyncio -from copy import copy +from copy import copy, deepcopy class Visitable: @@ -7,13 +7,14 @@ class Visitable: def __init__(self, *visitors, **scripts): self.visitors = list(visitors) - self.scripts = scripts or { - k: v for k, v in self.default_scripts.items() - } + self.scripts = deepcopy(self.default_scripts) + self.scripts.update(scripts) + ''' def script(self, name): script = copy(self.scripts[name]) return script +''' def visitor(self, name): for visitor in self.visitors: diff --git a/podctl/visitors/base.py b/podctl/visitors/base.py index 684eef8..bdc0456 100644 --- a/podctl/visitors/base.py +++ b/podctl/visitors/base.py @@ -13,3 +13,6 @@ class Base: await script.umounts() await script.umount() proc = await script.exec('buildah', 'rm', script.ctr, raises=False) + + def __repr__(self): + return f'Base({self.base})' diff --git a/podctl/visitors/commit.py b/podctl/visitors/commit.py index c470469..54dafa0 100644 --- a/podctl/visitors/commit.py +++ b/podctl/visitors/commit.py @@ -18,7 +18,7 @@ CI_VARS = ( class Commit: def __init__(self, repo, tags=None, format=None, push=None, registry=None): self.repo = repo - self.registry = registry + self.registry = registry or 'localhost' self.push = push or os.getenv('CI') # figure out registry host @@ -81,19 +81,5 @@ class Commit: await script.exec('podman', 'push', f'{self.repo}:{tag}') await script.umount() - async def run(self, script): - await script.exec( - 'podman', 'run', '-d', - '--name', script.container.name, - ':'.join((self.repo, self.tags[0])), - ) - - async def up(self, script): - name = '-'.join([script.pod.name, script.container.name]) - try: - await script.exec('podman', 'inspect', name) - except WrongResult: - await script.exec( - 'podman', 'run', '-d', '--name', name, - ':'.join((self.repo, self.tags[0])), - ) + def __repr__(self): + return f'Commit({self.registry}/{self.repo}:{self.tags})' diff --git a/podctl/visitors/dumbinit.py b/podctl/visitors/dumbinit.py index d98c1a7..4e73edf 100644 --- a/podctl/visitors/dumbinit.py +++ b/podctl/visitors/dumbinit.py @@ -10,6 +10,9 @@ class DumbInit: def __init__(self, cmd): self.cmd = cmd - def post_build(self, script): + async def post_build(self, script): cmd = '--cmd "dumb-init bash -euxc \'%s\'"' % self.cmd - script.config(cmd) + await script.config(cmd) + + def __repr__(self): + return f'DumbInit({self.cmd})' diff --git a/podctl/visitors/packages.py b/podctl/visitors/packages.py index 1a9be46..3738f42 100644 --- a/podctl/visitors/packages.py +++ b/podctl/visitors/packages.py @@ -150,3 +150,6 @@ class Packages: cache_lists = os.path.join(cachedir, 'lists') await script.mount(cache_lists, f'/var/lib/apt/lists') return cachedir + + def __repr__(self): + return f'Packages({self.packages})'