diff options
Diffstat (limited to 'contrib')
-rw-r--r-- | contrib/cirrus/README.md | 75 | ||||
-rwxr-xr-x | contrib/cirrus/build_vm_images.sh | 4 | ||||
-rw-r--r-- | contrib/cirrus/lib.sh | 36 | ||||
-rw-r--r-- | contrib/cirrus/packer/fedora_setup.sh | 3 | ||||
-rw-r--r-- | contrib/cirrus/packer/ubuntu_setup.sh | 7 | ||||
-rw-r--r-- | contrib/python/podman/podman/libs/images.py | 2 | ||||
-rw-r--r-- | contrib/python/pypodman/pypodman/lib/__init__.py | 5 | ||||
-rw-r--r-- | contrib/python/pypodman/pypodman/lib/actions/commit_action.py | 32 | ||||
-rw-r--r-- | contrib/python/pypodman/pypodman/lib/actions/export_action.py | 22 | ||||
-rw-r--r-- | contrib/python/pypodman/pypodman/lib/actions/history_action.py | 2 | ||||
-rw-r--r-- | contrib/python/pypodman/pypodman/lib/actions/images_action.py | 8 | ||||
-rw-r--r-- | contrib/python/pypodman/pypodman/lib/actions/import_action.py | 41 | ||||
-rw-r--r-- | contrib/python/pypodman/pypodman/lib/parser_actions.py | 51 | ||||
-rw-r--r-- | contrib/python/pypodman/pypodman/lib/report.py | 29 |
14 files changed, 233 insertions, 84 deletions
diff --git a/contrib/cirrus/README.md b/contrib/cirrus/README.md new file mode 100644 index 000000000..0d315c4f5 --- /dev/null +++ b/contrib/cirrus/README.md @@ -0,0 +1,75 @@ +![PODMAN logo](../../logo/podman-logo-source.svg) + +# Cirrus-CI + +Similar to other integrated github CI/CD services, Cirrus utilizes a simple +YAML-based configuration/description file: ``.cirrus.yml``. Ref: https://cirrus-ci.org/ + +## Workflow + +All tasks execute in parallel, unless there are conditions or dependencies +which alter this behavior. Within each task, each script executes in sequence, +so long as any previous script exited successfully. The overall state of each +task (pass or fail) is set based on the exit status of the last script to execute. + +### ``full_vm_testing`` Task + +1. Unconditionally, spin up one VM per ``matrix: image_name`` item defined + in ``.cirrus.yml``. Once accessible, ``ssh`` into each VM and run the following + scripts. + +2. ``setup_environment.sh``: Configure root's ``.bash_profile`` + for all subsequent scripts (each run in a new shell). Any + distribution-specific environment variables are also defined + here. For example, setting tags/flags to use compiling. + +3. ``verify_source.sh``: Perform per-distribution source + verification, lint-checking, etc. This acts as a minimal + gate, blocking extended use of VMs when a PR's code or commits + would otherwise not be accepted. Should run for less than a minute. + +4. ``unit_test.sh``: Execute unit-testing, as defined by the ``Makefile``. + This should execute within 10-minutes, but often much faster. + +5. ``integration_test.sh``: Execute integration-testing. This is + much more involved, and relies on access to external + resources like container images and code from other repositories. + Total execution time is capped at 2-hours (includes all the above) + but this script normally completes in less than an hour. + +### ``build_vm_images`` Task + +1. When a PR is merged (``$CIRRUS_BRANCH`` == ``master``), run another + round of the ``full_vm_testing`` task (above). + +2. After confirming the tests all pass post-merge, spin up a special VM + capable of communicating with the GCE API. Once accessible, ``ssh`` into + the special VM and run the following scripts. + +3. ``setup_environment.sh``: Configure root's ``.bash_profile`` + for all subsequent scripts (each run in a new shell). Any + distribution-specific environment variables are also defined + here. For example, setting tags/flags to use compiling. + +4. ``build_vm_images.sh``: Examine the merged PR's description on github. + If it contains the magic string ``***CIRRUS: REBUILD IMAGES***``, then + continue. Otherwise display a message, take no further action, and + exit successfully. This prevents production of new VM images unless + they are called for, thereby saving the cost of needlessly storing them. + +5. If the magic string was found, utilize [the packer tool](http://packer.io/docs/) + to produce new VM images. Create a new VM from each base-image, connect + to them with ``ssh``, and perform these steps as defined by the + ``libpod_images.json`` file. + + 1. Copy the current state of the repository into ``/tmp/libpod``. + 2. Execute distribution-specific scripts to prepare the image for + use by the ``full_vm_testing`` task (above). + 3. If successful, shut down each VM and create a new GCE Image + named after the base image and the commit sha of the merge. + +***Note:*** The ``.cirrus.yml`` file must be manually updated with the new +images names, then the change sent in via a secondary pull-request. This +ensures that all the ``full_vm_testing`` tasks can pass with the new images, +before subjecting all future PRs to them. A workflow to automate this +process is described in comments at the end of the ``.cirrus.yml`` file. diff --git a/contrib/cirrus/build_vm_images.sh b/contrib/cirrus/build_vm_images.sh index 80c689a6c..ffbb2d5d5 100755 --- a/contrib/cirrus/build_vm_images.sh +++ b/contrib/cirrus/build_vm_images.sh @@ -22,9 +22,7 @@ SCRIPT_BASE $SCRIPT_BASE PACKER_BASE $PACKER_BASE " -# TODO: Skip building images if $CIRRUS_BRANCH =~ "master" and -# commit message of $CIRRUS_CHANGE_IN_REPO contains a magic word -# produced by 'commit_and_create_upstream_pr.sh' script (see .cirrus.yml) +require_regex '\*\*\*\s*CIRRUS:\s*REBUILD\s*IMAGES\s*\*\*\*' 'Not re-building VM images' show_env_vars diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index 1e0052a65..2fa91258b 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -117,6 +117,22 @@ cdsudo() { sudo --preserve-env=GOPATH --non-interactive bash -c "$CMD" } +# Skip a build if $1 does not match in the PR Title/Description with message $2 +require_regex() { + req_env_var " + CIRRUS_CHANGE_MESSAGE $CIRRUS_CHANGE_MESSAGE + 1 $1 + 2 $2 + " + regex="$1" + msg="$2" + if ! echo "$CIRRUS_CHANGE_MESSAGE" | egrep -q "$regex" + then + echo "***** The PR Title/Description did not match the regular expression: $MAGIC_RE" + echo "***** $msg" + exit 0 + fi +} # Helper/wrapper script to only show stderr/stdout on non-zero exit install_ooe() { @@ -142,8 +158,8 @@ EOF install_cni_plugins() { echo "Installing CNI Plugins from commit $CNI_COMMIT" req_env_var " - GOPATH $GOPATH - CNI_COMMIT $CNI_COMMIT + GOPATH $GOPATH + CNI_COMMIT $CNI_COMMIT " DEST="$GOPATH/src/github.com/containernetworking/plugins" rm -rf "$DEST" @@ -160,9 +176,9 @@ install_runc(){ echo "Installing RunC from commit $RUNC_COMMIT" echo "Platform is $OS_RELEASE_ID" req_env_var " - GOPATH $GOPATH - RUNC_COMMIT $RUNC_COMMIT - OS_RELEASE_ID $OS_RELEASE_ID + GOPATH $GOPATH + RUNC_COMMIT $RUNC_COMMIT + OS_RELEASE_ID $OS_RELEASE_ID " if [[ "$OS_RELEASE_ID" =~ "ubuntu" ]]; then echo "Running make install.libseccomp.sudo for ubuntu" @@ -202,8 +218,8 @@ install_buildah() { install_conmon(){ echo "Installing conmon from commit $CRIO_COMMIT" req_env_var " - GOPATH $GOPATH - CRIO_COMMIT $CRIO_COMMIT + GOPATH $GOPATH + CRIO_COMMIT $CRIO_COMMIT " DEST="$GOPATH/src/github.com/kubernetes-sigs/cri-o.git" rm -rf "$DEST" @@ -234,8 +250,8 @@ install_criu(){ install_testing_dependencies() { echo "Installing ginkgo, gomega, and easyjson into \$GOPATH=$GOPATH" req_env_var " - GOPATH $GOPATH - GOSRC $GOSRC + GOPATH $GOPATH + GOSRC $GOSRC " cd "$GOSRC" ooe.sh go get -u github.com/onsi/ginkgo/ginkgo @@ -263,7 +279,7 @@ install_varlink(){ _finalize(){ echo "Removing leftover giblets from cloud-init" cd / - sudo rm -rf /var/lib/cloud + sudo rm -rf /var/lib/cloud/instance? sudo rm -rf /root/.ssh/* sudo rm -rf /home/* } diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh index 16b6e4e6b..f9fea04a7 100644 --- a/contrib/cirrus/packer/fedora_setup.sh +++ b/contrib/cirrus/packer/fedora_setup.sh @@ -21,8 +21,7 @@ install_ooe export GOPATH="$(mktemp -d)" trap "sudo rm -rf $GOPATH" EXIT -# breaks networking on f28/29 in GCE -# ooe.sh sudo dnf update -y +ooe.sh sudo dnf update -y ooe.sh sudo dnf install -y \ atomic-registries \ diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh index ff20944dc..4cf1f335b 100644 --- a/contrib/cirrus/packer/ubuntu_setup.sh +++ b/contrib/cirrus/packer/ubuntu_setup.sh @@ -21,9 +21,10 @@ install_ooe export GOPATH="$(mktemp -d)" trap "sudo rm -rf $GOPATH" EXIT -ooe.sh sudo apt-get -qq update -ooe.sh sudo apt-get -qq update # sometimes it needs to get it twice :S -ooe.sh sudo apt-get -qq upgrade +# Try twice as workaround for minor networking problems +echo "Updating system and installing package dependencies" +ooe.sh sudo apt-get -qq update || sudo apt-get -qq update +ooe.sh sudo apt-get -qq upgrade || sudo apt-get -qq upgrade ooe.sh sudo apt-get -qq install --no-install-recommends \ apparmor \ autoconf \ diff --git a/contrib/python/podman/podman/libs/images.py b/contrib/python/podman/podman/libs/images.py index 982546cd2..9453fb416 100644 --- a/contrib/python/podman/podman/libs/images.py +++ b/contrib/python/podman/podman/libs/images.py @@ -137,7 +137,7 @@ class Images(): results = podman.DeleteUnusedImages() return results['images'] - def import_image(self, source, reference, message=None, changes=None): + def import_image(self, source, reference, message='', changes=None): """Read image tarball from source and save in image store.""" with self._client() as podman: results = podman.ImportImage(source, reference, message, changes) diff --git a/contrib/python/pypodman/pypodman/lib/__init__.py b/contrib/python/pypodman/pypodman/lib/__init__.py index 5525ddaef..be1b5f467 100644 --- a/contrib/python/pypodman/pypodman/lib/__init__.py +++ b/contrib/python/pypodman/pypodman/lib/__init__.py @@ -4,14 +4,15 @@ import sys import podman from pypodman.lib.action_base import AbstractActionBase from pypodman.lib.parser_actions import (BooleanAction, BooleanValidate, - PathAction, PositiveIntAction, - UnitAction) + ChangeAction, PathAction, + PositiveIntAction, UnitAction) from pypodman.lib.podman_parser import PodmanArgumentParser from pypodman.lib.report import Report, ReportColumn # Silence pylint overlording... assert BooleanAction assert BooleanValidate +assert ChangeAction assert PathAction assert PositiveIntAction assert UnitAction diff --git a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py b/contrib/python/pypodman/pypodman/lib/actions/commit_action.py index 0da6a2078..21665ad0b 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/commit_action.py @@ -2,7 +2,7 @@ import sys import podman -from pypodman.lib import AbstractActionBase, BooleanAction +from pypodman.lib import AbstractActionBase, BooleanAction, ChangeAction class Commit(AbstractActionBase): @@ -12,7 +12,9 @@ class Commit(AbstractActionBase): def subparser(cls, parent): """Add Commit command to parent parser.""" parser = parent.add_parser( - 'commit', help='create image from container') + 'commit', + help='create image from container', + ) parser.add_argument( '--author', help='Set the author for the committed image', @@ -20,11 +22,7 @@ class Commit(AbstractActionBase): parser.add_argument( '--change', '-c', - choices=('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', 'ONBUILD', - 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR'), - action='append', - type=str.upper, - help='Apply the following possible changes to the created image', + action=ChangeAction, ) parser.add_argument( '--format', @@ -69,27 +67,11 @@ class Commit(AbstractActionBase): ) parser.set_defaults(class_=cls, method='commit') - def __init__(self, args): - """Construct Commit class.""" - if not args.container: - raise ValueError('You must supply one container id' - ' or name to be used as source.') - if not args.image: - raise ValueError('You must supply one image id' - ' or name to be created.') - super().__init__(args) - - # used only on client - del self.opts['image'] - del self.opts['container'] - def commit(self): """Create image from container.""" try: try: ctnr = self.client.containers.get(self._args.container[0]) - ident = ctnr.commit(**self.opts) - print(ident) except podman.ContainerNotFound as e: sys.stdout.flush() print( @@ -97,6 +79,9 @@ class Commit(AbstractActionBase): file=sys.stderr, flush=True) return 1 + else: + ident = ctnr.commit(self.opts['image'][0], **self.opts) + print(ident) except podman.ErrorOccurred as e: sys.stdout.flush() print( @@ -104,3 +89,4 @@ class Commit(AbstractActionBase): file=sys.stderr, flush=True) return 1 + return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/export_action.py b/contrib/python/pypodman/pypodman/lib/actions/export_action.py index f62cd3535..7ef178c4c 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/export_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/export_action.py @@ -12,13 +12,16 @@ class Export(AbstractActionBase): def subparser(cls, parent): """Add Export command to parent parser.""" parser = parent.add_parser( - 'export', help='export container to tarball') + 'export', + help='export container to tarball', + ) parser.add_argument( '--output', '-o', metavar='PATH', nargs=1, - help='Write to a file', + required=True, + help='Write to this file on host', ) parser.add_argument( 'container', @@ -27,23 +30,11 @@ class Export(AbstractActionBase): ) parser.set_defaults(class_=cls, method='export') - def __init__(self, args): - """Construct Export class.""" - if not args.container: - raise ValueError('You must supply one container id' - ' or name to be used as source.') - - if not args.output: - raise ValueError('You must supply one filename' - ' to be created as tarball using --output.') - super().__init__(args) - def export(self): """Create tarball from container filesystem.""" try: try: ctnr = self.client.containers.get(self._args.container[0]) - ctnr.export(self._args.output[0]) except podman.ContainerNotFound as e: sys.stdout.flush() print( @@ -51,6 +42,8 @@ class Export(AbstractActionBase): file=sys.stderr, flush=True) return 1 + else: + ctnr.export(self._args.output[0]) except podman.ErrorOccurred as e: sys.stdout.flush() print( @@ -58,3 +51,4 @@ class Export(AbstractActionBase): file=sys.stderr, flush=True) return 1 + return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/history_action.py b/contrib/python/pypodman/pypodman/lib/actions/history_action.py index 3e3f539fc..f9aaa54f6 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/history_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/history_action.py @@ -60,7 +60,7 @@ class History(AbstractActionBase): if self._args.human: fields.update({ 'size': - humanize.naturalsize(details.size, binary=True), + humanize.naturalsize(details.size), 'created': humanize.naturaldate( podman.datetime_parse(details.created)), diff --git a/contrib/python/pypodman/pypodman/lib/actions/images_action.py b/contrib/python/pypodman/pypodman/lib/actions/images_action.py index d28e32db9..29bf90dd2 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/images_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/images_action.py @@ -37,7 +37,7 @@ class Images(AbstractActionBase): self.columns = OrderedDict({ 'name': - ReportColumn('name', 'REPOSITORY', 40), + ReportColumn('name', 'REPOSITORY', 0), 'tag': ReportColumn('tag', 'TAG', 10), 'id': @@ -65,18 +65,18 @@ class Images(AbstractActionBase): 'created': humanize.naturaldate(podman.datetime_parse(image.created)), 'size': - humanize.naturalsize(int(image.size), binary=True), + humanize.naturalsize(int(image.size)), 'repoDigests': ' '.join(image.repoDigests), }) for r in image.repoTags: - name, tag = r.split(':', 1) + name, tag = r.rsplit(':', 1) fields.update({ 'name': name, 'tag': tag, }) - rows.append(fields) + rows.append(fields) if not self._args.digests: del self.columns['repoDigests'] diff --git a/contrib/python/pypodman/pypodman/lib/actions/import_action.py b/contrib/python/pypodman/pypodman/lib/actions/import_action.py index 49b8a5a57..43448144a 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/import_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/import_action.py @@ -2,7 +2,7 @@ import sys import podman -from pypodman.lib import AbstractActionBase +from pypodman.lib import AbstractActionBase, ChangeAction class Import(AbstractActionBase): @@ -12,18 +12,19 @@ class Import(AbstractActionBase): def subparser(cls, parent): """Add Import command to parent parser.""" parser = parent.add_parser( - 'import', help='import tarball as image filesystem') + 'import', + help='import tarball as image filesystem', + ) parser.add_argument( '--change', '-c', - action='append', - choices=('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', - 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR'), - type=str.upper, - help='Apply the following possible instructions', + action=ChangeAction, ) parser.add_argument( - '--message', '-m', help='Set commit message for imported image.') + '--message', + '-m', + help='Set commit message for imported image.', + ) parser.add_argument( 'source', metavar='PATH', @@ -38,18 +39,25 @@ class Import(AbstractActionBase): ) parser.set_defaults(class_=cls, method='import_') - def __init__(self, args): - """Construct Import class.""" - super().__init__(args) - def import_(self): """Import tarball as image filesystem.""" + # ImportImage() validates it's parameters therefore we need to create + # pristine dict() for keywords + options = {} + if 'message' in self.opts: + options['message'] = self.opts['message'] + if 'change' in self.opts and self.opts['change']: + options['changes'] = self.opts['change'] + + reference = self.opts['reference'][0] if 'reference' in self.opts\ + else None + try: ident = self.client.images.import_image( - self.opts.source, - self.opts.reference, - message=self.opts.message, - changes=self.opts.change) + self.opts['source'][0], + reference, + **options, + ) print(ident) except podman.ErrorOccurred as e: sys.stdout.flush() @@ -58,3 +66,4 @@ class Import(AbstractActionBase): file=sys.stderr, flush=True) return 1 + return 0 diff --git a/contrib/python/pypodman/pypodman/lib/parser_actions.py b/contrib/python/pypodman/pypodman/lib/parser_actions.py index 2a5859e47..c10b85495 100644 --- a/contrib/python/pypodman/pypodman/lib/parser_actions.py +++ b/contrib/python/pypodman/pypodman/lib/parser_actions.py @@ -4,9 +4,10 @@ Supplimental argparse.Action converters and validaters. The constructors are very verbose but remain for IDE support. """ import argparse +import copy import os -# API defined by argparse.Action shut up pylint +# API defined by argparse.Action therefore shut up pylint # pragma pylint: disable=redefined-builtin # pragma pylint: disable=too-few-public-methods # pragma pylint: disable=too-many-arguments @@ -63,6 +64,54 @@ class BooleanAction(argparse.Action): setattr(namespace, self.dest, val) +class ChangeAction(argparse.Action): + """Convert and validate change argument.""" + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=[], + type=None, + choices=None, + required=False, + help=None, + metavar='OPT=VALUE'): + """Create ChangeAction object.""" + help = (help or '') + ('Apply change(s) to the new image.' + ' May be given multiple times.') + + super().__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + """Convert and Validate input.""" + print(self.dest) + items = getattr(namespace, self.dest, None) or [] + items = copy.copy(items) + + choices = ('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', 'ONBUILD', + 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR') + + opt, val = values.split('=', 1) + if opt not in choices: + parser.error('{} is not a supported "--change" option,' + ' valid options are: {}'.format( + opt, ', '.join(choices))) + items.append(values) + setattr(namespace, self.dest, items) + + class UnitAction(argparse.Action): """Validate number given is positive integer, with optional suffix.""" diff --git a/contrib/python/pypodman/pypodman/lib/report.py b/contrib/python/pypodman/pypodman/lib/report.py index 1db4268da..b689390fd 100644 --- a/contrib/python/pypodman/pypodman/lib/report.py +++ b/contrib/python/pypodman/pypodman/lib/report.py @@ -1,8 +1,23 @@ """Report Manager.""" +import string import sys from collections import namedtuple +class ReportFormatter(string.Formatter): + """Custom formatter to default missing keys to '<none>'.""" + + def get_value(self, key, args, kwargs): + """Map missing key to value '<none>'.""" + try: + if isinstance(key, int): + return args[key] + else: + return kwargs[key] + except KeyError: + return '<none>' + + class ReportColumn(namedtuple('ReportColumn', 'key display width default')): """Hold attributes of output column.""" @@ -26,18 +41,24 @@ class Report(): """ self._columns = columns self._file = file + self._format_string = None + self._formatter = ReportFormatter() self._heading = heading self.epilog = epilog - self._format = None def row(self, **fields): """Print row for report.""" if self._heading: hdrs = {k: v.display for (k, v) in self._columns.items()} - print(self._format.format(**hdrs), flush=True, file=self._file) + print( + self._formatter.format(self._format_string, **hdrs), + flush=True, + file=self._file, + ) self._heading = False + fields = {k: str(v) for k, v in fields.items()} - print(self._format.format(**fields)) + print(self._formatter.format(self._format_string, **fields)) def __enter__(self): """Return `self` upon entering the runtime context.""" @@ -63,4 +84,4 @@ class Report(): display_len = info.width fmt.append('{{{0}:{1}.{1}}}'.format(key, display_len)) - self._format = ' '.join(fmt) + self._format_string = ' '.join(fmt) |