ansible-collections-openstack/plugins/modules/coe_cluster_template.py
Dmitriy Rabotyagov 40a384214c Ensure coe_cluster_template compare labels properly
SDK does return all keys and values of the template labels as
strings. At the same time user can define some labels as integers or
booleans, which will break comparison of labels and lead to module
failure on consecutive runs.

Change-Id: I7ab624428c8bb06030a2b28888f5cb89bb249f08
2024-10-09 20:56:10 +00:00

536 lines
19 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2018 Catalyst IT Ltd.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: coe_cluster_template
short_description: Manage COE cluster template in OpenStack Cloud
author: OpenStack Ansible SIG
description:
- Add or remove a COE (Container Orchestration Engine) cluster template
via OpenStack's Magnum aka Container Infrastructure Management API.
options:
coe:
description:
- The Container Orchestration Engine for this cluster template
- Required if I(state) is C(present).
choices: [kubernetes, swarm, mesos]
type: str
dns_nameserver:
description:
- The DNS nameserver address.
- Magnum's default value for I(dns_nameserver) is C(8.8.8.8).
type: str
docker_storage_driver:
description:
- Docker storage driver.
choices: [devicemapper, overlay, overlay2]
type: str
docker_volume_size:
description:
- The size in GB of the docker volume.
type: int
external_network_id:
description:
- The external network to attach to the cluster.
- When I(is_floating_ip_enabled) is set to C(true), then
I(external_network_id) must be defined.
type: str
fixed_network:
description:
- The fixed network name or id to attach to the cluster.
type: str
fixed_subnet:
description:
- The fixed subnet name or id to attach to the cluster.
type: str
flavor_id:
description:
- The flavor of the minion node for this cluster template.
type: str
is_floating_ip_enabled:
description:
- Indicates whether created clusters should have a floating ip or not.
- When I(is_floating_ip_enabled) is set to C(true), then
I(external_network_id) must be defined.
type: bool
default: true
aliases: ['floating_ip_enabled']
is_master_lb_enabled:
description:
- Indicates whether created clusters should have a load balancer
for master nodes or not.
- Magnum's default value for I(is_master_lb_enabled) is C(true),
ours is C(false).
type: bool
default: false
aliases: ['master_lb_enabled']
is_public:
description:
- Indicates whether the cluster template is public or not.
- Magnum's default value for I(is_public) is C(false).
type: bool
aliases: ['public']
is_registry_enabled:
description:
- Indicates whether the docker registry is enabled.
- Magnum's default value for I(is_registry_enabled) is C(false).
type: bool
aliases: ['registry_enabled']
insecure_registry:
description:
- The URL pointing to users own private insecure docker registry.
type: str
is_tls_disabled:
description:
- Indicates whether the TLS should be disabled.
- Magnum's default value for I(is_tls_disabled) is C(false).
type: bool
aliases: ['tls_disabled']
keypair_id:
description:
- Name or ID of the keypair to use.
type: str
image_id:
description:
- Image id the cluster will be based on.
- Required if I(state) is C(present).
type: str
labels:
description:
- One or more key/value pairs.
type: raw
http_proxy:
description:
- Address of a proxy that will receive all HTTP requests and relay them.
- The format is a URL including a port number.
type: str
https_proxy:
description:
- Address of a proxy that will receive all HTTPS requests and relay them.
- The format is a URL including a port number.
type: str
master_flavor_id:
description:
- The flavor of the master node for this cluster template.
type: str
name:
description:
- Name that has to be given to the cluster template.
required: true
type: str
network_driver:
description:
- The name of the driver used for instantiating container networks.
choices: [flannel, calico, docker]
type: str
no_proxy:
description:
- A comma separated list of IPs for which proxies should not be
used in the cluster.
type: str
server_type:
description:
- Server type for this cluster template.
- Magnum's default value for I(server_type) is C(vm).
choices: [vm, bm]
type: str
state:
description:
- Indicate desired state of the resource.
choices: [present, absent]
default: present
type: str
volume_driver:
description:
- The name of the driver used for instantiating container volumes.
choices: [cinder, rexray]
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = r'''
cluster_template:
description: Dictionary describing the template.
returned: On success when I(state) is C(present).
type: dict
contains:
apiserver_port:
description: The exposed port of COE API server.
type: int
cluster_distro:
description: Display the attribute os_distro defined as appropriate
metadata in image for the bay/cluster driver.
type: str
coe:
description: The Container Orchestration Engine for this cluster
template. Supported COEs include kubernetes, swarm, mesos.
type: str
sample: kubernetes
created_at:
description: The date and time when the resource was created.
type: str
dns_nameserver:
description: The DNS nameserver for the servers and containers in the
bay/cluster to use.
type: str
sample: '8.8.8.8'
docker_storage_driver:
description: "The name of a driver to manage the storage for the images
and the container's writable layer."
type: str
docker_volume_size:
description: The size in GB for the local storage on each server for the
Docker daemon to cache the images and host the containers.
type: int
sample: 5
external_network_id:
description: The name or network ID of a Neutron network to provide
connectivity to the external internet for the bay/cluster.
type: str
sample: public
fixed_network:
description: The fixed network name to attach to the cluster.
type: str
sample: 07767ec6-85f5-44cb-bd63-242a8e7f0d9d
fixed_subnet:
description: The fixed subnet name to attach to the cluster.
type: str
sample: 05567ec6-85f5-44cb-bd63-242a8e7f0d9d
flavor_id:
description: The nova flavor ID or name for booting the node servers.
type: str
sample: c1.c1r1
http_proxy:
description: Address of a proxy that will receive all HTTP requests
and relay them. The format is a URL including a port
number.
type: str
sample: http://10.0.0.11:9090
https_proxy:
description: Address of a proxy that will receive all HTTPS requests
and relay them. The format is a URL including a port
number.
type: str
sample: https://10.0.0.10:8443
id:
description: The UUID of the cluster template.
type: str
image_id:
description: The name or UUID of the base image in Glance to boot the
servers for the bay/cluster.
type: str
sample: 05567ec6-85f5-44cb-bd63-242a8e7f0e9d
insecure_registry:
description: "The URL pointing to users's own private insecure docker
registry to deploy and run docker containers."
type: str
is_floating_ip_enabled:
description: Indicates whether created clusters should have a
floating ip or not.
type: bool
sample: true
is_hidden:
description: Indicates whether the cluster template is hidden or not.
type: bool
sample: false
is_master_lb_enabled:
description: Indicates whether created clusters should have a load
balancer for master nodes or not.
type: bool
sample: true
is_public:
description: Access to a baymodel/cluster template is normally limited to
the admin, owner or users within the same tenant as the
owners. Setting this flag makes the baymodel/cluster
template public and accessible by other users. The default
is not public.
type: bool
sample: false
is_registry_enabled:
description: "Docker images by default are pulled from the public Docker
registry, but in some cases, users may want to use a
private registry. This option provides an alternative
registry based on the Registry V2: Magnum will create a
local registry in the bay/cluster backed by swift to host
the images. The default is to use the public registry."
type: bool
sample: false
is_tls_disabled:
description: Transport Layer Security (TLS) is normally enabled to secure
the bay/cluster. In some cases, users may want to disable
TLS in the bay/cluster, for instance during development or
to troubleshoot certain problems. Specifying this parameter
will disable TLS so that users can access the COE endpoints
without a certificate. The default is TLS enabled.
type: bool
sample: false
keypair_id:
description: Name of the SSH keypair to configure in the bay/cluster
servers for ssh access.
type: str
sample: mykey
labels:
description: One or more key/value pairs.
type: dict
sample: {'key1': 'value1', 'key2': 'value2'}
master_flavor_id:
description: The flavor of the master node for this cluster template.
type: str
sample: c1.c1r1
name:
description: Name that has to be given to the cluster template.
type: str
sample: k8scluster
network_driver:
description: The name of a network driver for providing the networks for
the containers
type: str
sample: calico
no_proxy:
description: A comma separated list of IPs for which proxies should
not be used in the cluster.
type: str
sample: 10.0.0.4,10.0.0.5
server_type:
description: The servers in the bay/cluster can be vm or baremetal.
type: str
sample: vm
updated_at:
description: The date and time when the resource was updated.
type: str
uuid:
description: The UUID of the cluster template.
type: str
volume_driver:
description: The name of a volume driver for managing the persistent
storage for the containers.
type: str
sample: cinder
'''
EXAMPLES = r'''
- name: Create a new Kubernetes cluster template
openstack.cloud.coe_cluster_template:
cloud: devstack
coe: kubernetes
image_id: 2a8c9888-9054-4b06-a1ca-2bb61f9adb72
keypair_id: mykey
name: k8s
is_public: false
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class COEClusterTemplateModule(OpenStackModule):
argument_spec = dict(
coe=dict(choices=['kubernetes', 'swarm', 'mesos']),
dns_nameserver=dict(),
docker_storage_driver=dict(choices=['devicemapper', 'overlay',
'overlay2']),
docker_volume_size=dict(type='int'),
external_network_id=dict(),
fixed_network=dict(),
fixed_subnet=dict(),
flavor_id=dict(),
http_proxy=dict(),
https_proxy=dict(),
image_id=dict(),
is_floating_ip_enabled=dict(type='bool', default=True,
aliases=['floating_ip_enabled']),
keypair_id=dict(),
labels=dict(type='raw'),
master_flavor_id=dict(),
insecure_registry=dict(),
is_master_lb_enabled=dict(type='bool', default=False,
aliases=['master_lb_enabled']),
is_public=dict(type='bool', aliases=['public']),
is_registry_enabled=dict(type='bool', aliases=['registry_enabled']),
is_tls_disabled=dict(type='bool', aliases=['tls_disabled']),
name=dict(required=True),
network_driver=dict(choices=['flannel', 'calico', 'docker']),
no_proxy=dict(),
server_type=dict(choices=['vm', 'bm']),
state=dict(default='present', choices=['absent', 'present']),
volume_driver=dict(choices=['cinder', 'rexray']),
)
module_kwargs = dict(
required_if=[
('state', 'present', ('coe', 'image_id')),
],
supports_check_mode=True,
)
def run(self):
state = self.params['state']
cluster_template = self._find()
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(state, cluster_template))
if state == 'present' and not cluster_template:
# Create cluster_template
cluster_template = self._create()
self.exit_json(
changed=True,
cluster_template=cluster_template.to_dict(computed=False))
elif state == 'present' and cluster_template:
# Update cluster_template
update = self._build_update(cluster_template)
if update:
cluster_template = self._update(cluster_template, update)
self.exit_json(
changed=bool(update),
cluster_template=cluster_template.to_dict(computed=False))
elif state == 'absent' and cluster_template:
# Delete cluster_template
self._delete(cluster_template)
self.exit_json(changed=True)
elif state == 'absent' and not cluster_template:
# Do nothing
self.exit_json(changed=False)
def _build_update(self, cluster_template):
update = {}
if self.params['is_floating_ip_enabled'] \
and self.params['external_network_id'] is None:
raise ValueError('is_floating_ip_enabled is True'
' but external_network_id is missing')
# TODO: Implement support for updates.
non_updateable_keys = [k for k in ['coe', 'dns_nameserver',
'docker_storage_driver',
'docker_volume_size',
'external_network_id',
'fixed_network',
'fixed_subnet', 'flavor_id',
'http_proxy', 'https_proxy',
'image_id',
'insecure_registry',
'is_floating_ip_enabled',
'is_master_lb_enabled',
'is_public', 'is_registry_enabled',
'is_tls_disabled', 'keypair_id',
'master_flavor_id', 'name',
'network_driver', 'no_proxy',
'server_type', 'volume_driver']
if self.params[k] is not None
and self.params[k] != cluster_template[k]]
labels = self.params['labels']
if labels is not None:
if isinstance(labels, str):
labels = dict([tuple(kv.split(":"))
for kv in labels.split(",")])
elif isinstance(labels, dict):
labels = dict({str(k): str(v)
for k, v in labels.items()})
if labels != cluster_template['labels']:
non_updateable_keys.append('labels')
if non_updateable_keys:
self.fail_json(msg='Cannot update parameters {0}'
.format(non_updateable_keys))
attributes = dict((k, self.params[k])
for k in []
if self.params[k] is not None
and self.params[k] != cluster_template[k])
if attributes:
update['attributes'] = attributes
return update
def _create(self):
if self.params['is_floating_ip_enabled'] \
and self.params['external_network_id'] is None:
raise ValueError('is_floating_ip_enabled is True'
' but external_network_id is missing')
# TODO: Complement *_id parameters with find_* functions to allow
# specifying names in addition to IDs.
kwargs = dict((k, self.params[k])
for k in ['coe', 'dns_nameserver',
'docker_storage_driver', 'docker_volume_size',
'external_network_id', 'fixed_network',
'fixed_subnet', 'flavor_id', 'http_proxy',
'https_proxy', 'image_id',
'insecure_registry', 'is_floating_ip_enabled',
'is_master_lb_enabled', 'is_public',
'is_registry_enabled', 'is_tls_disabled',
'keypair_id', 'master_flavor_id', 'name',
'network_driver', 'no_proxy', 'server_type',
'volume_driver']
if self.params[k] is not None)
labels = self.params['labels']
if labels is not None:
if isinstance(labels, str):
labels = dict([tuple(kv.split(":"))
for kv in labels.split(",")])
kwargs['labels'] = labels
return self.conn.container_infrastructure_management.\
create_cluster_template(**kwargs)
def _delete(self, cluster_template):
self.conn.container_infrastructure_management.\
delete_cluster_template(cluster_template['id'])
def _find(self):
name = self.params['name']
filters = {}
image_id = self.params['image_id']
if image_id is not None:
filters['image_id'] = image_id
coe = self.params['coe']
if coe is not None:
filters['coe'] = coe
return self.conn.get_cluster_template(name_or_id=name,
filters=filters)
def _update(self, cluster_template, update):
attributes = update.get('attributes')
if attributes:
# TODO: Implement support for updates.
# cluster_template = self.conn.\
# container_infrastructure_management.update_cluster_template(...)
pass
return cluster_template
def _will_change(self, state, cluster_template):
if state == 'present' and not cluster_template:
return True
elif state == 'present' and cluster_template:
return bool(self._build_update(cluster_template))
elif state == 'absent' and cluster_template:
return True
else:
# state == 'absent' and not cluster_template:
return False
def main():
module = COEClusterTemplateModule()
module()
if __name__ == "__main__":
main()