|
|
@ -32,26 +32,43 @@ options:
|
|
|
|
description:
|
|
|
|
description:
|
|
|
|
- Name of a container.
|
|
|
|
- Name of a container.
|
|
|
|
required: true
|
|
|
|
required: true
|
|
|
|
|
|
|
|
architecture:
|
|
|
|
|
|
|
|
description:
|
|
|
|
|
|
|
|
- The archiecture for the container (e.g. "x86_64" or "i686").
|
|
|
|
|
|
|
|
See https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1
|
|
|
|
|
|
|
|
required: false
|
|
|
|
config:
|
|
|
|
config:
|
|
|
|
description:
|
|
|
|
description:
|
|
|
|
- A config dictionary for creating a container.
|
|
|
|
- The config for the container (e.g. '{"limits.cpu": "2"}').
|
|
|
|
See https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1
|
|
|
|
See https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1
|
|
|
|
- Required when the container is not created yet and the state is
|
|
|
|
- If the container already exists and its "config" value in metadata
|
|
|
|
not absent.
|
|
|
|
obtained from
|
|
|
|
- If the container already exists and its metadata obtained from
|
|
|
|
|
|
|
|
GET /1.0/containers/<name>
|
|
|
|
GET /1.0/containers/<name>
|
|
|
|
https://github.com/lxc/lxd/blob/master/doc/rest-api.md#10containersname
|
|
|
|
https://github.com/lxc/lxd/blob/master/doc/rest-api.md#10containersname
|
|
|
|
are different, they this module tries to apply the configurations.
|
|
|
|
are different, they this module tries to apply the configurations.
|
|
|
|
The following keys in config will be compared and applied.
|
|
|
|
- The key starts with 'volatile.' are ignored for this comparison.
|
|
|
|
- architecture
|
|
|
|
|
|
|
|
- config
|
|
|
|
|
|
|
|
- The key starts with 'volatile.' are ignored for comparison.
|
|
|
|
|
|
|
|
- devices
|
|
|
|
|
|
|
|
- ephemeral
|
|
|
|
|
|
|
|
- profiles
|
|
|
|
|
|
|
|
- Not all config values are supported to apply the existing container.
|
|
|
|
- Not all config values are supported to apply the existing container.
|
|
|
|
Maybe you need to delete and recreate a container.
|
|
|
|
Maybe you need to delete and recreate a container.
|
|
|
|
required: false
|
|
|
|
required: false
|
|
|
|
|
|
|
|
devices:
|
|
|
|
|
|
|
|
description:
|
|
|
|
|
|
|
|
- The devices for the container
|
|
|
|
|
|
|
|
(e.g. '{ "rootfs": { "path": "/dev/kvm", "type": "unix-char" }').
|
|
|
|
|
|
|
|
See 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 https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1
|
|
|
|
|
|
|
|
required: false
|
|
|
|
|
|
|
|
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 https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1
|
|
|
|
|
|
|
|
required: false
|
|
|
|
state:
|
|
|
|
state:
|
|
|
|
choices:
|
|
|
|
choices:
|
|
|
|
- started
|
|
|
|
- started
|
|
|
@ -113,14 +130,13 @@ EXAMPLES = """
|
|
|
|
lxd_container:
|
|
|
|
lxd_container:
|
|
|
|
name: mycontainer
|
|
|
|
name: mycontainer
|
|
|
|
state: started
|
|
|
|
state: started
|
|
|
|
config:
|
|
|
|
source:
|
|
|
|
source:
|
|
|
|
type: image
|
|
|
|
type: image
|
|
|
|
mode: pull
|
|
|
|
mode: pull
|
|
|
|
server: https://images.linuxcontainers.org
|
|
|
|
server: https://images.linuxcontainers.org
|
|
|
|
protocol: lxd
|
|
|
|
protocol: lxd
|
|
|
|
alias: "ubuntu/xenial/amd64"
|
|
|
|
alias: "ubuntu/xenial/amd64"
|
|
|
|
profiles: ["default"]
|
|
|
|
profiles: ["default"]
|
|
|
|
|
|
|
|
- name: Install python in the created container "mycontainer"
|
|
|
|
- name: Install python in the created container "mycontainer"
|
|
|
|
command: lxc exec mycontainer -- apt install -y python
|
|
|
|
command: lxc exec mycontainer -- apt install -y python
|
|
|
|
- name: Copy /etc/hosts in the created container "mycontainer" to localhost with name "mycontainer-hosts"
|
|
|
|
- name: Copy /etc/hosts in the created container "mycontainer" to localhost with name "mycontainer-hosts"
|
|
|
@ -144,14 +160,13 @@ EXAMPLES = """
|
|
|
|
lxd_container:
|
|
|
|
lxd_container:
|
|
|
|
name: mycontainer
|
|
|
|
name: mycontainer
|
|
|
|
state: stopped
|
|
|
|
state: stopped
|
|
|
|
config:
|
|
|
|
source:
|
|
|
|
source:
|
|
|
|
type: image
|
|
|
|
type: image
|
|
|
|
mode: pull
|
|
|
|
mode: pull
|
|
|
|
server: https://images.linuxcontainers.org
|
|
|
|
server: https://images.linuxcontainers.org
|
|
|
|
protocol: lxd
|
|
|
|
protocol: lxd
|
|
|
|
alias: "ubuntu/xenial/amd64"
|
|
|
|
alias: "ubuntu/xenial/amd64"
|
|
|
|
profiles: ["default"]
|
|
|
|
profiles: ["default"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- hosts: localhost
|
|
|
|
- hosts: localhost
|
|
|
|
connection: local
|
|
|
|
connection: local
|
|
|
@ -160,14 +175,13 @@ EXAMPLES = """
|
|
|
|
lxd_container:
|
|
|
|
lxd_container:
|
|
|
|
name: mycontainer
|
|
|
|
name: mycontainer
|
|
|
|
state: restarted
|
|
|
|
state: restarted
|
|
|
|
config:
|
|
|
|
source:
|
|
|
|
source:
|
|
|
|
type: image
|
|
|
|
type: image
|
|
|
|
mode: pull
|
|
|
|
mode: pull
|
|
|
|
server: https://images.linuxcontainers.org
|
|
|
|
server: https://images.linuxcontainers.org
|
|
|
|
protocol: lxd
|
|
|
|
protocol: lxd
|
|
|
|
alias: "ubuntu/xenial/amd64"
|
|
|
|
alias: "ubuntu/xenial/amd64"
|
|
|
|
profiles: ["default"]
|
|
|
|
profiles: ["default"]
|
|
|
|
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
RETURN="""
|
|
|
|
RETURN="""
|
|
|
@ -257,7 +271,13 @@ class LxdContainerManagement(object):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
self.module = module
|
|
|
|
self.module = module
|
|
|
|
self.container_name = self.module.params['name']
|
|
|
|
self.container_name = self.module.params['name']
|
|
|
|
self.config = self.module.params.get('config', None)
|
|
|
|
|
|
|
|
|
|
|
|
self.container_config = {}
|
|
|
|
|
|
|
|
for attr in ['architecture', 'config', 'devices', 'ephemeral', 'profiles', 'source']:
|
|
|
|
|
|
|
|
param_val = self.module.params.get(attr, None)
|
|
|
|
|
|
|
|
if param_val is not None:
|
|
|
|
|
|
|
|
self.container_config[attr] = param_val
|
|
|
|
|
|
|
|
|
|
|
|
self.state = self.module.params['state']
|
|
|
|
self.state = self.module.params['state']
|
|
|
|
self.timeout = self.module.params['timeout']
|
|
|
|
self.timeout = self.module.params['timeout']
|
|
|
|
self.wait_for_ipv4_addresses = self.module.params['wait_for_ipv4_addresses']
|
|
|
|
self.wait_for_ipv4_addresses = self.module.params['wait_for_ipv4_addresses']
|
|
|
@ -296,13 +316,12 @@ class LxdContainerManagement(object):
|
|
|
|
def _operate_and_wait(self, method, path, body_json=None):
|
|
|
|
def _operate_and_wait(self, method, path, body_json=None):
|
|
|
|
resp_json = self._send_request(method, path, body_json=body_json)
|
|
|
|
resp_json = self._send_request(method, path, body_json=body_json)
|
|
|
|
if resp_json['type'] == 'async':
|
|
|
|
if resp_json['type'] == 'async':
|
|
|
|
path = '{0}/wait?timeout={1}'.format(resp_json['operation'], self.timeout)
|
|
|
|
url = '{0}/wait?timeout={1}'.format(resp_json['operation'], self.timeout)
|
|
|
|
resp_json = self._send_request('GET', path)
|
|
|
|
resp_json = self._send_request('GET', url)
|
|
|
|
if resp_json['metadata']['status'] != 'Success':
|
|
|
|
if resp_json['metadata']['status'] != 'Success':
|
|
|
|
url = self._path_to_url(path)
|
|
|
|
|
|
|
|
self.module.fail_json(
|
|
|
|
self.module.fail_json(
|
|
|
|
msg='error response for waiting opearation',
|
|
|
|
msg='error response for waiting opearation',
|
|
|
|
request={'method': method, 'url': url, 'json': body_json, 'timeout': self.timeout},
|
|
|
|
request={'method': method, 'url': url, 'timeout': self.timeout},
|
|
|
|
response={'json': resp_json},
|
|
|
|
response={'json': resp_json},
|
|
|
|
logs=self.logs
|
|
|
|
logs=self.logs
|
|
|
|
)
|
|
|
|
)
|
|
|
@ -333,10 +352,10 @@ class LxdContainerManagement(object):
|
|
|
|
return self._operate_and_wait('PUT', '/1.0/containers/{0}/state'.format(self.container_name), body_json=body_json)
|
|
|
|
return self._operate_and_wait('PUT', '/1.0/containers/{0}/state'.format(self.container_name), body_json=body_json)
|
|
|
|
|
|
|
|
|
|
|
|
def _create_container(self):
|
|
|
|
def _create_container(self):
|
|
|
|
config = self.config.copy()
|
|
|
|
config = self.container_config.copy()
|
|
|
|
config['name'] = self.container_name
|
|
|
|
config['name'] = self.container_name
|
|
|
|
self._operate_and_wait('POST', '/1.0/containers', config)
|
|
|
|
self._operate_and_wait('POST', '/1.0/containers', config)
|
|
|
|
self.actions.append('creat')
|
|
|
|
self.actions.append('create')
|
|
|
|
|
|
|
|
|
|
|
|
def _start_container(self):
|
|
|
|
def _start_container(self):
|
|
|
|
self._change_state('start')
|
|
|
|
self._change_state('start')
|
|
|
@ -453,13 +472,13 @@ class LxdContainerManagement(object):
|
|
|
|
self._freeze_container()
|
|
|
|
self._freeze_container()
|
|
|
|
|
|
|
|
|
|
|
|
def _needs_to_change_config(self, key):
|
|
|
|
def _needs_to_change_config(self, key):
|
|
|
|
if key not in self.config:
|
|
|
|
if key not in self.container_config:
|
|
|
|
return False
|
|
|
|
return False
|
|
|
|
if key == 'config':
|
|
|
|
if key == 'config':
|
|
|
|
old_configs = dict((k, v) for k, v in self.old_container_json['metadata'][key].items() if not k.startswith('volatile.'))
|
|
|
|
old_configs = dict((k, v) for k, v in self.old_container_json['metadata'][key].items() if not k.startswith('volatile.'))
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
old_configs = self.old_container_json['metadata'][key]
|
|
|
|
old_configs = self.old_container_json['metadata'][key]
|
|
|
|
return self.config[key] != old_configs
|
|
|
|
return self.container_config[key] != old_configs
|
|
|
|
|
|
|
|
|
|
|
|
def _needs_to_apply_configs(self):
|
|
|
|
def _needs_to_apply_configs(self):
|
|
|
|
return (
|
|
|
|
return (
|
|
|
@ -479,16 +498,16 @@ class LxdContainerManagement(object):
|
|
|
|
'profiles': old_metadata['profiles']
|
|
|
|
'profiles': old_metadata['profiles']
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if self._needs_to_change_config('architecture'):
|
|
|
|
if self._needs_to_change_config('architecture'):
|
|
|
|
body_json['architecture'] = self.config['architecture']
|
|
|
|
body_json['architecture'] = self.container_config['architecture']
|
|
|
|
if self._needs_to_change_config('config'):
|
|
|
|
if self._needs_to_change_config('config'):
|
|
|
|
for k, v in self.config['config'].items():
|
|
|
|
for k, v in self.container_config['config'].items():
|
|
|
|
body_json['config'][k] = v
|
|
|
|
body_json['config'][k] = v
|
|
|
|
if self._needs_to_change_config('ephemeral'):
|
|
|
|
if self._needs_to_change_config('ephemeral'):
|
|
|
|
body_json['ephemeral'] = self.config['ephemeral']
|
|
|
|
body_json['ephemeral'] = self.container_config['ephemeral']
|
|
|
|
if self._needs_to_change_config('devices'):
|
|
|
|
if self._needs_to_change_config('devices'):
|
|
|
|
body_json['devices'] = self.config['devices']
|
|
|
|
body_json['devices'] = self.container_config['devices']
|
|
|
|
if self._needs_to_change_config('profiles'):
|
|
|
|
if self._needs_to_change_config('profiles'):
|
|
|
|
body_json['profiles'] = self.config['profiles']
|
|
|
|
body_json['profiles'] = self.container_config['profiles']
|
|
|
|
self._operate_and_wait('PUT', '/1.0/containers/{0}'.format(self.container_name), body_json=body_json)
|
|
|
|
self._operate_and_wait('PUT', '/1.0/containers/{0}'.format(self.container_name), body_json=body_json)
|
|
|
|
self.actions.append('apply_configs')
|
|
|
|
self.actions.append('apply_configs')
|
|
|
|
|
|
|
|
|
|
|
@ -522,9 +541,24 @@ def main():
|
|
|
|
type='str',
|
|
|
|
type='str',
|
|
|
|
required=True
|
|
|
|
required=True
|
|
|
|
),
|
|
|
|
),
|
|
|
|
|
|
|
|
architecture=dict(
|
|
|
|
|
|
|
|
type='str',
|
|
|
|
|
|
|
|
),
|
|
|
|
config=dict(
|
|
|
|
config=dict(
|
|
|
|
type='dict',
|
|
|
|
type='dict',
|
|
|
|
),
|
|
|
|
),
|
|
|
|
|
|
|
|
devices=dict(
|
|
|
|
|
|
|
|
type='dict',
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
ephemeral=dict(
|
|
|
|
|
|
|
|
type='bool',
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
profiles=dict(
|
|
|
|
|
|
|
|
type='list',
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
source=dict(
|
|
|
|
|
|
|
|
type='dict',
|
|
|
|
|
|
|
|
),
|
|
|
|
state=dict(
|
|
|
|
state=dict(
|
|
|
|
choices=LXD_ANSIBLE_STATES.keys(),
|
|
|
|
choices=LXD_ANSIBLE_STATES.keys(),
|
|
|
|
default='started'
|
|
|
|
default='started'
|
|
|
|