예제 #1
0
 def _update_service(service_name, service_config):
     if url and namespace:
         # Reference previously pushed image
         image_id = self.get_latest_image_id_for_service(service_name)
         if not image_id:
             raise exceptions.AnsibleContainerConductorException(
                 u"Unable to get image ID for service {}. Did you forget to run "
                 u"`ansible-container build`?".format(service_name)
             )
         image_tag = tag or self.get_build_stamp_for_image(image_id)
         if repository_prefix:
             image_name = "{}-{}".format(repository_prefix, service_name)
         elif repository_prefix is None:
             image_name = "{}-{}".format(self.project_name, service_name)
         else:
             image_name = service_name
         repository = "{}/{}".format(namespace, image_name)
         image_name = "{}:{}".format(repository, image_tag)
         pull_url = pull_from_url if pull_from_url else url
         service_config['image'] = "{}/{}".format(pull_url.rstrip('/'), image_name)
     else:
         # We're using a local image, so check that the image was built
         image = self.get_latest_image_for_service(service_name)
         if image is None:
             raise exceptions.AnsibleContainerConductorException(
                 u"No image found for service {}, make sure you've run `ansible-container "
                 u"build`".format(service_name)
             )
         service_config['image'] = image.tags[0]
예제 #2
0
    def login(self, username, password, email, url, config_path):
        """
        If username and password are provided, authenticate with the registry.
        Otherwise, check the config file for existing authentication data.
        """
        if username and password:
            try:
                self.client.login(username=username,
                                  password=password,
                                  email=email,
                                  registry=url,
                                  reauth=True)
            except docker_errors.APIError as exc:
                raise exceptions.AnsibleContainerConductorException(
                    u"Error logging into registry: {}".format(exc))
            except Exception:
                raise

            self._update_config_file(username, password, email, url,
                                     config_path)

        username, password = self._get_registry_auth(url, config_path)
        if not username:
            raise exceptions.AnsibleContainerConductorException(
                u'Please provide login credentials for registry {}.'.format(
                    url))
        return username, password
예제 #3
0
 def get_build_stamp_for_image(self, image_id):
     build_stamp = None
     try:
         image = self.client.images.get(image_id)
     except docker_errors.ImageNotFound:
         raise exceptions.AnsibleContainerConductorException(
             "Unable to find image {}".format(image_id)
         )
     if image and image.tags:
         build_stamp = [tag for tag in image.tags if not tag.endswith(':latest')][0].split(':')[-1]
     return build_stamp
예제 #4
0
 def await_conductor_command(self, command, config, base_path, params, save_container=False):
     conductor_id = self.run_conductor(command, config, base_path, params)
     try:
         while self.service_is_running('conductor'):
             time.sleep(0.1)
     finally:
         exit_code = self.service_exit_code('conductor')
         msg = 'Preserving as requested.' if save_container else 'Cleaning up.'
         logger.info('Conductor terminated. {}'.format(msg), save_container=save_container,
                     conductor_id=conductor_id, command_rc=exit_code)
         if not save_container:
             self.delete_container(conductor_id, remove_volumes=True)
         if exit_code:
             raise exceptions.AnsibleContainerConductorException(
                 u'Conductor exited with status %s' % exit_code
             )
예제 #5
0
    def _update_config_file(username, password, email, url, config_path):
        """Update the config file with the authorization."""
        try:
            # read the existing config
            config = json.load(open(config_path, "r"))
        except ValueError:
            config = dict()

        if not config.get('auths'):
            config['auths'] = dict()

        if not config['auths'].get(url):
            config['auths'][url] = dict()
        encoded_credentials = dict(auth=base64.b64encode(username + b':' +
                                                         password),
                                   email=email)
        config['auths'][url] = encoded_credentials
        try:
            json.dump(config, open(config_path, "w"), indent=5, sort_keys=True)
        except Exception as exc:
            raise exceptions.AnsibleContainerConductorException(
                u"Failed to write registry config to {0} - {1}".format(
                    config_path, exc))
예제 #6
0
    def generate_orchestration_playbook(self, url=None, namespace=None, local_images=True, **kwargs):
        """
        Generate an Ansible playbook to orchestrate services.
        :param url: registry URL where images will be pulled from
        :param namespace: registry namespace
        :param local_images: bypass pulling images, and use local copies
        :return: playbook dict
        """
        states = ['start', 'restart', 'stop', 'destroy']

        service_def = {}
        for service_name, service in self.services.items():
            service_definition = {}
            if service.get('roles'):
                image = self.get_latest_image_for_service(service_name)
                if image is None:
                    raise exceptions.AnsibleContainerConductorException(
                        u"No image found for service {}, make sure you've run `ansible-container "
                        u"build`".format(service_name)
                    )
                service_definition[u'image'] = image.tags[0]
            else:
                try:
                    image = self.client.images.get(service['from'])
                except docker.errors.ImageNotFound:
                    image = None
                    logger.warning(u"Image {} for service {} not found. "
                                   u"An attempt will be made to pull it.".format(service['from'], service_name))
                if image:
                    service_definition[u'image'] = image.tags[0]
                else:
                    service_definition[u'image'] = service['from']
            for extra in self.COMPOSE_WHITELIST:
                if extra in service:
                    service_definition[extra] = service[extra]
            logger.debug(u'Adding new service to definition',
                         service=service_name, definition=service_definition)
            service_def[service_name] = service_definition

        tasks = []
        for desired_state in states:
            task_params = {
                u'project_name': self.project_name,
                u'definition': {
                    u'version': u'2',
                    u'services': service_def,
                }
            }
            if self.volumes:
                task_params[u'definition'][u'volumes'] = dict(self.volumes)

            if desired_state in {'restart', 'start', 'stop'}:
                task_params[u'state'] = u'present'
                if desired_state == 'restart':
                    task_params[u'restarted'] = True
                if desired_state == 'stop':
                    task_params[u'stopped'] = True
            elif desired_state == 'destroy':
                task_params[u'state'] = u'absent'
                task_params[u'remove_volumes'] = u'yes'

            tasks.append({u'docker_service': task_params, u'tags': [desired_state]})

        playbook = [{
            u'hosts': u'localhost',
            u'gather_facts': False,
            u'tasks': tasks,
        }]

        for service in list(self.services.keys()) + ['conductor']:
            image_name = self.image_name_for_service(service)
            for image in self.client.images.list(name=image_name):
                logger.debug('Found image for service', tags=image.tags, id=image.short_id)
                for tag in image.tags:
                    logger.debug('Adding task to destroy image', tag=tag)
                    playbook[0][u'tasks'].append({
                        u'docker_image': {
                            u'name': tag,
                            u'state': u'absent',
                            u'force': u'yes'
                        },
                        u'tags': u'destroy'
                    })

        logger.debug(u'Created playbook to run project', playbook=playbook)
        return playbook
예제 #7
0
    def run_conductor(self, command, config, base_path, params, engine_name=None, volumes=None):
        image_id = self.get_latest_image_id_for_service('conductor')
        if image_id is None:
            raise exceptions.AnsibleContainerConductorException(
                    u"Conductor container can't be found. Run "
                    u"`ansible-container build` first")

        serialized_params = base64.b64encode(json.dumps(params).encode("utf-8")).decode()
        serialized_config = base64.b64encode(json.dumps(ordereddict_to_list(config)).encode("utf-8")).decode()

        if not volumes:
            volumes = {}

        if params.get('with_volumes'):
            for volume in params.get('with_volumes'):
                volume_parts = volume.split(':')
                volume_parts[0] = os.path.normpath(os.path.abspath(os.path.expanduser(volume_parts[0])))
                volumes[volume_parts[0]] = {
                    'bind': volume_parts[1] if len(volume_parts) > 1 else volume_parts[0],
                    'mode': volume_parts[2] if len(volume_parts) > 2 else 'rw'
                }

        permissions = 'ro' if command != 'install' else 'rw'
        volumes[base_path] = {'bind': '/src', 'mode': permissions}

        if params.get('deployment_output_path'):
            deployment_path = params['deployment_output_path']
            volumes[deployment_path] = {'bind': deployment_path, 'mode': 'rw'}

        roles_path = None
        if params.get('roles_path'):
            # User specified --roles-path
            roles_path = os.path.normpath(os.path.abspath(os.path.expanduser(params.get('roles_path'))))
            volumes[roles_path] = {'bind': roles_path, 'mode': 'ro'}

        environ = {}
        if os.environ.get('DOCKER_HOST'):
            environ['DOCKER_HOST'] = os.environ['DOCKER_HOST']
            if os.environ.get('DOCKER_CERT_PATH'):
                environ['DOCKER_CERT_PATH'] = '/etc/docker'
                volumes[os.environ['DOCKER_CERT_PATH']] = {'bind': '/etc/docker',
                                                           'mode': 'ro'}
            if os.environ.get('DOCKER_TLS_VERIFY'):
                environ['DOCKER_TLS_VERIFY'] = os.environ['DOCKER_TLS_VERIFY']
        else:
            environ['DOCKER_HOST'] = 'unix:///var/run/docker.sock'
            volumes['/var/run/docker.sock'] = {'bind': '/var/run/docker.sock',
                                               'mode': 'rw'}
        if params.get('with_variables'):
            for var in params['with_variables']:
                key, value = var.split('=', 1)
                environ[key] = value

        if roles_path:
            environ['ANSIBLE_ROLES_PATH'] = "%s:/src/roles:/etc/ansible/roles" % roles_path
        else:
            environ['ANSIBLE_ROLES_PATH'] = '/src/roles:/etc/ansible/roles'

        if params.get('devel'):
            conductor_path = os.path.dirname(container.__file__)
            logger.debug(u"Binding Ansible Container code at %s into conductor "
                         u"container", conductor_path)
            volumes[conductor_path] = {'bind': '/_ansible/container', 'mode': 'rw'}

        if command in ('login', 'push') and params.get('config_path'):
            config_path = params.get('config_path')
            volumes[config_path] = {'bind': config_path,
                                    'mode': 'rw'}

        if not engine_name:
            engine_name = __name__.rsplit('.', 2)[-2]

        run_kwargs = dict(
            name=self.container_name_for_service('conductor'),
            command=['conductor',
                     command,
                     '--project-name', self.project_name,
                     '--engine', engine_name,
                     '--params', serialized_params,
                     '--config', serialized_config,
                     '--encoding', 'b64json'],
            detach=True,
            user='******',
            volumes=volumes,
            environment=environ,
            working_dir='/src',
            cap_add=['SYS_ADMIN']
        )

        # Anytime a playbook is executed, /src is bind mounted to a tmpdir, and that seems to
        # require privileged=True
        run_kwargs['privileged'] = True

        logger.debug('Docker run:', image=image_id, params=run_kwargs)
        try:
            container_obj = self.client.containers.run(
                image_id,
                **run_kwargs
            )
        except docker_errors.APIError as exc:
            if exc.response.status_code == StatusCodes.CONFLICT:
                raise exceptions.AnsibleContainerConductorException(
                    u"Can't start conductor container, another conductor for "
                    u"this project already exists or wasn't cleaned up.")
            six.reraise(*sys.exc_info())
        else:
            log_iter = container_obj.logs(stdout=True, stderr=True, stream=True)
            mux = logmux.LogMultiplexer()
            mux.add_iterator(log_iter, plainLogger)
            return container_obj.id
예제 #8
0
    def run_conductor(self,
                      command,
                      config,
                      base_path,
                      params,
                      engine_name=None,
                      volumes=None):
        image_id = self.get_latest_image_id_for_service('conductor')
        if image_id is None:
            raise exceptions.AnsibleContainerConductorException(
                u"Conductor container can't be found. Run "
                u"`ansible-container build` first")
        config['settings'] = dict(pwd=base_path, **config.get('settings', {}))

        serialized_params = base64.b64encode(
            json.dumps(params).encode("utf-8")).decode()
        serialized_config = base64.b64encode(
            json.dumps(config).encode("utf-8")).decode()

        if not volumes:
            volumes = {}
        permissions = 'ro' if command != 'install' else 'rw'
        volumes[base_path] = {'bind': '/src', 'mode': permissions}

        if params.get('deployment_output_path'):
            deployment_path = params['deployment_output_path']
            volumes[deployment_path] = {'bind': deployment_path, 'mode': 'rw'}

        environ = {}
        if os.environ.get('DOCKER_HOST'):
            environ['DOCKER_HOST'] = os.environ['DOCKER_HOST']
            if os.environ.get('DOCKER_CERT_PATH'):
                environ['DOCKER_CERT_PATH'] = '/etc/docker'
                volumes[os.environ['DOCKER_CERT_PATH']] = {
                    'bind': '/etc/docker',
                    'mode': 'ro'
                }
            if os.environ.get('DOCKER_TLS_VERIFY'):
                environ['DOCKER_TLS_VERIFY'] = os.environ['DOCKER_TLS_VERIFY']
        else:
            environ['DOCKER_HOST'] = 'unix:///var/run/docker.sock'
            volumes['/var/run/docker.sock'] = {
                'bind': '/var/run/docker.sock',
                'mode': 'rw'
            }

        environ['ANSIBLE_ROLES_PATH'] = '/src/roles:/etc/ansible/roles'

        if params.get('devel'):
            conductor_path = os.path.dirname(container.__file__)
            logger.debug(
                u"Binding Ansible Container code at %s into conductor "
                u"container", conductor_path)
            volumes[conductor_path] = {
                'bind': '/_ansible/container',
                'mode': 'rw'
            }

        if command in ('login', 'push') and params.get('config_path'):
            config_path = params.get('config_path')
            volumes[config_path] = {'bind': config_path, 'mode': 'rw'}

        if not engine_name:
            engine_name = __name__.rsplit('.', 2)[-2]

        run_kwargs = dict(name=self.container_name_for_service('conductor'),
                          command=[
                              'conductor', command, '--project-name',
                              self.project_name, '--engine', engine_name,
                              '--params', serialized_params, '--config',
                              serialized_config, '--encoding', 'b64json'
                          ],
                          detach=True,
                          user='******',
                          volumes=volumes,
                          environment=environ,
                          working_dir='/src',
                          cap_add=['SYS_ADMIN'])

        logger.debug('Docker run:', image=image_id, params=run_kwargs)
        try:
            container_obj = self.client.containers.run(image_id, **run_kwargs)
        except docker_errors.APIError as exc:
            if exc.response.status_code == StatusCodes.CONFLICT:
                raise exceptions.AnsibleContainerConductorException(
                    u"Can't start conductor container, another conductor for "
                    u"this project already exists or wasn't cleaned up.")
            six.reraise(*sys.exc_info())
        else:
            log_iter = container_obj.logs(stdout=True,
                                          stderr=True,
                                          stream=True)
            mux = logmux.LogMultiplexer()
            mux.add_iterator(log_iter, plainLogger)
            return container_obj.id