1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00
community.general/plugins/modules/cloud/kubevirt/kubevirt_pvc.py
Andrew Klychkov 41cfdda6a3
modules: fix examples to use FQCN (#644)
* modules: fix examples to use FQCN

* fix

* fix

* fix
2020-07-13 21:50:31 +02:00

457 lines
16 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Ansible Project
# 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
DOCUMENTATION = '''
module: kubevirt_pvc
short_description: Manage PVCs on Kubernetes
author: KubeVirt Team (@kubevirt)
description:
- Use Openshift Python SDK to manage PVCs on Kubernetes
- Support Containerized Data Importer out of the box
options:
resource_definition:
description:
- "A partial YAML definition of the PVC object being created/updated. Here you can define Kubernetes
PVC Resource parameters not covered by this module's parameters."
- "NOTE: I(resource_definition) has lower priority than module parameters. If you try to define e.g.
I(metadata.namespace) here, that value will be ignored and I(namespace) used instead."
aliases:
- definition
- inline
type: dict
state:
description:
- "Determines if an object should be created, patched, or deleted. When set to C(present), an object will be
created, if it does not already exist. If set to C(absent), an existing object will be deleted. If set to
C(present), an existing object will be patched, if its attributes differ from those specified using
module options and I(resource_definition)."
default: present
choices:
- present
- absent
force:
description:
- If set to C(True), and I(state) is C(present), an existing object will be replaced.
default: false
type: bool
merge_type:
description:
- Whether to override the default patch merge approach with a specific type.
- "This defaults to C(['strategic-merge', 'merge']), which is ideal for using the same parameters
on resource kinds that combine Custom Resources and built-in resources."
- See U(https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment)
- If more than one merge_type is given, the merge_types will be tried in order
choices:
- json
- merge
- strategic-merge
type: list
name:
description:
- Use to specify a PVC object name.
required: true
type: str
namespace:
description:
- Use to specify a PVC object namespace.
required: true
type: str
annotations:
description:
- Annotations attached to this object.
- U(https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
type: dict
labels:
description:
- Labels attached to this object.
- U(https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
type: dict
selector:
description:
- A label query over volumes to consider for binding.
- U(https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
type: dict
access_modes:
description:
- Contains the desired access modes the volume should have.
- "More info: U(https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes)"
type: list
size:
description:
- How much storage to allocate to the PVC.
type: str
aliases:
- storage
storage_class_name:
description:
- Name of the StorageClass required by the claim.
- "More info: U(https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1)"
type: str
volume_mode:
description:
- "This defines what type of volume is required by the claim. Value of Filesystem is implied when not
included in claim spec. This is an alpha feature of kubernetes and may change in the future."
type: str
volume_name:
description:
- This is the binding reference to the PersistentVolume backing this claim.
type: str
cdi_source:
description:
- "If data is to be copied onto the PVC using the Containerized Data Importer you can specify the source of
the data (along with any additional configuration) as well as it's format."
- "Valid source types are: blank, http, s3, registry, pvc and upload. The last one requires using the
M(community.general.kubevirt_cdi_upload) module to actually perform an upload."
- "Source data format is specified using the optional I(content_type). Valid options are C(kubevirt)
(default; raw image) and C(archive) (tar.gz)."
- "This uses the DataVolume source syntax:
U(https://github.com/kubevirt/containerized-data-importer/blob/master/doc/datavolumes.md#https3registry-source)"
type: dict
wait:
description:
- "If set, this module will wait for the PVC to become bound and CDI (if enabled) to finish its operation
before returning."
- "Used only if I(state) set to C(present)."
- "Unless used in conjunction with I(cdi_source), this might result in a timeout, as clusters may be configured
to not bind PVCs until first usage."
default: false
type: bool
wait_timeout:
description:
- Specifies how much time in seconds to wait for PVC creation to complete if I(wait) option is enabled.
- Default value is reasonably high due to an expectation that CDI might take a while to finish its operation.
type: int
default: 300
extends_documentation_fragment:
- community.kubernetes.k8s_auth_options
requirements:
- python >= 2.7
- openshift >= 0.8.2
'''
EXAMPLES = '''
- name: Create a PVC and import data from an external source
community.general.kubevirt_pvc:
name: pvc1
namespace: default
size: 100Mi
access_modes:
- ReadWriteOnce
cdi_source:
http:
url: https://www.source.example/path/of/data/vm.img
# If the URL points to a tar.gz containing the disk image, uncomment the line below:
#content_type: archive
- name: Create a PVC as a clone from a different PVC
community.general.kubevirt_pvc:
name: pvc2
namespace: default
size: 100Mi
access_modes:
- ReadWriteOnce
cdi_source:
pvc:
namespace: source-ns
name: source-pvc
- name: Create a PVC ready for data upload
community.general.kubevirt_pvc:
name: pvc3
namespace: default
size: 100Mi
access_modes:
- ReadWriteOnce
cdi_source:
upload: yes
# You need the kubevirt_cdi_upload module to actually upload something
- name: Create a PVC with a blank raw image
community.general.kubevirt_pvc:
name: pvc4
namespace: default
size: 100Mi
access_modes:
- ReadWriteOnce
cdi_source:
blank: yes
- name: Create a PVC and fill it with data from a container
community.general.kubevirt_pvc:
name: pvc5
namespace: default
size: 100Mi
access_modes:
- ReadWriteOnce
cdi_source:
registry:
url: "docker://kubevirt/fedora-cloud-registry-disk-demo"
'''
RETURN = '''
result:
description:
- The created, patched, or otherwise present object. Will be empty in the case of a deletion.
returned: success
type: complex
contains:
api_version:
description: The versioned schema of this representation of an object.
returned: success
type: str
kind:
description: Represents the REST resource this object represents.
returned: success
type: str
metadata:
description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
returned: success
type: complex
spec:
description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
returned: success
type: complex
status:
description: Current status details for the object.
returned: success
type: complex
items:
description: Returned only when multiple yaml documents are passed to src or resource_definition
returned: when resource_definition or src contains list of objects
type: list
duration:
description: elapsed time of task in seconds
returned: when C(wait) is true
type: int
sample: 48
'''
import copy
import traceback
from collections import defaultdict
from ansible_collections.community.kubernetes.plugins.module_utils.common import AUTH_ARG_SPEC
from ansible_collections.community.kubernetes.plugins.module_utils.raw import KubernetesRawModule
from ansible_collections.community.general.plugins.module_utils.kubevirt import virtdict, KubeVirtRawModule
PVC_ARG_SPEC = {
'name': {'required': True},
'namespace': {'required': True},
'state': {
'type': 'str',
'choices': [
'present', 'absent'
],
'default': 'present'
},
'force': {
'type': 'bool',
'default': False,
},
'merge_type': {
'type': 'list',
'choices': ['json', 'merge', 'strategic-merge']
},
'resource_definition': {
'type': 'dict',
'aliases': ['definition', 'inline']
},
'labels': {'type': 'dict'},
'annotations': {'type': 'dict'},
'selector': {'type': 'dict'},
'access_modes': {'type': 'list'},
'size': {
'type': 'str',
'aliases': ['storage']
},
'storage_class_name': {'type': 'str'},
'volume_mode': {'type': 'str'},
'volume_name': {'type': 'str'},
'cdi_source': {'type': 'dict'},
'wait': {
'type': 'bool',
'default': False
},
'wait_timeout': {
'type': 'int',
'default': 300
}
}
class CreatePVCFailed(Exception):
pass
class KubevirtPVC(KubernetesRawModule):
def __init__(self):
super(KubevirtPVC, self).__init__()
@property
def argspec(self):
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
argument_spec.update(PVC_ARG_SPEC)
return argument_spec
@staticmethod
def fix_serialization(obj):
if obj and hasattr(obj, 'to_dict'):
return obj.to_dict()
return obj
def _parse_cdi_source(self, _cdi_src, metadata):
cdi_src = copy.deepcopy(_cdi_src)
annotations = metadata['annotations']
labels = metadata['labels']
valid_content_types = ('kubevirt', 'archive')
valid_sources = ('http', 's3', 'pvc', 'upload', 'blank', 'registry')
if 'content_type' in cdi_src:
content_type = cdi_src.pop('content_type')
if content_type not in valid_content_types:
raise ValueError("cdi_source.content_type must be one of {0}, not: '{1}'".format(
valid_content_types, content_type))
annotations['cdi.kubevirt.io/storage.contentType'] = content_type
if len(cdi_src) != 1:
raise ValueError("You must specify exactly one valid CDI source, not {0}: {1}".format(len(cdi_src), tuple(cdi_src.keys())))
src_type = tuple(cdi_src.keys())[0]
src_spec = cdi_src[src_type]
if src_type not in valid_sources:
raise ValueError("Got an invalid CDI source type: '{0}', must be one of {1}".format(src_type, valid_sources))
# True for all cases save one
labels['app'] = 'containerized-data-importer'
if src_type == 'upload':
annotations['cdi.kubevirt.io/storage.upload.target'] = ''
elif src_type == 'blank':
annotations['cdi.kubevirt.io/storage.import.source'] = 'none'
elif src_type == 'pvc':
if not isinstance(src_spec, dict) or sorted(src_spec.keys()) != ['name', 'namespace']:
raise ValueError("CDI Source 'pvc' requires specifying 'name' and 'namespace' (and nothing else)")
labels['app'] = 'host-assisted-cloning'
annotations['k8s.io/CloneRequest'] = '{0}/{1}'.format(src_spec['namespace'], src_spec['name'])
elif src_type in ('http', 's3', 'registry'):
if not isinstance(src_spec, dict) or 'url' not in src_spec:
raise ValueError("CDI Source '{0}' requires specifying 'url'".format(src_type))
unknown_params = set(src_spec.keys()).difference(set(('url', 'secretRef', 'certConfigMap')))
if unknown_params:
raise ValueError("CDI Source '{0}' does not know recognize params: {1}".format(src_type, tuple(unknown_params)))
annotations['cdi.kubevirt.io/storage.import.source'] = src_type
annotations['cdi.kubevirt.io/storage.import.endpoint'] = src_spec['url']
if 'secretRef' in src_spec:
annotations['cdi.kubevirt.io/storage.import.secretName'] = src_spec['secretRef']
if 'certConfigMap' in src_spec:
annotations['cdi.kubevirt.io/storage.import.certConfigMap'] = src_spec['certConfigMap']
def _wait_for_creation(self, resource, uid):
return_obj = None
desired_cdi_status = 'Succeeded'
use_cdi = True if self.params.get('cdi_source') else False
if use_cdi and 'upload' in self.params['cdi_source']:
desired_cdi_status = 'Running'
for event in resource.watch(namespace=self.namespace, timeout=self.params.get('wait_timeout')):
entity = event['object']
metadata = entity.metadata
if not hasattr(metadata, 'uid') or metadata.uid != uid:
continue
if entity.status.phase == 'Bound':
if use_cdi and hasattr(metadata, 'annotations'):
import_status = metadata.annotations.get('cdi.kubevirt.io/storage.pod.phase')
if import_status == desired_cdi_status:
return_obj = entity
break
elif import_status == 'Failed':
raise CreatePVCFailed("PVC creation incomplete; importing data failed")
else:
return_obj = entity
break
elif entity.status.phase == 'Failed':
raise CreatePVCFailed("PVC creation failed")
if not return_obj:
raise CreatePVCFailed("PVC creation timed out")
return self.fix_serialization(return_obj)
def execute_module(self):
KIND = 'PersistentVolumeClaim'
API = 'v1'
definition = virtdict()
definition['kind'] = KIND
definition['apiVersion'] = API
metadata = definition['metadata']
metadata['name'] = self.params.get('name')
metadata['namespace'] = self.params.get('namespace')
if self.params.get('annotations'):
metadata['annotations'] = self.params.get('annotations')
if self.params.get('labels'):
metadata['labels'] = self.params.get('labels')
if self.params.get('cdi_source'):
self._parse_cdi_source(self.params.get('cdi_source'), metadata)
spec = definition['spec']
if self.params.get('access_modes'):
spec['accessModes'] = self.params.get('access_modes')
if self.params.get('size'):
spec['resources']['requests']['storage'] = self.params.get('size')
if self.params.get('storage_class_name'):
spec['storageClassName'] = self.params.get('storage_class_name')
if self.params.get('selector'):
spec['selector'] = self.params.get('selector')
if self.params.get('volume_mode'):
spec['volumeMode'] = self.params.get('volume_mode')
if self.params.get('volume_name'):
spec['volumeName'] = self.params.get('volume_name')
# 'resource_definition:' has lower priority than module parameters
definition = dict(KubeVirtRawModule.merge_dicts(definition, self.resource_definitions[0]))
self.client = self.get_api_client()
resource = self.find_resource(KIND, API, fail=True)
definition = self.set_defaults(resource, definition)
result = self.perform_action(resource, definition)
if self.params.get('wait') and self.params.get('state') == 'present':
result['result'] = self._wait_for_creation(resource, result['result']['metadata']['uid'])
self.exit_json(**result)
def main():
module = KubevirtPVC()
try:
module.execute_module()
except Exception as e:
module.fail_json(msg=str(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()