text: #!/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()
  