files.new_contents: # coding: utf-8
  
  import re
  import os
  import time
  import tmt
  import click
  import requests
  
  from tmt.utils import ProvisionError, WORKDIR_ROOT
  
  def import_testcloud():
      """
      Import testcloud module only when needed
  
      Until we have a separate package for each plugin.
      """
      global testcloud
      try:
          import testcloud.image
          import testcloud.instance
      except ImportError:
          raise ProvisionError(
              "Install 'testcloud' to provision using this method.")
  
  # Testcloud cache to our tmt's workdir root
  TESTCLOUD_DATA = os.path.join(WORKDIR_ROOT, 'testcloud')
  TESTCLOUD_IMAGES = os.path.join(TESTCLOUD_DATA, 'images')
  
  # Userdata for cloud-init
  USER_DATA = """#cloud-config
  password: %s
  chpasswd:
    expire: false
  users:
    - default
    - name: {user_name}
  ssh_authorized_keys:
    - {public_key}
  ssh_pwauth: true
  disable_root: false
  runcmd:
    - sed -i -e '/^.*PermitRootLogin/s/^.*$/PermitRootLogin yes/'
      /etc/ssh/sshd_config
    - systemctl reload sshd
  """
  
  # Libvirt domain XML template related variables
  DOMAIN_TEMPLATE_NAME = 'domain-template.jinja'
  DOMAIN_TEMPLATE_FILE = os.path.join(TESTCLOUD_DATA, DOMAIN_TEMPLATE_NAME)
  DOMAIN_TEMPLATE = """<domain type='kvm'>
      <name>{{ domain_name }}</name>
      <uuid>{{ uuid }}</uuid>
      <memory unit='KiB'>{{ memory }}</memory>
      <currentMemory unit='KiB'>{{ memory }}</currentMemory>
    <vcpu placement='static'>1</vcpu>
    <os>
      <type arch='x86_64' machine='pc'>hvm</type>
      {{ uefi_loader }}
      <boot dev='hd'/>
    </os>
    <features>
      <acpi/>
      <apic/>
      <vmport state='off'/>
    </features>
    <cpu mode='host-passthrough'/>
    <clock offset='utc'>
      <timer name='rtc' tickpolicy='catchup'/>
      <timer name='pit' tickpolicy='delay'/>
      <timer name='hpet' present='no'/>
    </clock>
    <on_poweroff>destroy</on_poweroff>
    <on_reboot>restart</on_reboot>
    <on_crash>restart</on_crash>
    <pm>
      <suspend-to-mem enabled='no'/>
      <suspend-to-disk enabled='no'/>
    </pm>
    <devices>
      <emulator>/usr/bin/qemu-kvm</emulator>
      <disk type='file' device='disk'>
        <driver name='qemu' type='qcow2'/>
        <source file="{{ disk }}"/>
        <target dev='vda' bus='virtio'/>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
      </disk>
      <disk type='file' device='disk'>
        <driver name='qemu' type='raw' cache='unsafe'/>
        <source file="{{ seed }}"/>
        <target dev='vdb' bus='virtio'/>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
      </disk>
      <interface type='network'>
          <mac address="{{ mac_address }}"/>
        <source network='default'/>
        <model type='rtl8139'/>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
      </interface>
      <serial type='pty'>
        <target port='0'/>
      </serial>
      <console type='pty'>
        <target type='serial' port='0'/>
      </console>
      <input type='keyboard' bus='ps2'/>
      <memballoon model='virtio'>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x09' function='0x0'/>
      </memballoon>
      <rng model='virtio'>
        <backend model='random'>/dev/urandom</backend>
      </rng>
    </devices>
  </domain>
  """
  
  # VM defaults
  DEFAULT_BOOT_TIMEOUT = 60      # seconds
  DEFAULT_CONNECT_TIMEOUT = 10   # seconds
  
  # Image guessing related variables
  KOJI_URL = 'https://kojipkgs.fedoraproject.org/compose'
  
  
  class ProvisionTestcloud(tmt.steps.provision.ProvisionPlugin):
      """
      Use testcloud to provision a guest
  
      Minimal config which uses the latest fedora image:
  
          provision:
              how: virtual
  
      Here's a full config example:
  
          provision:
              how: virtual
              image: fedora
              user: root
              memory: 2048
  
      As the image use 'fedora' for the latest released Fedora compose,
      'rawhide' for the latest Rawhide compose, short aliases such as
      'fedora-32', 'f-32' or 'f32' for specific release or a full url to
      the qcow2 image for example from:
  
          https://kojipkgs.fedoraproject.org/compose/
  
      Use full path for image stored on local disk, for example:
  
          /var/tmp/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2
      """
  
      # Guest instance
      _guest = None
  
      # Supported methods
      _methods = [
          tmt.steps.Method(name='virtual.testcloud', doc=__doc__, order=50),
          ]
  
      @classmethod
      def options(cls, how=None):
          """ Prepare command line options for testcloud """
          return [
              click.option(
                  '-i', '--image', metavar='IMAGE',
                  help='Select image to use. Short name or complete url.'),
              click.option(
                  '-m', '--memory', metavar='MEMORY',
                  help='Set available memory in MB, 2048 MB by default.'),
              click.option(
                  '-D', '--disk', metavar='MEMORY',
                  help='Specify disk size in GB, 10 GB by default.'),
              click.option(
                  '-u', '--user', metavar='USER',
                  help='Username to use for all guest operations.'),
              ] + super().options(how)
  
      def default(self, option, default=None):
          """ Return default data for given option """
          defaults = {
              'user': 'root',
              'memory': 2048,
              'disk': 10,
              'image': 'fedora',
              }
          if option in defaults:
              return defaults[option]
          return default
  
      def show(self):
          """ Show provision details """
          super().show(['image', 'user', 'memory', 'disk'])
  
      def wake(self, data=None):
          """ Override options and wake up the guest """
          super().wake(['image', 'memory', 'disk', 'user'])
  
          # Convert memory and disk to integers
          for key in ['memory', 'disk']:
              if isinstance(self.get(key), str):
                  self.data[key] = int(self.data[key])
  
          # Wake up testcloud instance
          if data:
              guest = GuestTestcloud(data, name=self.name, parent=self.step)
              guest.wake()
              self._guest = guest
  
      def go(self):
          """ Provision the testcloud instance """
          super().go()
  
          # Give info about provided data
          data = dict()
          for key in ['image', 'user', 'memory', 'disk']:
              data[key] = self.get(key)
              if key == 'memory':
                  self.info('memory', f"{self.get('memory')} MB", 'green')
              elif key == 'disk':
                  self.info('disk', f"{self.get('disk')} GB", 'green')
              else:
                  self.info(key, data[key], 'green')
  
          # Create a new GuestTestcloud instance and start it
          self._guest = GuestTestcloud(data, name=self.name, parent=self.step)
          self._guest.start()
  
      def guest(self):
          """ Return the provisioned guest """
          return self._guest
  
  
  class GuestTestcloud(tmt.Guest):
      """
      Testcloud Instance
  
      The following keys are expected in the 'data' dictionary::
  
          image ...... qcov image name or url
          user ....... user name to log in
          memory ..... memory size for vm
          disk ....... disk size for vm
      """
  
      @staticmethod
      def _guess_image_url(name):
          """ Guess image url for given name """
  
          def latest_release():
              """ Get the latest released
  
  [... the rest is omitted ...]
files.new_path: tmt/steps/provision/testcloud.py
files.old_contents: # coding: utf-8
  
  import re
  import os
  import time
  import tmt
  import click
  import requests
  
  from tmt.utils import ProvisionError, WORKDIR_ROOT
  
  def import_testcloud():
      """
      Import testcloud module only when needed
  
      Until we have a separate package for each plugin.
      """
      global testcloud
      try:
          import testcloud.image
          import testcloud.instance
      except ImportError:
          raise ProvisionError(
              "Install 'testcloud' to provision using this method.")
  
  # Testcloud cache to our tmt's workdir root
  TESTCLOUD_DATA = os.path.join(WORKDIR_ROOT, 'testcloud')
  TESTCLOUD_IMAGES = os.path.join(TESTCLOUD_DATA, 'images')
  
  # Userdata for cloud-init
  USER_DATA = """#cloud-config
  password: %s
  chpasswd:
    expire: false
  users:
    - default
    - name: {user_name}
  ssh_authorized_keys:
    - {public_key}
  ssh_pwauth: true
  disable_root: false
  runcmd:
    - sed -i -e '/^.*PermitRootLogin/s/^.*$/PermitRootLogin yes/'
      /etc/ssh/sshd_config
    - systemctl reload sshd
  """
  
  # Libvirt domain XML template related variables
  DOMAIN_TEMPLATE_NAME = 'domain-template.jinja'
  DOMAIN_TEMPLATE_FILE = os.path.join(TESTCLOUD_DATA, DOMAIN_TEMPLATE_NAME)
  DOMAIN_TEMPLATE = """<domain type='kvm'>
      <name>{{ domain_name }}</name>
      <uuid>{{ uuid }}</uuid>
      <memory unit='KiB'>{{ memory }}</memory>
      <currentMemory unit='KiB'>{{ memory }}</currentMemory>
    <vcpu placement='static'>1</vcpu>
    <os>
      <type arch='x86_64' machine='pc'>hvm</type>
      {{ uefi_loader }}
      <boot dev='hd'/>
    </os>
    <features>
      <acpi/>
      <apic/>
      <vmport state='off'/>
    </features>
    <cpu mode='host-passthrough'/>
    <clock offset='utc'>
      <timer name='rtc' tickpolicy='catchup'/>
      <timer name='pit' tickpolicy='delay'/>
      <timer name='hpet' present='no'/>
    </clock>
    <on_poweroff>destroy</on_poweroff>
    <on_reboot>restart</on_reboot>
    <on_crash>restart</on_crash>
    <pm>
      <suspend-to-mem enabled='no'/>
      <suspend-to-disk enabled='no'/>
    </pm>
    <devices>
      <emulator>/usr/bin/qemu-kvm</emulator>
      <disk type='file' device='disk'>
        <driver name='qemu' type='qcow2'/>
        <source file="{{ disk }}"/>
        <target dev='vda' bus='virtio'/>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
      </disk>
      <disk type='file' device='disk'>
        <driver name='qemu' type='raw' cache='unsafe'/>
        <source file="{{ seed }}"/>
        <target dev='vdb' bus='virtio'/>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
      </disk>
      <interface type='network'>
          <mac address="{{ mac_address }}"/>
        <source network='default'/>
        <model type='rtl8139'/>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
      </interface>
      <serial type='pty'>
        <target port='0'/>
      </serial>
      <console type='pty'>
        <target type='serial' port='0'/>
      </console>
      <input type='keyboard' bus='ps2'/>
      <memballoon model='virtio'>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x09' function='0x0'/>
      </memballoon>
      <rng model='virtio'>
        <backend model='random'>/dev/urandom</backend>
      </rng>
    </devices>
  </domain>
  """
  
  # VM defaults
  DEFAULT_BOOT_TIMEOUT = 60      # seconds
  DEFAULT_CONNECT_TIMEOUT = 10   # seconds
  
  # Image guessing related variables
  KOJI_URL = 'https://kojipkgs.fedoraproject.org/compose'
  RAWHIDE_URL = f'{KOJI_URL}/rawhide/latest-Fedora-Rawhide'
  RAWHIDE_ID = f'{RAWHIDE_URL}/COMPOSE_ID'
  RAWHIDE_IMAGE_URL = f'{RAWHIDE_URL}/compose/Cloud/x86_64/images'
  
  
  class ProvisionTestcloud(tmt.steps.provision.ProvisionPlugin):
      """
      Use testcloud to provision a guest
  
      Minimal config which uses the latest fedora image:
  
          provision:
              how: virtual
  
      Here's a full config example:
  
          provision:
              how: virtual
              image: fedora
              user: root
              memory: 2048
  
      For the image use 'fedora' for the latest rawhide compose or full
      url to the qcow2 image for example from:
  
          https://kojipkgs.fedoraproject.org/compose/
  
      Use full path for image stored on local disk, for example:
  
          /var/tmp/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2
      """
  
      # Guest instance
      _guest = None
  
      # Supported methods
      _methods = [
          tmt.steps.Method(name='virtual.testcloud', doc=__doc__, order=50),
          ]
  
      @classmethod
      def options(cls, how=None):
          """ Prepare command line options for testcloud """
          return [
              click.option(
                  '-i', '--image', metavar='IMAGE',
                  help='Select image to use. Short name or complete url.'),
              click.option(
                  '-m', '--memory', metavar='MEMORY',
                  help='Set available memory in MB, 2048 MB by default.'),
              click.option(
                  '-D', '--disk', metavar='MEMORY',
                  help='Specify disk size in GB, 10 GB by default.'),
              click.option(
                  '-u', '--user', metavar='USER',
                  help='Username to use for all guest operations.'),
              ] + super().options(how)
  
      def default(self, option, default=None):
          """ Return default data for given option """
          defaults = {
              'user': 'root',
              'memory': 2048,
              'disk': 10,
              'image': 'fedora',
              }
          if option in defaults:
              return defaults[option]
          return default
  
      def show(self):
          """ Show provision details """
          super().show(['image', 'user', 'memory', 'disk'])
  
      def wake(self, data=None):
          """ Override options and wake up the guest """
          super().wake(['image', 'memory', 'disk', 'user'])
  
          # Convert memory and disk to integers
          for key in ['memory', 'disk']:
              if isinstance(self.get(key), str):
                  self.data[key] = int(self.data[key])
  
          # Wake up testcloud instance
          if data:
              guest = GuestTestcloud(data, name=self.name, parent=self.step)
              guest.wake()
              self._guest = guest
  
      def go(self):
          """ Provision the testcloud instance """
          super().go()
  
          # Give info about provided data
          data = dict()
          for key in ['image', 'user', 'memory', 'disk']:
              data[key] = self.get(key)
              if key == 'memory':
                  self.info('memory', f"{self.get('memory')} MB", 'green')
              elif key == 'disk':
                  self.info('disk', f"{self.get('disk')} GB", 'green')
              else:
                  self.info(key, data[key], 'green')
  
          # Create a new GuestTestcloud instance and start it
          self._guest = GuestTestcloud(data, name=self.name, parent=self.step)
          self._guest.start()
  
      def guest(self):
          """ Return the provisioned guest """
          return self._guest
  
  
  class GuestTestcloud(tmt.Guest):
      """
      Testcloud Instance
  
      The following keys are expected in the 'data' dictionary::
  
          image ...... qcov image name or url
          user ....... user name to log in
          memory ..... memory size for vm
          disk ....... disk size for vm
      """
  
      @staticmethod
      def _guess_image_url(name
  
  [... the rest is omitted ...]
files.old_path: tmt/steps/provision/testcloud.py