#!/usr/bin/python # # Copyright 2016 Red Hat | Ansible # GNU General Public License v3.0+ (see COPYING or www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function __metaclass__ = type
import json
from ansible.module_utils.docker_common import AnsibleDockerClient
import qb.ipc.stdio import qb.ipc.stdio.logging
class QBAnsibleDockerClient(AnsibleDockerClient):
# Construction # ======================================================================== def __init__(self, *args, **kwds): self.logger = qb.ipc.stdio.logging.getLogger( 'qb.ansible.modules.docker.client.QBAnsibleDockerClient' ) AnsibleDockerClient.__init__(self, *args, **kwds) # Instance Methods # ============================================================================ # Logging and Output # ---------------------------------------------------------------------------- def out(self, msg): ''' Write output from the Docker daemon to STDOUT for the user to see what's going on. This used to be called `log` and was used for more than just logging output from Docker, and it didn't do anything... though there was some commented out code to write to a file. Now it writes to the QB master process' STDOUT through `QB::IPC::STDOUT`, assuming that's present. :param msg - A string or dict. :return: None ''' # Bail unless QB STDOUT is connected if not qb.ipc.stdio.client.stdout.connected: return None # What we're gonna write string = None if isinstance(msg, str): # If the message is just a string, write that string = msg elif isinstance( msg, dict ): # Dicts come for the Docker daemon/API, so we want to extract the # relevant output and display that nicely... work in progress if 'stream' in msg: # This is part of an output 'stream' from a build, # so just grab that that variable. string = msg['stream'] elif 'status' in msg: # This is part of an output from a pull (and maybe more?) if 'id' in msg: if 'progress' in msg: string = "{id} {status} {progress}".format(**msg) else: string = "{id} {status}".format(**msg) else: string = msg['status'] else: # Structures we're not sure how to deal with yet... just # just pretty-dump them string = json.dumps( msg, sort_keys=True, indent=4, separators=(',', ': ') ) else: self.logger.warning( "Unregonized `msg` type {} in .out", type(msg), payload=dict(msg=msg) ) if string is not None: qb.ipc.stdio.client.stdout.println(string) def log(self, msg, pretty_print=False): ''' Override :class:`AnsibleDockerClient.log` to actually do something. It's used in :class:`AnsibleDockerClient` to do *both* logging and relaying Docker API/daemon output, but we try to split it up and send logging to :attr:`logger` and output to :meth:`out` - output seems to always be `dict`, though this might be wrong. :param msg: A stirng or dict. :param pretty_print: Boolean, but not used - part of super API. :return: None ''' if isinstance(msg, dict): self.out(msg) elif isinstance(msg, str): self.logger.info(msg) else: self.logger.warning( "Unregonized `msg` type {} in .log".format(type(msg)), payload=dict(msg=msg) ) def fail(self, msg, **values): ''' Overrides :class:`AnsibleDockerClient.fail` to log the failure first (as `critical`/`fatal`). Also adds feature to accept a dict of values which will be :meth: `str.format` into the `msg` and also logged as the payload. :param msg: String message, which may have `{key}` template markers in it to be subsititued from `values`. :param values: Optional dict of values to interpolate and log. :return: See :class:`AnsibleDockerClient.fail` ''' self.logger.critical(msg, payload=values) return super(QBAnsibleDockerClient, self).fail(msg) # Actions # ------------------------------------------------------------------------ def try_pull_image(self, name, tag="latest"): ''' Try to pull an image (before building or loading) ''' self.logger.info( "Attempting to pull image {}:{}".format(name, tag) ) try: for line in self.pull(name, tag=tag, stream=True, decode=True): self.out(line) if line.get('error'): self.logger.info( "Attempt to pull {}:{} failed".format(name, tag) ) return None except Exception as exc: self.logger.warning( "Error pulling image {}:{} - {}".format(name, tag, str(exc)) ) return None return self.find_image(name=name, tag=tag)