class DockerVolumeManager(object):
    def __init__(self, client):
        self.client = client
        self.parameters = TaskParameters(client)
        self.check_mode = self.client.check_mode
        self.results = {u'changed': False, u'actions': []}
        self.diff = self.client.module._diff
        self.diff_tracker = DifferenceTracker()
        self.diff_result = dict()

        self.existing_volume = self.get_existing_volume()

        state = self.parameters.state
        if state == 'present':
            self.present()
        elif state == 'absent':
            self.absent()

        if self.diff or self.check_mode or self.parameters.debug:
            if self.diff:
                self.diff_result['before'], self.diff_result[
                    'after'] = self.diff_tracker.get_before_after()
            self.results['diff'] = self.diff_result

    def get_existing_volume(self):
        try:
            volumes = self.client.volumes()
        except APIError as e:
            self.client.fail(text_type(e))

        if volumes[u'Volumes'] is None:
            return None

        for volume in volumes[u'Volumes']:
            if volume['Name'] == self.parameters.volume_name:
                return volume

        return None

    def has_different_config(self):
        """
        Return the list of differences between the current parameters and the existing volume.

        :return: list of options that differ
        """
        differences = DifferenceTracker()
        if self.parameters.driver and self.parameters.driver != self.existing_volume[
                'Driver']:
            differences.add('driver',
                            parameter=self.parameters.driver,
                            active=self.existing_volume['Driver'])
        if self.parameters.driver_options:
            if not self.existing_volume.get('Options'):
                differences.add('driver_options',
                                parameter=self.parameters.driver_options,
                                active=self.existing_volume.get('Options'))
            else:
                for key, value in iteritems(self.parameters.driver_options):
                    if (not self.existing_volume['Options'].get(key)
                            or value != self.existing_volume['Options'][key]):
                        differences.add(
                            'driver_options.%s' % key,
                            parameter=value,
                            active=self.existing_volume['Options'].get(key))
        if self.parameters.labels:
            existing_labels = self.existing_volume.get('Labels', {})
            for label in self.parameters.labels:
                if existing_labels.get(label) != self.parameters.labels.get(
                        label):
                    differences.add(
                        'labels.%s' % label,
                        parameter=self.parameters.labels.get(label),
                        active=existing_labels.get(label))

        return differences

    def create_volume(self):
        if not self.existing_volume:
            if not self.check_mode:
                try:
                    params = dict(
                        driver=self.parameters.driver,
                        driver_opts=self.parameters.driver_options,
                    )

                    if self.parameters.labels is not None:
                        params['labels'] = self.parameters.labels

                    resp = self.client.create_volume(
                        self.parameters.volume_name, **params)
                    self.existing_volume = self.client.inspect_volume(
                        resp['Name'])
                except APIError as e:
                    self.client.fail(text_type(e))

            self.results['actions'].append(
                "Created volume %s with driver %s" %
                (self.parameters.volume_name, self.parameters.driver))
            self.results['changed'] = True

    def remove_volume(self):
        if self.existing_volume:
            if not self.check_mode:
                try:
                    self.client.remove_volume(self.parameters.volume_name)
                except APIError as e:
                    self.client.fail(text_type(e))

            self.results['actions'].append("Removed volume %s" %
                                           self.parameters.volume_name)
            self.results['changed'] = True

    def present(self):
        differences = DifferenceTracker()
        if self.existing_volume:
            differences = self.has_different_config()

        self.diff_tracker.add('exists',
                              parameter=True,
                              active=self.existing_volume is not None)
        if (not differences.empty and self.parameters.recreate
                == 'options-changed') or self.parameters.recreate == 'always':
            self.remove_volume()
            self.existing_volume = None

        self.create_volume()

        if self.diff or self.check_mode or self.parameters.debug:
            self.diff_result[
                'differences'] = differences.get_legacy_docker_diffs()
            self.diff_tracker.merge(differences)

        if not self.check_mode and not self.parameters.debug:
            self.results.pop('actions')

        volume_facts = self.get_existing_volume()
        self.results['ansible_facts'] = {u'docker_volume': volume_facts}
        self.results['volume'] = volume_facts

    def absent(self):
        self.diff_tracker.add('exists',
                              parameter=False,
                              active=self.existing_volume is not None)
        self.remove_volume()
class DockerNetworkManager(object):
    def __init__(self, client):
        self.client = client
        self.parameters = TaskParameters(client)
        self.check_mode = self.client.check_mode
        self.results = {u'changed': False, u'actions': []}
        self.diff = self.client.module._diff
        self.diff_tracker = DifferenceTracker()
        self.diff_result = dict()

        self.existing_network = self.get_existing_network()

        if not self.parameters.connected and self.existing_network:
            self.parameters.connected = container_names_in_network(
                self.existing_network)

        if (self.parameters.ipam_options['subnet']
                or self.parameters.ipam_options['iprange']
                or self.parameters.ipam_options['gateway']
                or self.parameters.ipam_options['aux_addresses']):
            self.parameters.ipam_config = [self.parameters.ipam_options]

        if self.parameters.ipam_config:
            try:
                for ipam_config in self.parameters.ipam_config:
                    validate_cidr(ipam_config['subnet'])
            except ValueError as e:
                self.client.fail(str(e))

        if self.parameters.driver_options:
            self.parameters.driver_options = clean_dict_booleans_for_docker_api(
                self.parameters.driver_options)

        state = self.parameters.state
        if state == 'present':
            self.present()
        elif state == 'absent':
            self.absent()

        if self.diff or self.check_mode or self.parameters.debug:
            if self.diff:
                self.diff_result['before'], self.diff_result[
                    'after'] = self.diff_tracker.get_before_after()
            self.results['diff'] = self.diff_result

    def get_existing_network(self):
        return self.client.get_network(name=self.parameters.name)

    def has_different_config(self, net):
        '''
        Evaluates an existing network and returns a tuple containing a boolean
        indicating if the configuration is different and a list of differences.

        :param net: the inspection output for an existing network
        :return: (bool, list)
        '''
        differences = DifferenceTracker()
        if self.parameters.driver and self.parameters.driver != net['Driver']:
            differences.add('driver',
                            parameter=self.parameters.driver,
                            active=net['Driver'])
        if self.parameters.driver_options:
            if not net.get('Options'):
                differences.add('driver_options',
                                parameter=self.parameters.driver_options,
                                active=net.get('Options'))
            else:
                for key, value in self.parameters.driver_options.items():
                    if not (key
                            in net['Options']) or value != net['Options'][key]:
                        differences.add('driver_options.%s' % key,
                                        parameter=value,
                                        active=net['Options'].get(key))

        if self.parameters.ipam_driver:
            if not net.get('IPAM') or net['IPAM'][
                    'Driver'] != self.parameters.ipam_driver:
                differences.add('ipam_driver',
                                parameter=self.parameters.ipam_driver,
                                active=net.get('IPAM'))

        if self.parameters.ipam_driver_options is not None:
            ipam_driver_options = net['IPAM'].get('Options') or {}
            if ipam_driver_options != self.parameters.ipam_driver_options:
                differences.add('ipam_driver_options',
                                parameter=self.parameters.ipam_driver_options,
                                active=ipam_driver_options)

        if self.parameters.ipam_config is not None and self.parameters.ipam_config:
            if not net.get('IPAM') or not net['IPAM']['Config']:
                differences.add('ipam_config',
                                parameter=self.parameters.ipam_config,
                                active=net.get('IPAM', {}).get('Config'))
            else:
                # Put network's IPAM config into the same format as module's IPAM config
                net_ipam_configs = []
                for net_ipam_config in net['IPAM']['Config']:
                    config = dict()
                    for k, v in net_ipam_config.items():
                        config[normalize_ipam_config_key(k)] = v
                    net_ipam_configs.append(config)
                # Compare lists of dicts as sets of dicts
                for idx, ipam_config in enumerate(self.parameters.ipam_config):
                    net_config = dict()
                    for net_ipam_config in net_ipam_configs:
                        if dicts_are_essentially_equal(ipam_config,
                                                       net_ipam_config):
                            net_config = net_ipam_config
                            break
                    for key, value in ipam_config.items():
                        if value is None:
                            # due to recursive argument_spec, all keys are always present
                            # (but have default value None if not specified)
                            continue
                        if value != net_config.get(key):
                            differences.add('ipam_config[%s].%s' % (idx, key),
                                            parameter=value,
                                            active=net_config.get(key))

        if self.parameters.enable_ipv6 is not None and self.parameters.enable_ipv6 != net.get(
                'EnableIPv6', False):
            differences.add('enable_ipv6',
                            parameter=self.parameters.enable_ipv6,
                            active=net.get('EnableIPv6', False))

        if self.parameters.internal is not None and self.parameters.internal != net.get(
                'Internal', False):
            differences.add('internal',
                            parameter=self.parameters.internal,
                            active=net.get('Internal'))

        if self.parameters.scope is not None and self.parameters.scope != net.get(
                'Scope'):
            differences.add('scope',
                            parameter=self.parameters.scope,
                            active=net.get('Scope'))

        if self.parameters.attachable is not None and self.parameters.attachable != net.get(
                'Attachable', False):
            differences.add('attachable',
                            parameter=self.parameters.attachable,
                            active=net.get('Attachable'))
        if self.parameters.labels:
            if not net.get('Labels'):
                differences.add('labels',
                                parameter=self.parameters.labels,
                                active=net.get('Labels'))
            else:
                for key, value in self.parameters.labels.items():
                    if not (key
                            in net['Labels']) or value != net['Labels'][key]:
                        differences.add('labels.%s' % key,
                                        parameter=value,
                                        active=net['Labels'].get(key))

        return not differences.empty, differences

    def create_network(self):
        if not self.existing_network:
            params = dict(
                driver=self.parameters.driver,
                options=self.parameters.driver_options,
            )

            ipam_pools = []
            if self.parameters.ipam_config:
                for ipam_pool in self.parameters.ipam_config:
                    if LooseVersion(docker_version) >= LooseVersion('2.0.0'):
                        ipam_pools.append(IPAMPool(**ipam_pool))
                    else:
                        ipam_pools.append(utils.create_ipam_pool(**ipam_pool))

            if self.parameters.ipam_driver or self.parameters.ipam_driver_options or ipam_pools:
                # Only add ipam parameter if a driver was specified or if IPAM parameters
                # were specified. Leaving this parameter away can significantly speed up
                # creation; on my machine creation with this option needs ~15 seconds,
                # and without just a few seconds.
                if LooseVersion(docker_version) >= LooseVersion('2.0.0'):
                    params['ipam'] = IPAMConfig(
                        driver=self.parameters.ipam_driver,
                        pool_configs=ipam_pools,
                        options=self.parameters.ipam_driver_options)
                else:
                    params['ipam'] = utils.create_ipam_config(
                        driver=self.parameters.ipam_driver,
                        pool_configs=ipam_pools)

            if self.parameters.enable_ipv6 is not None:
                params['enable_ipv6'] = self.parameters.enable_ipv6
            if self.parameters.internal is not None:
                params['internal'] = self.parameters.internal
            if self.parameters.scope is not None:
                params['scope'] = self.parameters.scope
            if self.parameters.attachable is not None:
                params['attachable'] = self.parameters.attachable
            if self.parameters.labels:
                params['labels'] = self.parameters.labels

            if not self.check_mode:
                resp = self.client.create_network(self.parameters.name,
                                                  **params)
                self.client.report_warnings(resp, ['Warning'])
                self.existing_network = self.client.get_network(
                    network_id=resp['Id'])
            self.results['actions'].append(
                "Created network %s with driver %s" %
                (self.parameters.name, self.parameters.driver))
            self.results['changed'] = True

    def remove_network(self):
        if self.existing_network:
            self.disconnect_all_containers()
            if not self.check_mode:
                self.client.remove_network(self.parameters.name)
            self.results['actions'].append("Removed network %s" %
                                           (self.parameters.name, ))
            self.results['changed'] = True

    def is_container_connected(self, container_name):
        if not self.existing_network:
            return False
        return container_name in container_names_in_network(
            self.existing_network)

    def connect_containers(self):
        for name in self.parameters.connected:
            if not self.is_container_connected(name):
                if not self.check_mode:
                    self.client.connect_container_to_network(
                        name, self.parameters.name)
                self.results['actions'].append("Connected container %s" %
                                               (name, ))
                self.results['changed'] = True
                self.diff_tracker.add('connected.{0}'.format(name),
                                      parameter=True,
                                      active=False)

    def disconnect_missing(self):
        if not self.existing_network:
            return
        containers = self.existing_network['Containers']
        if not containers:
            return
        for c in containers.values():
            name = c['Name']
            if name not in self.parameters.connected:
                self.disconnect_container(name)

    def disconnect_all_containers(self):
        containers = self.client.get_network(
            name=self.parameters.name)['Containers']
        if not containers:
            return
        for cont in containers.values():
            self.disconnect_container(cont['Name'])

    def disconnect_container(self, container_name):
        if not self.check_mode:
            self.client.disconnect_container_from_network(
                container_name, self.parameters.name)
        self.results['actions'].append("Disconnected container %s" %
                                       (container_name, ))
        self.results['changed'] = True
        self.diff_tracker.add('connected.{0}'.format(container_name),
                              parameter=False,
                              active=True)

    def present(self):
        different = False
        differences = DifferenceTracker()
        if self.existing_network:
            different, differences = self.has_different_config(
                self.existing_network)

        self.diff_tracker.add('exists',
                              parameter=True,
                              active=self.existing_network is not None)
        if self.parameters.force or different:
            self.remove_network()
            self.existing_network = None

        self.create_network()
        self.connect_containers()
        if not self.parameters.appends:
            self.disconnect_missing()

        if self.diff or self.check_mode or self.parameters.debug:
            self.diff_result[
                'differences'] = differences.get_legacy_docker_diffs()
            self.diff_tracker.merge(differences)

        if not self.check_mode and not self.parameters.debug:
            self.results.pop('actions')

        network_facts = self.get_existing_network()
        self.results['ansible_facts'] = {u'docker_network': network_facts}
        self.results['network'] = network_facts

    def absent(self):
        self.diff_tracker.add('exists',
                              parameter=False,
                              active=self.existing_network is not None)
        self.remove_network()
Exemplo n.º 3
0
class SwarmManager(DockerBaseClass):
    def __init__(self, client, results):

        super(SwarmManager, self).__init__()

        self.client = client
        self.results = results
        self.check_mode = self.client.check_mode
        self.swarm_info = {}

        self.state = client.module.params['state']
        self.force = client.module.params['force']
        self.node_id = client.module.params['node_id']

        self.differences = DifferenceTracker()
        self.parameters = TaskParameters.from_ansible_params(client)

        self.created = False

    def __call__(self):
        choice_map = {
            "present": self.init_swarm,
            "join": self.join,
            "absent": self.leave,
            "remove": self.remove,
            "inspect": self.inspect_swarm
        }

        if self.state == 'inspect':
            self.client.module.deprecate(
                "The 'inspect' state is deprecated, please use 'docker_swarm_info' to inspect swarm cluster",
                version='2.12')

        choice_map.get(self.state)()

        if self.client.module._diff or self.parameters.debug:
            diff = dict()
            diff['before'], diff['after'] = self.differences.get_before_after()
            self.results['diff'] = diff

    def inspect_swarm(self):
        try:
            data = self.client.inspect_swarm()
            json_str = json.dumps(data, ensure_ascii=False)
            self.swarm_info = json.loads(json_str)

            self.results['changed'] = False
            self.results['swarm_facts'] = self.swarm_info

            unlock_key = self.get_unlock_key()
            self.swarm_info.update(unlock_key)
        except APIError:
            return

    def get_unlock_key(self):
        default = {'UnlockKey': None}
        if not self.has_swarm_lock_changed():
            return default
        try:
            return self.client.get_unlock_key() or default
        except APIError:
            return default

    def has_swarm_lock_changed(self):
        return self.parameters.autolock_managers and (
            self.created
            or self.differences.has_difference_for('autolock_managers'))

    def init_swarm(self):
        if not self.force and self.client.check_if_swarm_manager():
            self.__update_swarm()
            return

        if not self.check_mode:
            init_arguments = {
                'advertise_addr': self.parameters.advertise_addr,
                'listen_addr': self.parameters.listen_addr,
                'force_new_cluster': self.force,
                'swarm_spec': self.parameters.spec,
            }
            if self.parameters.default_addr_pool is not None:
                init_arguments[
                    'default_addr_pool'] = self.parameters.default_addr_pool
            if self.parameters.subnet_size is not None:
                init_arguments['subnet_size'] = self.parameters.subnet_size
            try:
                self.client.init_swarm(**init_arguments)
            except APIError as exc:
                self.client.fail("Can not create a new Swarm Cluster: %s" %
                                 to_native(exc))

        if not self.client.check_if_swarm_manager():
            if not self.check_mode:
                self.client.fail("Swarm not created or other error!")

        self.created = True
        self.inspect_swarm()
        self.results['actions'].append("New Swarm cluster created: %s" %
                                       (self.swarm_info.get('ID')))
        self.differences.add('state', parameter='present', active='absent')
        self.results['changed'] = True
        self.results['swarm_facts'] = {
            'JoinTokens': self.swarm_info.get('JoinTokens'),
            'UnlockKey': self.swarm_info.get('UnlockKey')
        }

    def __update_swarm(self):
        try:
            self.inspect_swarm()
            version = self.swarm_info['Version']['Index']
            self.parameters.update_from_swarm_info(self.swarm_info)
            old_parameters = TaskParameters()
            old_parameters.update_from_swarm_info(self.swarm_info)
            self.parameters.compare_to_active(old_parameters, self.client,
                                              self.differences)
            if self.differences.empty:
                self.results['actions'].append("No modification")
                self.results['changed'] = False
                return
            update_parameters = TaskParameters.from_ansible_params(self.client)
            update_parameters.update_parameters(self.client)
            if not self.check_mode:
                self.client.update_swarm(
                    version=version,
                    swarm_spec=update_parameters.spec,
                    rotate_worker_token=self.parameters.rotate_worker_token,
                    rotate_manager_token=self.parameters.rotate_manager_token)
        except APIError as exc:
            self.client.fail("Can not update a Swarm Cluster: %s" %
                             to_native(exc))
            return

        self.inspect_swarm()
        self.results['actions'].append("Swarm cluster updated")
        self.results['changed'] = True

    def join(self):
        if self.client.check_if_swarm_node():
            self.results['actions'].append(
                "This node is already part of a swarm.")
            return
        if not self.check_mode:
            try:
                self.client.join_swarm(
                    remote_addrs=self.parameters.remote_addrs,
                    join_token=self.parameters.join_token,
                    listen_addr=self.parameters.listen_addr,
                    advertise_addr=self.parameters.advertise_addr)
            except APIError as exc:
                self.client.fail("Can not join the Swarm Cluster: %s" %
                                 to_native(exc))
        self.results['actions'].append("New node is added to swarm cluster")
        self.differences.add('joined', parameter=True, active=False)
        self.results['changed'] = True

    def leave(self):
        if not self.client.check_if_swarm_node():
            self.results['actions'].append("This node is not part of a swarm.")
            return
        if not self.check_mode:
            try:
                self.client.leave_swarm(force=self.force)
            except APIError as exc:
                self.client.fail(
                    "This node can not leave the Swarm Cluster: %s" %
                    to_native(exc))
        self.results['actions'].append("Node has left the swarm cluster")
        self.differences.add('joined', parameter='absent', active='present')
        self.results['changed'] = True

    def remove(self):
        if not self.client.check_if_swarm_manager():
            self.client.fail("This node is not a manager.")

        try:
            status_down = self.client.check_if_swarm_node_is_down(
                node_id=self.node_id, repeat_check=5)
        except APIError:
            return

        if not status_down:
            self.client.fail(
                "Can not remove the node. The status node is ready and not down."
            )

        if not self.check_mode:
            try:
                self.client.remove_node(node_id=self.node_id, force=self.force)
            except APIError as exc:
                self.client.fail(
                    "Can not remove the node from the Swarm Cluster: %s" %
                    to_native(exc))
        self.results['actions'].append("Node is removed from swarm cluster.")
        self.differences.add('joined', parameter=False, active=True)
        self.results['changed'] = True