Merge pull request #3796 from chouseknecht/fix_3766

Fix 3766
reviewable/pr18780/r1
Chris Houseknecht 10 years ago
commit bf6bf9e68f

@ -253,6 +253,17 @@ options:
- none
default: null
required: false
networks:
description:
- List of networks the container belongs to.
- Each network is a dict with keys C(name), C(ipv4_address), C(ipv6_address), C(links), C(aliases).
- For each network C(name) is required, all other keys are optional.
- If included, C(links) or C(aliases) are lists.
- For examples of the data structure and usage see EXAMPLES below.
- To remove a container from one or more networks, use the C(purge_networks) option.
default: null
required: false
version_added: "2.2"
oom_killer:
description:
- Whether or not to disable OOM Killer for the container.
@ -290,6 +301,13 @@ options:
- If true, always pull the latest version of an image. Otherwise, will only pull an image when missing.
default: false
required: false
purge_networks:
description:
- Remove the container from ALL networks not included in C(networks) parameter.
- Any default networks such as I(bridge), if not found in C(networks), will be removed as well.
default: false
required: false
version_added: "2.2"
read_only:
description:
- Mount the container's root file system as read-only.
@ -340,16 +358,16 @@ options:
required: false
state:
description:
- '"absent" - A container matching the specified name will be stopped and removed. Use force_kill to kill the container
- 'I(absent) - A container matching the specified name will be stopped and removed. Use force_kill to kill the container
rather than stopping it. Use keep_volumes to retain volumes associated with the removed container.'
- '"present" - Asserts the existence of a container matching the name and any provided configuration parameters. If no
- 'I(present)" - Asserts the existence of a container matching the name and any provided configuration parameters. If no
container matches the name, a container will be created. If a container matches the name but the provided configuration
does not match, the container will be updated, if it can be. If it cannot be updated, it will be removed and re-created
with the requested config. Image version will be taken into account when comparing configuration. To ignore image
version use the ignore_image option. Use the recreate option to force the re-creation of the matching container. Use
force_kill to kill the container rather than stopping it. Use keep_volumes to retain volumes associated with a removed
container.'
- '"started" - Asserts there is a running container matching the name and any provided configuration. If no container
- 'I(started) - Asserts there is a running container matching the name and any provided configuration. If no container
matches the name, a container will be created and started. If a container matching the name is found but the
configuration does not match, the container will be updated, if it can be. If it cannot be updated, it will be removed
and a new container will be created with the requested configuration and started. Image version will be taken into
@ -357,7 +375,7 @@ options:
re-create a matching container, even if it is running. Use restart to force a matching container to be stopped and
restarted. Use force_kill to kill a container rather than stopping it. Use keep_volumes to retain volumes associated
with a removed container.'
- '"stopped" - a container matching the specified name will be stopped. Use force_kill to kill a container rather than
- 'I(stopped) - a container matching the specified name will be stopped. Use force_kill to kill a container rather than
stopping it.'
required: false
default: started
@ -508,11 +526,70 @@ EXAMPLES = '''
syslog-facility: daemon
syslog-tag: myservice
- name: Create db container and connect to network
docker_container:
name: db_test
image: "postgres:latest"
networks:
- name: "{{ docker_network_name }}"
- name: Start container, connect to network and link
docker_container:
name: sleeper
image: ubuntu:14.04
networks:
- name: TestingNet
ipv4_address: "172.1.1.100"
aliases:
- sleepyzz
links:
- db_test:db
- name: TestingNet2
- name: Start a container with a command
docker_container:
name: sleepy
image: ubuntu:14.04
command: sleep infinity
- name: Add container to networks
docker_container:
docker_container:
name: sleepy
networks:
- name: TestingNet
ipv4_address: 172.1.1.18
links:
- sleeper
- name: TestingNet2
ipv4_address: 172.1.10.20
- name: Update network with aliases
docker_container:
name: sleepy
networks:
- name: TestingNet
aliases:
- sleepyz
- zzzz
- name: Remove container from one network
docker_container:
name: sleepy
networks:
- name: TestingNet2
purge_networks: yes
- name: Remove container from all networks
docker_container:
name: sleepy
purge_networks: yes
'''
RETURN = '''
ansible_docker_container:
description: Facts representing the current state of the container. Note that facts are not part of registred vars but accessible directly.
description: Facts representing the current state of the container. Note that facts are not part of registered vars but accessible directly.
returned: always
type: dict
sample: '{
@ -619,6 +696,7 @@ class TaskParameters(DockerBaseClass):
self.paused = None
self.pid_mode = None
self.privileged = None
self.purge_networks = None
self.pull = None
self.read_only = None
self.recreate = None
@ -656,7 +734,7 @@ class TaskParameters(DockerBaseClass):
self.publish_all_ports = True
self.published_ports = None
self.links = self._parse_links()
self.links = self._parse_links(self.links)
if self.volumes:
self.volumes = self._expand_host_paths()
@ -670,6 +748,16 @@ class TaskParameters(DockerBaseClass):
self.exp_links = None
self._parse_volumes()
if self.networks:
for network in self.networks:
if not network.get('name'):
self.fail("Parameter error: network must have a name attribute.")
network['id'] = self._get_network_id(network['name'])
if not network['id']:
self.fail("Parameter error: network named %s could not be found. Does it exist?" % network['name'])
if network.get('links'):
network['links'] = self._parse_links(network['links'])
def fail(self, msg):
self.client.module.fail_json(msg=msg)
@ -877,21 +965,22 @@ class TaskParameters(DockerBaseClass):
exposed.append(port_with_proto)
return exposed
def _parse_links(self):
@staticmethod
def _parse_links(links):
'''
Turn links into a dictionary
'''
if self.links is None:
if links is None:
return None
links = {}
for link in self.links:
result = {}
for link in links:
parsed_link = link.split(':', 1)
if len(parsed_link) == 2:
links[parsed_link[0]] = parsed_link[1]
result[parsed_link[0]] = parsed_link[1]
else:
links[parsed_link[0]] = parsed_link[0]
return links
result[parsed_link[0]] = parsed_link[0]
return result
def _parse_ulimits(self):
'''
@ -950,6 +1039,18 @@ class TaskParameters(DockerBaseClass):
final_env[name] = str(value)
return final_env
def _get_network_id(self, network_name):
network_id = None
try:
for network in self.client.networks(names=[network_name]):
if network['Name'] == network_name:
network_id = network['Id']
break
except Exception, exc:
self.fail("Error getting network id for %s - %s" % (network_name, str(exc)))
return network_id
class Container(DockerBaseClass):
@ -1170,26 +1271,59 @@ class Container(DockerBaseClass):
different = (len(differences) > 0)
return different, differences
def has_missing_networks(self):
def has_network_differences(self):
'''
Check if the container is connected to requested networks
Check if the container is connected to requested networks with expected options: links, aliases, ipv4, ipv6
'''
missing_networks = []
missing = False
different = False
differences = []
if not self.parameters.networks:
return missing, missing_networks
return different, differences
if not self.container.get('NetworkSettings'):
self.fail("has_missing_networks: Error parsing container properties. NetworkSettings missing.")
connected_networks = self.container['NetworkSettings']['Networks']
for network, config in self.parameters.networks.iteritems():
if connected_networks.get(network, None) is None:
missing_networks.append(network)
if len(missing_networks) > 0:
missing = True
return missing, missing_networks
for network in self.parameters.networks:
if connected_networks.get(network['name'], None) is None:
different = True
differences.append(dict(
parameter=network,
container=None
))
else:
diff = False
if network.get('ipv4_address') and network['ipv4_address'] != connected_networks[network['name']].get('IPAddress'):
diff = True
if network.get('ipv6_address') and network['ipv6_address'] != connected_networks[network['name']].get('GlobalIPv6Address'):
diff = True
if network.get('aliases') and not connected_networks[network['name']].get('Aliases'):
diff = True
if network.get('aliases') and connected_networks[network['name']].get('Aliases'):
if set(network.get('aliases')) != set(connected_networks[network['name']].get('Aliases')):
diff = True
if network.get('links') and not connected_networks[network['name']].get('Links'):
diff = True
if network.get('links') and connected_networks[network['name']].get('Links'):
expected_links = []
for link, alias in network['links'].iteritems():
expected_links.append("%s:%s" % (link, alias))
if set(expected_links) != set(connected_networks[network['name']].get('Links', [])):
diff = True
if diff:
different = True
differences.append(dict(
parameter=network,
container=dict(
name=network['name'],
ipv4_address=connected_networks[network['name']].get('IPAddress'),
ipv6_address=connected_networks[network['name']].get('GlobalIPv6Address'),
aliases=connected_networks[network['name']].get('Aliases'),
links=connected_networks[network['name']].get('Links')
)
))
return different, differences
def has_extra_networks(self):
'''
@ -1198,19 +1332,20 @@ class Container(DockerBaseClass):
extra_networks = []
extra = False
if not self.parameters.networks:
return extra, extra_networks
if not self.container.get('NetworkSettings'):
self.fail("has_extra_networks: Error parsing container properties. NetworkSettings missing.")
connected_networks = self.container['NetworkSettings']['Networks']
for network in connected_networks:
if network not in ('bridge', 'host') and not network.startswith('container:'):
if network not in self.parameters.networks:
extra_networks.append(network)
if len(extra_networks) > 0:
extra = True
connected_networks = self.container['NetworkSettings'].get('Networks')
if connected_networks:
for network, network_config in connected_networks.iteritems():
keep = False
if self.parameters.networks:
for expected_network in self.parameters.networks:
if expected_network['name'] == network:
keep = True
if not keep:
extra = True
extra_networks.append(dict(name=network, id=network_config['NetworkID']))
return extra, extra_networks
def _get_expected_entrypoint(self, image):
@ -1424,8 +1559,6 @@ class ContainerManager(DockerBaseClass):
container = self.update_limits(container)
container = self.update_networks(container)
# TODO implement has_extra_networks
if state == 'started' and not container.running:
container = self.container_start(container.Id)
elif state == 'started' and self.parameters.restart:
@ -1489,15 +1622,65 @@ class ContainerManager(DockerBaseClass):
return container
def update_networks(self, container):
networks_missing, missing_networks = container.has_missing_networks()
if networks_missing:
self.log("networks missing")
self.log(missing_networks, pretty_print=True)
if networks_missing and not self.check_mode:
for network in missing_networks:
self.connect_container_to_network(container.Id, network)
return self._get_container(container.Id)
return container
has_network_differences, network_differences = container.has_network_differences()
updated_container = container
if has_network_differences:
if self.diff.get('differences'):
self.diff['differences'].append(dict(network_differences=network_differences))
else:
self.diff['differences'] = dict(network_differences=network_differences)
self.results['changed'] = True
updated_container = self._add_networks(container, network_differences)
if self.parameters.purge_networks:
has_extra_networks, extra_networks = container.has_extra_networks()
if has_extra_networks:
if self.diff.get('differences'):
self.diff['differences'].append(dict(purge_networks=extra_networks))
else:
self.diff['differences'] = dict(purge_networks=extra_networks)
self.results['changed'] = True
updated_container = self._purge_networks(container, extra_networks)
return updated_container
def _add_networks(self, container, differences):
for diff in differences:
# remove the container from the network, if connected
if diff.get('container'):
self.results['actions'].append(dict(removed_from_network=diff['parameter']['name']))
if not self.check_mode:
try:
self.client.disconnect_container_from_network(container.Id, diff['parameter']['id'])
except Exception, exc:
self.fail("Error disconnecting container from network %s - %s" % (diff['parameter']['name'],
str(exc)))
# connect to the network
params = dict(
ipv4_address=diff['parameter'].get('ipv4_address', None),
ipv6_address=diff['parameter'].get('ipv6_address', None),
links=diff['parameter'].get('links', None),
aliases=diff['parameter'].get('aliases', None)
)
self.results['actions'].append(dict(added_to_network=diff['parameter']['name'], network_parameters=params))
if not self.check_mode:
try:
self.log("Connecting conainer to network %s" % diff['parameter']['id'])
self.log(params, pretty_print=True)
self.client.connect_container_to_network(container.Id, diff['parameter']['id'], **params)
except Exception, exc:
self.fail("Error connecting container to network %s - %s" % (diff['parameter']['name'], str(exc)))
return self._get_container(container.Id)
def _purge_networks(self, container, networks):
for network in networks:
self.results['actions'].append(dict(removed_from_network=network['name']))
if not self.check_mode:
try:
self.client.disconnect_container_from_network(container.Id, network['id'])
except Exception, exc:
self.fail("Error disconnecting container from network %s - %s" % (network['name'],
str(exc)))
return self._get_container(container.Id)
def container_create(self, image, create_parameters):
self.log("create container")
@ -1627,13 +1810,14 @@ def main():
memory_swappiness=dict(type='int'),
name=dict(type='str', required=True),
network_mode=dict(type='str'),
networks=dict(type='dict'),
networks=dict(type='list'),
oom_killer=dict(type='bool'),
paused=dict(type='bool', default=False),
pid_mode=dict(type='str'),
privileged=dict(type='bool', default=False),
published_ports=dict(type='list', aliases=['ports']),
pull=dict(type='bool', default=False),
purge_networks=dict(type='bool', deault=False),
read_only=dict(type='bool', default=False),
recreate=dict(type='bool', default=False),
restart=dict(type='bool', default=False),

Loading…
Cancel
Save