summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/cloud/lxd/lxd_container.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/modules/cloud/lxd/lxd_container.py')
-rw-r--r--lib/ansible/modules/cloud/lxd/lxd_container.py657
1 files changed, 0 insertions, 657 deletions
diff --git a/lib/ansible/modules/cloud/lxd/lxd_container.py b/lib/ansible/modules/cloud/lxd/lxd_container.py
deleted file mode 100644
index a75767e244..0000000000
--- a/lib/ansible/modules/cloud/lxd/lxd_container.py
+++ /dev/null
@@ -1,657 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2016, Hiroaki Nakamura <hnakamur@gmail.com>
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: lxd_container
-short_description: Manage LXD Containers
-version_added: "2.2"
-description:
- - Management of LXD containers
-author: "Hiroaki Nakamura (@hnakamur)"
-options:
- name:
- description:
- - Name of a container.
- required: true
- architecture:
- description:
- - The architecture for the container (e.g. "x86_64" or "i686").
- See U(https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1)
- required: false
- config:
- description:
- - 'The config for the container (e.g. {"limits.cpu": "2"}).
- See U(https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1)'
- - If the container already exists and its "config" value in metadata
- obtained from
- GET /1.0/containers/<name>
- U(https://github.com/lxc/lxd/blob/master/doc/rest-api.md#10containersname)
- are different, they this module tries to apply the configurations.
- - The key starts with 'volatile.' are ignored for this comparison.
- - Not all config values are supported to apply the existing container.
- Maybe you need to delete and recreate a container.
- required: false
- devices:
- description:
- - 'The devices for the container
- (e.g. { "rootfs": { "path": "/dev/kvm", "type": "unix-char" }).
- See U(https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1)'
- required: false
- ephemeral:
- description:
- - Whether or not the container is ephemeral (e.g. true or false).
- See U(https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1)
- required: false
- type: bool
- source:
- description:
- - 'The source for the container
- (e.g. { "type": "image",
- "mode": "pull",
- "server": "https://images.linuxcontainers.org",
- "protocol": "lxd",
- "alias": "ubuntu/xenial/amd64" }).'
- - 'See U(https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1) for complete API documentation.'
- - 'Note that C(protocol) accepts two choices: C(lxd) or C(simplestreams)'
- required: false
- state:
- choices:
- - started
- - stopped
- - restarted
- - absent
- - frozen
- description:
- - Define the state of a container.
- required: false
- default: started
- timeout:
- description:
- - A timeout for changing the state of the container.
- - This is also used as a timeout for waiting until IPv4 addresses
- are set to the all network interfaces in the container after
- starting or restarting.
- required: false
- default: 30
- wait_for_ipv4_addresses:
- description:
- - If this is true, the C(lxd_container) waits until IPv4 addresses
- are set to the all network interfaces in the container after
- starting or restarting.
- required: false
- default: false
- type: bool
- force_stop:
- description:
- - If this is true, the C(lxd_container) forces to stop the container
- when it stops or restarts the container.
- required: false
- default: false
- type: bool
- url:
- description:
- - The unix domain socket path or the https URL for the LXD server.
- required: false
- default: unix:/var/lib/lxd/unix.socket
- snap_url:
- description:
- - The unix domain socket path when LXD is installed by snap package manager.
- required: false
- default: unix:/var/snap/lxd/common/lxd/unix.socket
- version_added: '2.8'
- client_key:
- description:
- - The client certificate key file path.
- required: false
- default: '"{}/.config/lxc/client.key" .format(os.environ["HOME"])'
- aliases: [ key_file ]
- client_cert:
- description:
- - The client certificate file path.
- required: false
- default: '"{}/.config/lxc/client.crt" .format(os.environ["HOME"])'
- aliases: [ cert_file ]
- trust_password:
- description:
- - The client trusted password.
- - You need to set this password on the LXD server before
- running this module using the following command.
- lxc config set core.trust_password <some random password>
- See U(https://www.stgraber.org/2016/04/18/lxd-api-direct-interaction/)
- - If trust_password is set, this module send a request for
- authentication before sending any requests.
- required: false
-notes:
- - Containers must have a unique name. If you attempt to create a container
- with a name that already existed in the users namespace the module will
- simply return as "unchanged".
- - There are two ways to run commands in containers, using the command
- module or using the ansible lxd connection plugin bundled in Ansible >=
- 2.1, the later requires python to be installed in the container which can
- be done with the command module.
- - You can copy a file from the host to the container
- with the Ansible M(copy) and M(template) module and the `lxd` connection plugin.
- See the example below.
- - You can copy a file in the created container to the localhost
- with `command=lxc file pull container_name/dir/filename filename`.
- See the first example below.
-'''
-
-EXAMPLES = '''
-# An example for creating a Ubuntu container and install python
-- hosts: localhost
- connection: local
- tasks:
- - name: Create a started container
- lxd_container:
- name: mycontainer
- state: started
- source:
- type: image
- mode: pull
- server: https://images.linuxcontainers.org
- protocol: lxd # if you get a 404, try setting protocol: simplestreams
- alias: ubuntu/xenial/amd64
- profiles: ["default"]
- wait_for_ipv4_addresses: true
- timeout: 600
-
- - name: check python is installed in container
- delegate_to: mycontainer
- raw: dpkg -s python
- register: python_install_check
- failed_when: python_install_check.rc not in [0, 1]
- changed_when: false
-
- - name: install python in container
- delegate_to: mycontainer
- raw: apt-get install -y python
- when: python_install_check.rc == 1
-
-# An example for creating an Ubuntu 14.04 container using an image fingerprint.
-# This requires changing 'server' and 'protocol' key values, replacing the
-# 'alias' key with with 'fingerprint' and supplying an appropriate value that
-# matches the container image you wish to use.
-- hosts: localhost
- connection: local
- tasks:
- - name: Create a started container
- lxd_container:
- name: mycontainer
- state: started
- source:
- type: image
- mode: pull
- # Provides current (and older) Ubuntu images with listed fingerprints
- server: https://cloud-images.ubuntu.com/releases
- # Protocol used by 'ubuntu' remote (as shown by 'lxc remote list')
- protocol: simplestreams
- # This provides an Ubuntu 14.04 LTS amd64 image from 20150814.
- fingerprint: e9a8bdfab6dc
- profiles: ["default"]
- wait_for_ipv4_addresses: true
- timeout: 600
-
-# An example for deleting a container
-- hosts: localhost
- connection: local
- tasks:
- - name: Delete a container
- lxd_container:
- name: mycontainer
- state: absent
-
-# An example for restarting a container
-- hosts: localhost
- connection: local
- tasks:
- - name: Restart a container
- lxd_container:
- name: mycontainer
- state: restarted
-
-# An example for restarting a container using https to connect to the LXD server
-- hosts: localhost
- connection: local
- tasks:
- - name: Restart a container
- lxd_container:
- url: https://127.0.0.1:8443
- # These client_cert and client_key values are equal to the default values.
- #client_cert: "{{ lookup('env', 'HOME') }}/.config/lxc/client.crt"
- #client_key: "{{ lookup('env', 'HOME') }}/.config/lxc/client.key"
- trust_password: mypassword
- name: mycontainer
- state: restarted
-
-# Note your container must be in the inventory for the below example.
-#
-# [containers]
-# mycontainer ansible_connection=lxd
-#
-- hosts:
- - mycontainer
- tasks:
- - name: copy /etc/hosts in the created container to localhost with name "mycontainer-hosts"
- fetch:
- src: /etc/hosts
- dest: /tmp/mycontainer-hosts
- flat: true
-'''
-
-RETURN = '''
-addresses:
- description: Mapping from the network device name to a list of IPv4 addresses in the container
- returned: when state is started or restarted
- type: dict
- sample: {"eth0": ["10.155.92.191"]}
-old_state:
- description: The old state of the container
- returned: when state is started or restarted
- type: str
- sample: "stopped"
-logs:
- description: The logs of requests and responses.
- returned: when ansible-playbook is invoked with -vvvv.
- type: list
- sample: "(too long to be placed here)"
-actions:
- description: List of actions performed for the container.
- returned: success
- type: list
- sample: '["create", "start"]'
-'''
-import datetime
-import os
-import time
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.lxd import LXDClient, LXDClientException
-
-
-# LXD_ANSIBLE_STATES is a map of states that contain values of methods used
-# when a particular state is evoked.
-LXD_ANSIBLE_STATES = {
- 'started': '_started',
- 'stopped': '_stopped',
- 'restarted': '_restarted',
- 'absent': '_destroyed',
- 'frozen': '_frozen'
-}
-
-# ANSIBLE_LXD_STATES is a map of states of lxd containers to the Ansible
-# lxc_container module state parameter value.
-ANSIBLE_LXD_STATES = {
- 'Running': 'started',
- 'Stopped': 'stopped',
- 'Frozen': 'frozen',
-}
-
-# ANSIBLE_LXD_DEFAULT_URL is a default value of the lxd endpoint
-ANSIBLE_LXD_DEFAULT_URL = 'unix:/var/lib/lxd/unix.socket'
-
-# CONFIG_PARAMS is a list of config attribute names.
-CONFIG_PARAMS = [
- 'architecture', 'config', 'devices', 'ephemeral', 'profiles', 'source'
-]
-
-
-class LXDContainerManagement(object):
- def __init__(self, module):
- """Management of LXC containers via Ansible.
-
- :param module: Processed Ansible Module.
- :type module: ``object``
- """
- self.module = module
- self.name = self.module.params['name']
- self._build_config()
-
- self.state = self.module.params['state']
-
- self.timeout = self.module.params['timeout']
- self.wait_for_ipv4_addresses = self.module.params['wait_for_ipv4_addresses']
- self.force_stop = self.module.params['force_stop']
- self.addresses = None
-
- self.key_file = self.module.params.get('client_key', None)
- self.cert_file = self.module.params.get('client_cert', None)
- self.debug = self.module._verbosity >= 4
-
- try:
- if self.module.params['url'] != ANSIBLE_LXD_DEFAULT_URL:
- self.url = self.module.params['url']
- elif os.path.exists(self.module.params['snap_url'].replace('unix:', '')):
- self.url = self.module.params['snap_url']
- else:
- self.url = self.module.params['url']
- except Exception as e:
- self.module.fail_json(msg=e.msg)
-
- try:
- self.client = LXDClient(
- self.url, key_file=self.key_file, cert_file=self.cert_file,
- debug=self.debug
- )
- except LXDClientException as e:
- self.module.fail_json(msg=e.msg)
- self.trust_password = self.module.params.get('trust_password', None)
- self.actions = []
-
- def _build_config(self):
- self.config = {}
- for attr in CONFIG_PARAMS:
- param_val = self.module.params.get(attr, None)
- if param_val is not None:
- self.config[attr] = param_val
-
- def _get_container_json(self):
- return self.client.do(
- 'GET', '/1.0/containers/{0}'.format(self.name),
- ok_error_codes=[404]
- )
-
- def _get_container_state_json(self):
- return self.client.do(
- 'GET', '/1.0/containers/{0}/state'.format(self.name),
- ok_error_codes=[404]
- )
-
- @staticmethod
- def _container_json_to_module_state(resp_json):
- if resp_json['type'] == 'error':
- return 'absent'
- return ANSIBLE_LXD_STATES[resp_json['metadata']['status']]
-
- def _change_state(self, action, force_stop=False):
- body_json = {'action': action, 'timeout': self.timeout}
- if force_stop:
- body_json['force'] = True
- return self.client.do('PUT', '/1.0/containers/{0}/state'.format(self.name), body_json=body_json)
-
- def _create_container(self):
- config = self.config.copy()
- config['name'] = self.name
- self.client.do('POST', '/1.0/containers', config)
- self.actions.append('create')
-
- def _start_container(self):
- self._change_state('start')
- self.actions.append('start')
-
- def _stop_container(self):
- self._change_state('stop', self.force_stop)
- self.actions.append('stop')
-
- def _restart_container(self):
- self._change_state('restart', self.force_stop)
- self.actions.append('restart')
-
- def _delete_container(self):
- self.client.do('DELETE', '/1.0/containers/{0}'.format(self.name))
- self.actions.append('delete')
-
- def _freeze_container(self):
- self._change_state('freeze')
- self.actions.append('freeze')
-
- def _unfreeze_container(self):
- self._change_state('unfreeze')
- self.actions.append('unfreez')
-
- def _container_ipv4_addresses(self, ignore_devices=None):
- ignore_devices = ['lo'] if ignore_devices is None else ignore_devices
-
- resp_json = self._get_container_state_json()
- network = resp_json['metadata']['network'] or {}
- network = dict((k, v) for k, v in network.items() if k not in ignore_devices) or {}
- addresses = dict((k, [a['address'] for a in v['addresses'] if a['family'] == 'inet']) for k, v in network.items()) or {}
- return addresses
-
- @staticmethod
- def _has_all_ipv4_addresses(addresses):
- return len(addresses) > 0 and all(len(v) > 0 for v in addresses.values())
-
- def _get_addresses(self):
- try:
- due = datetime.datetime.now() + datetime.timedelta(seconds=self.timeout)
- while datetime.datetime.now() < due:
- time.sleep(1)
- addresses = self._container_ipv4_addresses()
- if self._has_all_ipv4_addresses(addresses):
- self.addresses = addresses
- return
- except LXDClientException as e:
- e.msg = 'timeout for getting IPv4 addresses'
- raise
-
- def _started(self):
- if self.old_state == 'absent':
- self._create_container()
- self._start_container()
- else:
- if self.old_state == 'frozen':
- self._unfreeze_container()
- elif self.old_state == 'stopped':
- self._start_container()
- if self._needs_to_apply_container_configs():
- self._apply_container_configs()
- if self.wait_for_ipv4_addresses:
- self._get_addresses()
-
- def _stopped(self):
- if self.old_state == 'absent':
- self._create_container()
- else:
- if self.old_state == 'stopped':
- if self._needs_to_apply_container_configs():
- self._start_container()
- self._apply_container_configs()
- self._stop_container()
- else:
- if self.old_state == 'frozen':
- self._unfreeze_container()
- if self._needs_to_apply_container_configs():
- self._apply_container_configs()
- self._stop_container()
-
- def _restarted(self):
- if self.old_state == 'absent':
- self._create_container()
- self._start_container()
- else:
- if self.old_state == 'frozen':
- self._unfreeze_container()
- if self._needs_to_apply_container_configs():
- self._apply_container_configs()
- self._restart_container()
- if self.wait_for_ipv4_addresses:
- self._get_addresses()
-
- def _destroyed(self):
- if self.old_state != 'absent':
- if self.old_state == 'frozen':
- self._unfreeze_container()
- if self.old_state != 'stopped':
- self._stop_container()
- self._delete_container()
-
- def _frozen(self):
- if self.old_state == 'absent':
- self._create_container()
- self._start_container()
- self._freeze_container()
- else:
- if self.old_state == 'stopped':
- self._start_container()
- if self._needs_to_apply_container_configs():
- self._apply_container_configs()
- self._freeze_container()
-
- def _needs_to_change_container_config(self, key):
- if key not in self.config:
- return False
- if key == 'config':
- old_configs = dict((k, v) for k, v in self.old_container_json['metadata'][key].items() if not k.startswith('volatile.'))
- for k, v in self.config['config'].items():
- if k not in old_configs:
- return True
- if old_configs[k] != v:
- return True
- return False
- else:
- old_configs = self.old_container_json['metadata'][key]
- return self.config[key] != old_configs
-
- def _needs_to_apply_container_configs(self):
- return (
- self._needs_to_change_container_config('architecture') or
- self._needs_to_change_container_config('config') or
- self._needs_to_change_container_config('ephemeral') or
- self._needs_to_change_container_config('devices') or
- self._needs_to_change_container_config('profiles')
- )
-
- def _apply_container_configs(self):
- old_metadata = self.old_container_json['metadata']
- body_json = {
- 'architecture': old_metadata['architecture'],
- 'config': old_metadata['config'],
- 'devices': old_metadata['devices'],
- 'profiles': old_metadata['profiles']
- }
- if self._needs_to_change_container_config('architecture'):
- body_json['architecture'] = self.config['architecture']
- if self._needs_to_change_container_config('config'):
- for k, v in self.config['config'].items():
- body_json['config'][k] = v
- if self._needs_to_change_container_config('ephemeral'):
- body_json['ephemeral'] = self.config['ephemeral']
- if self._needs_to_change_container_config('devices'):
- body_json['devices'] = self.config['devices']
- if self._needs_to_change_container_config('profiles'):
- body_json['profiles'] = self.config['profiles']
- self.client.do('PUT', '/1.0/containers/{0}'.format(self.name), body_json=body_json)
- self.actions.append('apply_container_configs')
-
- def run(self):
- """Run the main method."""
-
- try:
- if self.trust_password is not None:
- self.client.authenticate(self.trust_password)
-
- self.old_container_json = self._get_container_json()
- self.old_state = self._container_json_to_module_state(self.old_container_json)
- action = getattr(self, LXD_ANSIBLE_STATES[self.state])
- action()
-
- state_changed = len(self.actions) > 0
- result_json = {
- 'log_verbosity': self.module._verbosity,
- 'changed': state_changed,
- 'old_state': self.old_state,
- 'actions': self.actions
- }
- if self.client.debug:
- result_json['logs'] = self.client.logs
- if self.addresses is not None:
- result_json['addresses'] = self.addresses
- self.module.exit_json(**result_json)
- except LXDClientException as e:
- state_changed = len(self.actions) > 0
- fail_params = {
- 'msg': e.msg,
- 'changed': state_changed,
- 'actions': self.actions
- }
- if self.client.debug:
- fail_params['logs'] = e.kwargs['logs']
- self.module.fail_json(**fail_params)
-
-
-def main():
- """Ansible Main module."""
-
- module = AnsibleModule(
- argument_spec=dict(
- name=dict(
- type='str',
- required=True
- ),
- architecture=dict(
- type='str',
- ),
- config=dict(
- type='dict',
- ),
- devices=dict(
- type='dict',
- ),
- ephemeral=dict(
- type='bool',
- ),
- profiles=dict(
- type='list',
- ),
- source=dict(
- type='dict',
- ),
- state=dict(
- choices=LXD_ANSIBLE_STATES.keys(),
- default='started'
- ),
- timeout=dict(
- type='int',
- default=30
- ),
- wait_for_ipv4_addresses=dict(
- type='bool',
- default=False
- ),
- force_stop=dict(
- type='bool',
- default=False
- ),
- url=dict(
- type='str',
- default=ANSIBLE_LXD_DEFAULT_URL
- ),
- snap_url=dict(
- type='str',
- default='unix:/var/snap/lxd/common/lxd/unix.socket'
- ),
- client_key=dict(
- type='str',
- default='{0}/.config/lxc/client.key'.format(os.environ['HOME']),
- aliases=['key_file']
- ),
- client_cert=dict(
- type='str',
- default='{0}/.config/lxc/client.crt'.format(os.environ['HOME']),
- aliases=['cert_file']
- ),
- trust_password=dict(type='str', no_log=True)
- ),
- supports_check_mode=False,
- )
-
- lxd_manage = LXDContainerManagement(module=module)
- lxd_manage.run()
-
-
-if __name__ == '__main__':
- main()