mirror of https://github.com/ansible/ansible.git
Adding docker_network module. (#4404)
parent
4047096ac4
commit
fdfdfe9017
@ -0,0 +1,385 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2016 Red Hat | Ansible
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
module: docker_network
|
||||
version_added: "2.2"
|
||||
short_description: Manage Docker networks
|
||||
description:
|
||||
- Create/remove Docker networks and connect containers to them.
|
||||
- Performs largely the same function as the "docker network" CLI subcommand.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the network to operate on.
|
||||
default: null
|
||||
required: true
|
||||
aliases:
|
||||
- network_name
|
||||
|
||||
connected:
|
||||
description:
|
||||
- List of container names or container IDs to connect to a network.
|
||||
default: null
|
||||
|
||||
driver:
|
||||
description:
|
||||
- Specify the type of network. Docker provides bridge and overlay drivers, but 3rd party drivers can also be used.
|
||||
default: bridge
|
||||
|
||||
driver_options:
|
||||
description:
|
||||
- Dictionary of network settings. Consult docker docs for valid options and values.
|
||||
default: null
|
||||
|
||||
force:
|
||||
description:
|
||||
- >
|
||||
With state 'absent' forces disconnecting all containers from the
|
||||
network prior to deleting the network. With state 'present' will
|
||||
disconnect all containers, delete the network and re-create the
|
||||
network. This option is required if you have changed the ipam or
|
||||
driver options and want an existing network to be updated to use the
|
||||
new options.
|
||||
default: false
|
||||
|
||||
appends:
|
||||
description:
|
||||
- By default the connected list is canonical, meaning containers not on the list are removed from the network.
|
||||
Use 'appends' to leave existing containers connected.
|
||||
default: false
|
||||
aliases:
|
||||
- incremental
|
||||
|
||||
ipam_driver:
|
||||
description:
|
||||
- Specifiy an IPAM driver.
|
||||
default: null
|
||||
|
||||
ipam_options:
|
||||
description:
|
||||
- Dictionary of IPAM options.
|
||||
default: null
|
||||
|
||||
state:
|
||||
description:
|
||||
- >
|
||||
"absent" deletes the network. If a network has connected containers, it
|
||||
cannot be deleted. Use the force option to disconnect all containers
|
||||
and delete the network.
|
||||
- >
|
||||
"present" creates the network, if it does not already exist with the
|
||||
specified parameters, and connects the list of containers provided via
|
||||
the connected parameter. Containers not on the list will be
|
||||
disconnected. An empty list will leave no containers connected to the
|
||||
network. Use the appends option to leave existing containers
|
||||
connected. Use the force options to force re-creation of the network.
|
||||
default: present
|
||||
choices:
|
||||
- absent
|
||||
- present
|
||||
|
||||
extends_documentation_fragment:
|
||||
- docker
|
||||
|
||||
authors:
|
||||
- "Ben Keith (@keitwb)"
|
||||
- "Chris Houseknecht (@chouseknecht)"
|
||||
|
||||
requirements:
|
||||
- "python >= 2.6"
|
||||
- "docker-py >= 1.7.0"
|
||||
- "The docker server >= 1.9.0"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create a network
|
||||
docker_network:
|
||||
name: network_one
|
||||
|
||||
- name: Remove all but selected list of containers
|
||||
docker_network:
|
||||
name: network_one
|
||||
connected:
|
||||
- container_a
|
||||
- container_b
|
||||
- container_c
|
||||
|
||||
- name: Remove a single container
|
||||
docker_network:
|
||||
name: network_one
|
||||
connected: "{{ fulllist|difference(['container_a']) }}"
|
||||
|
||||
- name: Add a container to a network, leaving existing containers connected
|
||||
docker_network:
|
||||
name: network_one
|
||||
connected:
|
||||
- container_a
|
||||
appends: yes
|
||||
|
||||
- name: Create a network with options
|
||||
docker_network:
|
||||
name: network_two
|
||||
driver_options:
|
||||
com.docker.network.bridge.name: net2
|
||||
ipam_options:
|
||||
subnet: '172.3.26.0/16'
|
||||
gateway: 172.3.26.1
|
||||
iprange: '192.168.1.0/24'
|
||||
|
||||
- name: Delete a network, disconnecting all containers
|
||||
docker_network:
|
||||
name: network_one
|
||||
state: absent
|
||||
force: yes
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
facts:
|
||||
description: Network inspection results for the affected network.
|
||||
returned: success
|
||||
type: complex
|
||||
sample: {}
|
||||
'''
|
||||
|
||||
|
||||
from ansible.module_utils.docker_common import *
|
||||
|
||||
try:
|
||||
from docker import utils
|
||||
from docker.utils.types import Ulimit
|
||||
except:
|
||||
# missing docker-py handled in ansible.module_utils.docker
|
||||
pass
|
||||
|
||||
|
||||
class TaskParameters(DockerBaseClass):
|
||||
def __init__(self, client):
|
||||
super(TaskParameters, self).__init__()
|
||||
self.client = client
|
||||
|
||||
self.network_name = None
|
||||
self.connected = None
|
||||
self.driver = None
|
||||
self.driver_options = None
|
||||
self.ipam_driver = None
|
||||
self.ipam_options = None
|
||||
self.appends = None
|
||||
self.force = None
|
||||
self.debug = None
|
||||
|
||||
for key, value in client.module.params.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
def container_names_in_network(network):
|
||||
return [c['Name'] for c in network['Containers'].values()]
|
||||
|
||||
|
||||
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.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)
|
||||
|
||||
state = self.parameters.state
|
||||
if state == 'present':
|
||||
self.present()
|
||||
elif state == 'absent':
|
||||
self.absent()
|
||||
|
||||
def get_existing_network(self):
|
||||
networks = self.client.networks()
|
||||
network = None
|
||||
for n in networks:
|
||||
if n['Name'] == self.parameters.network_name:
|
||||
self.results[u'actions'].append('Found network %s' % self.parameters.network_name)
|
||||
network = n
|
||||
return network
|
||||
|
||||
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)
|
||||
'''
|
||||
different = False
|
||||
differences = []
|
||||
if self.parameters.driver and self.parameters.driver != net['Driver']:
|
||||
different = True
|
||||
differences.append('driver')
|
||||
if self.parameters.driver_options:
|
||||
if not net.get('Options'):
|
||||
different = True
|
||||
differences.append('driver_options')
|
||||
else:
|
||||
for key, value in self.parameters.driver_options.iteritems():
|
||||
if not net['Options'].get(key) or value != net['Options'][key]:
|
||||
different = True
|
||||
differences.append('driver_options.%s' % key)
|
||||
if self.parameters.ipam_driver:
|
||||
if not net.get('IPAM') or net['IPAM']['Driver'] != self.parameters.ipam_driver:
|
||||
different = True
|
||||
differences.append('ipam_driver')
|
||||
if self.parameters.ipam_options:
|
||||
if not net.get('IPAM') or not net['IPAM'].get('Config'):
|
||||
different = True
|
||||
differences.append('ipam_options')
|
||||
else:
|
||||
for key, value in self.parameters.ipam_options.iteritems():
|
||||
camelkey = None
|
||||
for net_key in net['IPAM']['Config'][0]:
|
||||
if key == net_key.lower():
|
||||
camelkey = net_key
|
||||
break
|
||||
if not camelkey:
|
||||
# key not found
|
||||
different = True
|
||||
differences.append('ipam_options.%s' % key)
|
||||
elif net['IPAM']['Config'][0].get(camelkey) != value:
|
||||
# key has different value
|
||||
different = True
|
||||
differences.append('ipam_options.%s' % key)
|
||||
return different, differences
|
||||
|
||||
def create_network(self):
|
||||
if not self.existing_network:
|
||||
ipam_pools = []
|
||||
if self.parameters.ipam_options:
|
||||
ipam_pools.append(utils.create_ipam_pool(**self.parameters.ipam_options))
|
||||
|
||||
ipam_config = utils.create_ipam_config(driver=self.parameters.ipam_driver,
|
||||
pool_configs=ipam_pools)
|
||||
|
||||
if not self.check_mode:
|
||||
resp = self.client.create_network(self.parameters.network_name,
|
||||
driver=self.parameters.driver,
|
||||
options=self.parameters.driver_options,
|
||||
ipam=ipam_config)
|
||||
|
||||
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
|
||||
|
||||
def disconnect_missing(self):
|
||||
for c in self.existing_network['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']
|
||||
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
|
||||
|
||||
def present(self):
|
||||
different = False
|
||||
differences = []
|
||||
if self.existing_network:
|
||||
different, differences = self.has_different_config(self.existing_network)
|
||||
|
||||
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.results['diff'] = differences
|
||||
|
||||
if not self.check_mode and not self.parameters.debug:
|
||||
self.results.pop('actions')
|
||||
|
||||
self.results['facts'] = self.get_existing_network()
|
||||
|
||||
def absent(self):
|
||||
self.remove_network()
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
network_name = dict(type='str', required=True, aliases=['name']),
|
||||
connected = dict(type='list', default=[]),
|
||||
state = dict(type='str', default='present', choices=['present', 'absent']),
|
||||
driver = dict(type='str', default='bridge'),
|
||||
driver_options = dict(type='dict', default={}),
|
||||
force = dict(type='bool', default=False),
|
||||
appends = dict(type='bool', default=False, aliases=['incremental']),
|
||||
ipam_driver = dict(type='str', default=None),
|
||||
ipam_options = dict(type='dict', default={}),
|
||||
debug = dict(type='bool', default=False)
|
||||
)
|
||||
|
||||
required_if = []
|
||||
|
||||
client = AnsibleDockerClient(
|
||||
argument_spec=argument_spec,
|
||||
required_if=required_if,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
cm = DockerNetworkManager(client)
|
||||
client.module.exit_json(**cm.results)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue