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()
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