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.driver_options: self.parameters.driver_options = get_driver_options( 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): try: return self.client.inspect_network(self.parameters.network_name) except NotFound: return None 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_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: for idx, ipam_config in enumerate(self.parameters.ipam_config): net_config = dict() try: ip_version = get_ip_version(ipam_config['subnet']) for net_ipam_config in net['IPAM']['Config']: if ip_version == get_ip_version( net_ipam_config['Subnet']): net_config = net_ipam_config except ValueError as e: self.client.fail(str(e)) 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 camelkey = None for net_key in net_config: if key == net_key.lower(): camelkey = net_key break if not camelkey or net_config.get(camelkey) != value: differences.add('ipam_config[%s].%s' % (idx, key), parameter=value, active=net_config.get(camelkey) if camelkey else None) 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: if self.parameters.internal: if not net.get('Internal'): differences.add('internal', parameter=self.parameters.internal, active=net.get('Internal')) else: if net.get('Internal'): differences.add('internal', parameter=self.parameters.internal, active=net.get('Internal')) 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 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) 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 not self.check_mode: resp = self.client.create_network(self.parameters.network_name, **params) self.existing_network = self.client.inspect_network(resp['Id']) self.results['actions'].append( "Created network %s with driver %s" % (self.parameters.network_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.network_name) self.results['actions'].append("Removed network %s" % (self.parameters.network_name, )) self.results['changed'] = True def is_container_connected(self, container_name): 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.network_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.inspect_network( self.parameters.network_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.network_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') self.results['ansible_facts'] = { u'docker_network': self.get_existing_network() } def absent(self): self.diff_tracker.add('exists', parameter=False, active=self.existing_network is not None) self.remove_network()
class DockerServiceManager(): def get_networks_names_ids(self): return [{ 'name': n['Name'], 'id': n['Id'] } for n in self.client.networks()] def get_service(self, name): raw_data = self.client.services(filters={'name': name}) if len(raw_data) == 0: return None raw_data = raw_data[0] networks_names_ids = self.get_networks_names_ids() ds = DockerService() task_template_data = raw_data['Spec']['TaskTemplate'] update_config_data = raw_data['Spec']['UpdateConfig'] ds.image = task_template_data['ContainerSpec']['Image'] ds.user = task_template_data['ContainerSpec'].get('User', 'root') ds.env = task_template_data['ContainerSpec'].get('Env', []) ds.args = task_template_data['ContainerSpec'].get('Args', []) ds.update_delay = update_config_data['Delay'] ds.update_parallelism = update_config_data['Parallelism'] ds.update_failure_action = update_config_data['FailureAction'] ds.update_monitor = update_config_data['Monitor'] ds.update_max_failure_ratio = update_config_data['MaxFailureRatio'] if 'Order' in update_config_data: ds.update_order = update_config_data['Order'] dns_config = task_template_data['ContainerSpec'].get('DNSConfig', None) if dns_config: if 'Nameservers' in dns_config.keys(): ds.dns = dns_config['Nameservers'] if 'Search' in dns_config.keys(): ds.dns_search = dns_config['Search'] if 'Options' in dns_config.keys(): ds.dns_options = dns_config['Options'] ds.hostname = task_template_data['ContainerSpec'].get('Hostname', '') ds.tty = task_template_data['ContainerSpec'].get('TTY', False) if 'Placement' in task_template_data.keys(): ds.constraints = task_template_data['Placement'].get( 'Constraints', []) restart_policy_data = task_template_data.get('RestartPolicy', None) if restart_policy_data: ds.restart_policy = restart_policy_data.get('Condition') ds.restart_policy_delay = restart_policy_data.get('Delay') ds.restart_policy_attempts = restart_policy_data.get('MaxAttempts') ds.restart_policy_window = restart_policy_data.get('Window') raw_data_endpoint = raw_data.get('Endpoint', None) if raw_data_endpoint: raw_data_endpoint_spec = raw_data_endpoint.get('Spec', None) if raw_data_endpoint_spec: ds.endpoint_mode = raw_data_endpoint_spec.get('Mode', 'vip') for port in raw_data_endpoint_spec.get('Ports', []): ds.publish.append({ 'protocol': port['Protocol'], 'mode': port.get('PublishMode', None), 'published_port': int(port['PublishedPort']), 'target_port': int(port['TargetPort']) }) if 'Resources' in task_template_data.keys(): if 'Limits' in task_template_data['Resources'].keys(): if 'NanoCPUs' in task_template_data['Resources'][ 'Limits'].keys(): ds.limit_cpu = float(task_template_data['Resources'] ['Limits']['NanoCPUs']) / 1000000000 if 'MemoryBytes' in task_template_data['Resources'][ 'Limits'].keys(): ds.limit_memory = int(task_template_data['Resources'] ['Limits']['MemoryBytes']) if 'Reservations' in task_template_data['Resources'].keys(): if 'NanoCPUs' in task_template_data['Resources'][ 'Reservations'].keys(): ds.reserve_cpu = float( task_template_data['Resources']['Reservations'] ['NanoCPUs']) / 1000000000 if 'MemoryBytes' in task_template_data['Resources'][ 'Reservations'].keys(): ds.reserve_memory = int(task_template_data['Resources'] ['Reservations']['MemoryBytes']) ds.labels = raw_data['Spec'].get('Labels', {}) if 'LogDriver' in task_template_data.keys(): ds.log_driver = task_template_data['LogDriver'].get( 'Name', 'json-file') ds.log_driver_options = task_template_data['LogDriver'].get( 'Options', {}) ds.container_labels = task_template_data['ContainerSpec'].get( 'Labels', {}) mode = raw_data['Spec']['Mode'] if 'Replicated' in mode.keys(): ds.mode = to_text('replicated', encoding='utf-8') ds.replicas = mode['Replicated']['Replicas'] elif 'Global' in mode.keys(): ds.mode = 'global' else: raise Exception("Unknown service mode: %s" % mode) for mount_data in raw_data['Spec']['TaskTemplate'][ 'ContainerSpec'].get('Mounts', []): ds.mounts.append({ 'source': mount_data['Source'], 'type': mount_data['Type'], 'target': mount_data['Target'], 'readonly': mount_data.get('ReadOnly', False) }) for config_data in raw_data['Spec']['TaskTemplate'][ 'ContainerSpec'].get('Configs', []): ds.configs.append({ 'config_id': config_data['ConfigID'], 'config_name': config_data['ConfigName'], 'filename': config_data['File'].get('Name'), 'uid': int(config_data['File'].get('UID')), 'gid': int(config_data['File'].get('GID')), 'mode': config_data['File'].get('Mode') }) for secret_data in raw_data['Spec']['TaskTemplate'][ 'ContainerSpec'].get('Secrets', []): ds.secrets.append({ 'secret_id': secret_data['SecretID'], 'secret_name': secret_data['SecretName'], 'filename': secret_data['File'].get('Name'), 'uid': int(secret_data['File'].get('UID')), 'gid': int(secret_data['File'].get('GID')), 'mode': secret_data['File'].get('Mode') }) networks_names_ids = self.get_networks_names_ids() for raw_network_data in raw_data['Spec']['TaskTemplate'].get( 'Networks', raw_data['Spec'].get('Networks', [])): network_name = [ network_name_id['name'] for network_name_id in networks_names_ids if network_name_id['id'] == raw_network_data['Target'] ] if len(network_name) == 0: ds.networks.append(raw_network_data['Target']) else: ds.networks.append(network_name[0]) ds.service_version = raw_data['Version']['Index'] ds.service_id = raw_data['ID'] return ds def update_service(self, name, old_service, new_service): update_policy, task_template, networks, endpoint_spec, mode, labels = new_service.generate_docker_py_service_description( name, self.get_networks_names_ids()) self.client.update_service(old_service.service_id, old_service.service_version, name=name, endpoint_spec=endpoint_spec, networks=networks, mode=mode, update_config=update_policy, task_template=task_template, labels=labels) def create_service(self, name, service): update_policy, task_template, networks, endpoint_spec, mode, labels = service.generate_docker_py_service_description( name, self.get_networks_names_ids()) self.client.create_service(name=name, endpoint_spec=endpoint_spec, mode=mode, networks=networks, update_config=update_policy, task_template=task_template, labels=labels) def remove_service(self, name): self.client.remove_service(name) def __init__(self, client): self.client = client self.diff_tracker = DifferenceTracker() def test_parameter_versions(self): parameters_versions = [{ 'param': 'dns', 'attribute': 'dns', 'min_version': '1.25' }, { 'param': 'dns_options', 'attribute': 'dns_options', 'min_version': '1.25' }, { 'param': 'dns_search', 'attribute': 'dns_search', 'min_version': '1.25' }, { 'param': 'hostname', 'attribute': 'hostname', 'min_version': '1.25' }, { 'param': 'tty', 'attribute': 'tty', 'min_version': '1.25' }, { 'param': 'secrets', 'attribute': 'secrets', 'min_version': '1.25' }, { 'param': 'configs', 'attribute': 'configs', 'min_version': '1.30' }, { 'param': 'update_order', 'attribute': 'update_order', 'min_version': '1.29' }] params = self.client.module.params empty_service = DockerService() for pv in parameters_versions: if (params[pv['param']] != getattr(empty_service, pv['attribute']) and (LooseVersion(self.client.version()['ApiVersion']) < LooseVersion(pv['min_version']))): self.client.module.fail_json( msg=('%s parameter supported only with api_version>=%s' % (pv['param'], pv['min_version']))) for publish_def in self.client.module.params.get('publish', []): if 'mode' in publish_def.keys(): if LooseVersion(self.client.version() ['ApiVersion']) < LooseVersion('1.25'): self.client.module.fail_json( msg= 'publish.mode parameter supported only with api_version>=1.25' ) if LooseVersion(docker_version) < LooseVersion('3.0.0'): self.client.module.fail_json( msg= 'publish.mode parameter requires docker python library>=3.0.0' ) def run(self): self.test_parameter_versions() module = self.client.module try: current_service = self.get_service(module.params['name']) except Exception as e: return module.fail_json( msg="Error looking for service named %s: %s" % (module.params['name'], e)) try: new_service = DockerService.from_ansible_params( module.params, current_service) except Exception as e: return module.fail_json(msg="Error parsing module parameters: %s" % e) changed = False msg = 'noop' rebuilt = False differences = DifferenceTracker() facts = {} if current_service: if module.params['state'] == 'absent': if not module.check_mode: self.remove_service(module.params['name']) msg = 'Service removed' changed = True else: changed, differences, need_rebuild, force_update = new_service.compare( current_service) if changed: self.diff_tracker.merge(differences) if need_rebuild: if not module.check_mode: self.remove_service(module.params['name']) self.create_service(module.params['name'], new_service) msg = 'Service rebuilt' rebuilt = True else: if not module.check_mode: self.update_service(module.params['name'], current_service, new_service) msg = 'Service updated' rebuilt = False else: if force_update: if not module.check_mode: self.update_service(module.params['name'], current_service, new_service) msg = 'Service forcefully updated' rebuilt = False changed = True else: msg = 'Service unchanged' facts = new_service.get_facts() else: if module.params['state'] == 'absent': msg = 'Service absent' else: if not module.check_mode: service_id = self.create_service(module.params['name'], new_service) msg = 'Service created' changed = True facts = new_service.get_facts() return msg, changed, rebuilt, differences.get_legacy_docker_diffs( ), facts
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 or self.parameters.force: 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') self.results['ansible_facts'] = { u'docker_volume': self.get_existing_volume() } def absent(self): self.diff_tracker.add('exists', parameter=False, active=self.existing_volume is not None) self.remove_volume()