def run_translate(g: guestfs.GuestFS):
    if (g.exists('/etc/redhat-release')
            and 'Red Hat' in g.cat('/etc/redhat-release')):
        distro = Distro.RHEL
    else:
        distro = Distro.CENTOS

    use_rhel_gce_license = utils.GetMetadataAttribute('use_rhel_gce_license')
    el_release = utils.GetMetadataAttribute('el_release')
    install_gce = utils.GetMetadataAttribute('install_gce_packages')
    spec = TranslateSpec(g=g,
                         use_rhel_gce_license=use_rhel_gce_license == 'true',
                         distro=distro,
                         el_release=el_release,
                         install_gce=install_gce == 'true')

    try:
        DistroSpecific(spec)
    except BaseException as raised:
        logging.debug('Translation failed due to: {}'.format(raised))
        for check in checks:
            msg = check(spec)
            if msg:
                raise RuntimeError('{} {}'.format(msg, raised)) from raised
        raise raised
    def check_inspection(self, support_threading):
        if not support_threading:
            return False

        try:
            # Check we can open the Python guestfs module.
            from guestfs import GuestFS
            g = GuestFS()

            # Check for the first version which fixed Python GIL bug.
            version = g.version()
            if version["major"] == 1: # major must be 1
                if version["minor"] == 8:
                    if version["release"] >= 6: # >= 1.8.6
                        return True
                elif version["minor"] == 10:
                    if version["release"] >= 1: # >= 1.10.1
                        return True
                elif version["minor"] == 11:
                    if version["release"] >= 2: # >= 1.11.2
                        return True
                elif version["minor"] >= 12:    # >= 1.12, 1.13, etc.
                    return True
        except:
            pass

        return False
    def check_inspection(self, support_threading):
        if not support_threading:
            return False

        try:
            # Check we can open the Python guestfs module.
            from guestfs import GuestFS
            g = GuestFS()

            # Check for the first version which fixed Python GIL bug.
            version = g.version()
            if version["major"] == 1:  # major must be 1
                if version["minor"] == 8:
                    if version["release"] >= 6:  # >= 1.8.6
                        return True
                elif version["minor"] == 10:
                    if version["release"] >= 1:  # >= 1.10.1
                        return True
                elif version["minor"] == 11:
                    if version["release"] >= 2:  # >= 1.11.2
                        return True
                elif version["minor"] >= 12:  # >= 1.12, 1.13, etc.
                    return True
        except:
            pass

        return False
Beispiel #4
0
def _reset_network(g: guestfs.GuestFS):
    """Update network to use DHCP."""
    logging.info('Updating network to use DHCP.')
    if g.exists('/etc/resolv.conf'):
        g.sh('echo "" > /etc/resolv.conf')
    g.write(
        '/etc/sysconfig/network/ifcfg-eth0', '\n'.join(
            ('BOOTPROTO=dhcp', 'STARTMODE=auto', 'DHCLIENT_SET_HOSTNAME=yes')))
Beispiel #5
0
def _install_packages(g: guestfs.GuestFS, pkgs: typing.List[str]):
  if not pkgs:
    return
  try:
    g.sh('zypper --debug --non-interactive install --no-recommends '
         + ' '.join(pkgs))
  except Exception as e:
    raise ValueError('Failed to install {}: {}'.format(pkgs, e))
Beispiel #6
0
def _unmount_guest(g: guestfs.GuestFS, chroot: Path, guest_runtime_dir: Path):
    mounts = [['umount', '-l', 'proc/'], ['umount', '-l', 'sys/'],
              ['umount', '-l', 'dev/'],
              ['umount', '-l', guest_runtime_dir.as_posix()]]
    for m in mounts:
        print('unmounting %s', m)
        subprocess.run(m, check=True, cwd=chroot.as_posix())
    g.umount_local()
Beispiel #7
0
def _update_grub(g: guestfs.GuestFS):
  """Update and rebuild grub to ensure the image is bootable on GCP.
  See https://cloud.google.com/compute/docs/import/import-existing-image
  """
  g.write('/etc/default/grub', configs.update_grub_conf(
      g.cat('/etc/default/grub'),
      GRUB_CMDLINE_LINUX_DEFAULT='console=ttyS0,38400n8',
      GRUB_CMDLINE_LINUX='',
  ))
  g.command(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'])
Beispiel #8
0
def _install_product(g: guestfs.GuestFS, release: _SuseRelease):
  """Executes SuseConnect -p for each product on `release`.

  Raises:
    ValueError: If there was a failure adding the subscription.
  """
  if release.cloud_product:
    try:
      g.command(['SUSEConnect', '--debug', '-p', release.cloud_product])
    except Exception as e:
      raise _disambiguate_suseconnect_product_error(
          g, release.cloud_product, e)
def install_cloud_sdk(g: guestfs.GuestFS, ubuntu_release: str) -> None:
    """ Installs Google Cloud SDK, supporting apt and snap.

  Args:
    g: A mounted GuestFS instance.
    ubuntu_release: The release nickname (eg: trusty).
  """
    try:
        run(g, 'gcloud --version')
        logging.info(
            'Found gcloud. Skipping installation of Google Cloud SDK.')
        return
    except RuntimeError:
        logging.info('Did not find previous install of gcloud.')

    if g.gcp_image_major < '18':
        g.write('/etc/apt/sources.list.d/partner.list',
                apt_cloud_sdk.format(ubuntu_release=ubuntu_release))
        utils.update_apt(g)
        utils.install_apt_packages(g, 'google-cloud-sdk')
        logging.info('Installed Google Cloud SDK with apt.')
        return

    # Starting at 18.04, Canonical installs the sdk using snap.
    # Running `snap install` directly is not an option here since it
    # requires the snapd daemon to be running on the guest.
    g.write('/etc/cloud/cloud.cfg.d/91-google-cloud-sdk.cfg',
            cloud_init_cloud_sdk)
    logging.info(
        'Google Cloud SDK will be installed using snap with cloud-init.')

    # Include /snap/bin in the PATH for startup and shutdown scripts.
    # This was present in the old guest agent, but lost in the new guest
    # agent.
    for p in [
            '/lib/systemd/system/google-shutdown-scripts.service',
            '/lib/systemd/system/google-startup-scripts.service'
    ]:
        logging.debug('[%s] Checking whether /bin/snap is on PATH.', p)
        if not g.exists(p):
            logging.debug('[%s] Skipping: Unit not found.', p)
            continue
        original_unit = g.cat(p)
        # Check whether the PATH is already set; if so, skip patching to avoid
        # overwriting existing directive.
        match = re.search('Environment=[\'"]?PATH.*',
                          original_unit,
                          flags=re.IGNORECASE)
        if match:
            logging.debug(
                '[%s] Skipping: PATH already defined in unit file: %s.', p,
                match.group())
            continue
        # Add Environment directive to unit file, and show diff in debug log.
        patched_unit = original_unit.replace('[Service]', snap_env_directive)
        g.write(p, patched_unit)
        diff = '\n'.join(Differ().compare(original_unit.splitlines(),
                                          patched_unit.splitlines()))
        logging.debug('[%s] PATH not defined. Added:\n%s', p, diff)
Beispiel #10
0
def cleanup(g: guestfs.GuestFS):
    """Shutdown and close the guestfs.GuestFS handle, retrying on failure."""
    success = False
    for i in range(6):
        try:
            logging.debug('g.shutdown(): {}'.format(g.shutdown()))
            logging.debug('g.close(): {}'.format(g.close()))
            success = True
            break
        except BaseException as raised:
            logging.debug('cleanup failed due to: {}'.format(raised))
            logging.debug('try again in 10 seconds')
            time.sleep(10)
    if not success:
        logging.debug('Shutdown failed. Continuing anyway.')
Beispiel #11
0
 def check_inspection(self):
     try:
         # Check we can open the Python guestfs module.
         from guestfs import GuestFS  # pylint: disable=import-error
         g = GuestFS(close_on_exit=False)
         return bool(getattr(g, "add_libvirt_dom", None))
     except Exception:
         return False
Beispiel #12
0
def _stage_etc_hosts(g: guestfs.GuestFS, dst: Path):
    """Stage copy of guest's /etc/hosts at dst with metadata server added."""
    original = ''
    if g.exists('/etc/hosts'):
        original = g.cat('/etc/hosts')

    metadata_host_line = None
    with open('/etc/hosts') as worker_etc_hosts:
        for line in worker_etc_hosts:
            if 'metadata.google.internal' in line:
                metadata_host_line = line
                break
    if not metadata_host_line:
        raise AssertionError(
            'Did not find metadata host in worker\'s /etc/hosts')

    dst.write_text('{}\n{}\n'.format(original, metadata_host_line))
Beispiel #13
0
 def check_inspection(self):
     try:
         # Check we can open the Python guestfs module.
         from guestfs import GuestFS  # pylint: disable=import-error
         GuestFS(close_on_exit=False)
         return True
     except:
         return False
def setup_cloud_init(g: guestfs.GuestFS):
    """ Install cloud-init if not present, and configure to the cloud provider.

  Args:
    g: A mounted GuestFS instance.
  """
    a = Apt(run)
    curr_version = a.get_package_version(g, 'cloud-init')
    available_versions = a.list_available_versions(g, 'cloud-init')
    # Try to avoid installing 21.3-1, which conflicts which the guest agent.
    # On first boot, systemd reaches a deadlock deadlock and doest start
    # its unit. If a version other than 21.3-1 isn't explicitly found, *and*
    # cloud-init isn't currently installed, then this allows apt to pick the
    # version to install.
    version_to_install = Apt.determine_version_to_install(
        curr_version, available_versions, {'21.3-1'})
    pkg_to_install = ''
    if version_to_install:
        pkg_to_install = 'cloud-init=' + version_to_install
    elif curr_version == '':
        pkg_to_install = 'cloud-init'
    # If this block doesn't execute, it means that cloud-init was found
    # on the system, but there wasn't an upgrade candidate. Therefore
    # leave the version that's currently installed.
    if pkg_to_install:
        logging.info(pkg_to_install)
        utils.install_apt_packages(g, pkg_to_install)
    # Ubuntu 14.04's version of cloud-init doesn't have `clean`.
    if g.gcp_image_major > '14':
        run(g, 'cloud-init clean')
    # Remove cloud-init configs that may conflict with GCE's.
    #
    # - subiquity disables automatic network configuration
    #     https://bugs.launchpad.net/ubuntu/+source/cloud-init/+bug/1871975
    for cfg in [
            'azure', 'curtin', 'waagent', 'walinuxagent', 'aws', 'amazon',
            'subiquity'
    ]:
        run(g, 'rm -f /etc/cloud/cloud.cfg.d/*%s*' % cfg)
    g.write('/etc/cloud/cloud.cfg.d/91-gce-system.cfg', cloud_init_config)
Beispiel #15
0
def install_cloud_sdk(g: guestfs.GuestFS, ubuntu_release: str) -> None:
    """ Installs Google Cloud SDK, supporting apt and snap.

  Args:
    g: A mounted GuestFS instance.
    ubuntu_release: The release nickname (eg: trusty).
  """
    try:
        g.sh('gcloud --version')
        logging.info(
            'Found gcloud. Skipping installation of Google Cloud SDK.')
        return
    except RuntimeError:
        logging.info('Did not find previous install of gcloud.')

    if g.gcp_image_major >= '18':
        # Starting at 18.04, the ubuntu-os-cloud images install the sdk
        # using snap. Running `snap install` directly is not an option
        # here since it requires the snapd daemon to be running on the guest.
        g.write('/etc/cloud/cloud.cfg.d/91-google-cloud-sdk.cfg',
                cloud_init_cloud_sdk)
        logging.info(
            'Google Cloud SDK will be installed using snap with cloud-init.')
    else:
        g.write('/etc/apt/sources.list.d/partner.list',
                apt_cloud_sdk.format(ubuntu_release=ubuntu_release))
        utils.update_apt(g)
        utils.install_apt_packages(g, 'google-cloud-sdk')
        logging.info('Installed Google Cloud SDK with apt.')
Beispiel #16
0
def _remove_existing_subscriptions(g: guestfs.GuestFS):
    """Remove existing subscriptions from guest.

  Implements steps from https://www.suse.com/support/kb/doc/?id=000019085
  """
    for cmd in [
            'rm -f /etc/SUSEConnect',
            'rm -f /etc/zypp/{repos,services,credentials}.d/*'
    ]:
        g.sh(cmd)

    updated = []
    skip = False
    for line in g.cat('/etc/hosts').splitlines():
        if skip:
            skip = False
        elif '# Added by SMT reg' in line:
            log.info('Removing previous SMT host from /etc/hosts')
            skip = True
        else:
            updated.append(line)
    g.write('/etc/hosts', '\n'.join(updated))
Beispiel #17
0
def _write_staged_to_guest(g: guestfs.GuestFS, staged_runtime_dir: Path,
                           staging_fs_root: Path):
    """Write the staged files to the guest.

  Returns:
    Path to mounted guest, path to runtime directory inside guest
  """
    chroot = Path(tempfile.mkdtemp(prefix='guest_chroot'))
    runtime_dir = Path(chroot, g.mkdtemp('/tmp/gceXXXXXX')[1:])

    log.info('writing network files')
    g.copy_in((staging_fs_root / 'etc/hosts').as_posix(), '/etc')
    g.copy_in((staging_fs_root / 'etc/resolv.conf').as_posix(), '/etc')
    log.info('writing network files...done')

    log.info('setting up chroot')
    g.mount_local(chroot.as_posix())
    thread = threading.Thread(target=g.mount_local_run, daemon=True)
    thread.start()
    log.info('setting up chroot...done')

    log.info('setting up mounts')
    host_runtime = staged_runtime_dir.as_posix()
    chroot_runtime = runtime_dir.relative_to(chroot).as_posix()
    mounts = [['mount', '-t', 'proc', '/proc', 'proc/'],
              ['mount', '--rbind', '/sys', 'sys/'],
              ['mount', '--rbind', '/dev', 'dev/'],
              ['mount', '--rbind', host_runtime, chroot_runtime]]
    for cmd in mounts:
        print('mounting {}'.format(cmd))
        subprocess.run(cmd, check=True, cwd=chroot.as_posix())
    log.info('setting up mounts...done')

    _validate_directory_structure(fs_root=chroot,
                                  runtime_dir=runtime_dir,
                                  check_os_mounts=True)
    return chroot, runtime_dir
Beispiel #18
0
class FileSystem:
    """Convenience wrapper over GuestFS instance.

    Simplifies some common routines.

    Automatically translates paths according to the contained File System.

    """

    def __init__(self, disk_path):
        self._root = None
        self._handler = GuestFS()
        self._disk_path = disk_path

    def __enter__(self):
        self.mount()

        return self

    def __exit__(self, *_):
        self.umount()

    def __getattr__(self, attr):
        return getattr(self._handler, attr)

    @property
    def osname(self):
        """Returns the Operating System name."""
        return self._handler.inspect_get_type(self._root)

    @property
    def fsroot(self):
        """Returns the file system root."""
        if self.osname == "windows":
            return "{}:\\".format(self._handler.inspect_get_drive_mappings(self._root)[0][0])
        else:
            return self._handler.inspect_get_mountpoints(self._root)[0][0]

    def mount(self, readonly=True):
        """Mounts the given disk.
        It must be called before any other method.

        """
        self._handler.add_drive_opts(self._disk_path, readonly=True)
        self._handler.launch()

        for mountpoint, device in self._inspect_disk():
            if readonly:
                self._handler.mount_ro(device, mountpoint)
            else:
                self._handler.mount(device, mountpoint)

        if self._handler.inspect_get_type(self._root) == "windows":
            self.path = self._windows_path
        else:
            self.path = posix_path

    def _inspect_disk(self):
        """Inspects the disk and returns the mountpoints mapping
        as a list which order is the supposed one for correct mounting.

        """
        roots = self._handler.inspect_os()

        if roots:
            self._root = roots[0]
            return sorted(self._handler.inspect_get_mountpoints(self._root), key=lambda m: len(m[0]))
        else:
            raise RuntimeError("No OS found on the given disk image.")

    def umount(self):
        """Unmounts the disk.

        After this method is called no further action is allowed.

        """
        self._handler.close()

    def download(self, source, destination):
        """Downloads the file on the disk at source into destination."""
        self._handler.download(posix_path(source), destination)

    def ls(self, path):
        """Lists the content at the given path."""
        return self._handler.ls(posix_path(path))

    def nodes(self, path):
        """Iterates over the files and directories contained within the disk
        starting from the given path.

        Yields the path of the nodes.

        """
        path = posix_path(path)

        yield from (self.path(path, e) for e in self._handler.find(path))

    def checksum(self, path, hashtype="sha1"):
        """Returns the checksum of the given path."""
        return self._handler.checksum(hashtype, posix_path(path))

    def checksums(self, path, hashtype="sha1"):
        """Iterates over the files hashes contained within the disk
        starting from the given path.

        The hashtype keyword allows to choose the file hashing algorithm.

        Yields the following values:

            "C:\\Windows\\System32\\NTUSER.DAT", "hash" for windows
            "/home/user/text.txt", "hash" for other FS

        """
        with NamedTemporaryFile(buffering=0) as tempfile:
            self._handler.checksums_out(hashtype, posix_path(path), tempfile.name)

            yield from (
                (self.path(f[1].lstrip(".")), f[0]) for f in (l.decode("utf8").strip().split(None, 1) for l in tempfile)
            )

    def stat(self, path):
        """Retrieves the status of the node at the given path.

        Returns a dictionary.

        """
        return self._handler.stat(posix_path(path))

    def file(self, path):
        """Analogous to Unix file command.
        Returns the type of node at the given path.

        """
        return self._handler.file(posix_path(path))

    def exists(self, path):
        """Returns whether the path exists."""
        return self._handler.exists(posix_path(path))

    def path(self, *segments):
        """Normalizes the path returned by guestfs in the File System format."""
        raise NotImplementedError("FileSystem needs to be mounted first")

    def _windows_path(self, *segments):
        drive = self._handler.inspect_get_drive_mappings(self._root)[0][0]

        return "%s:%s" % (drive, os.path.join(*segments).replace("/", "\\"))
Beispiel #19
0
def DistroSpecific(g: guestfs.GuestFS):
    el_release = utils.GetMetadataAttribute('el_release')
    install_gce = utils.GetMetadataAttribute('install_gce_packages')
    rhel_license = utils.GetMetadataAttribute('use_rhel_gce_license')

    # This must be performed prior to making network calls from the guest.
    # Otherwise, if /etc/resolv.conf is present, and has an immutable attribute,
    # guestfs will fail with:
    #
    #   rename: /sysroot/etc/resolv.conf to
    #     /sysroot/etc/i9r7obu6: Operation not permitted
    utils.common.ClearEtcResolv(g)

    # Some imported images haven't contained `/etc/yum.repos.d`.
    if not g.exists('/etc/yum.repos.d'):
        g.mkdir('/etc/yum.repos.d')

    if rhel_license == 'true':
        if 'Red Hat' in g.cat('/etc/redhat-release'):
            g.command(['yum', 'remove', '-y', '*rhui*'])
            logging.info('Adding in GCE RHUI package.')
            g.write('/etc/yum.repos.d/google-cloud.repo',
                    repo_compute % el_release)
            yum_install(g, 'google-rhui-client-rhel' + el_release)

    if install_gce == 'true':
        logging.info('Installing GCE packages.')
        g.write('/etc/yum.repos.d/google-cloud.repo',
                repo_compute % el_release)
        if el_release == '6':
            if 'CentOS' in g.cat('/etc/redhat-release'):
                logging.info('Installing CentOS SCL.')
                g.command(['rm', '-f', '/etc/yum.repos.d/CentOS-SCL.repo'])
                yum_install(g, 'centos-release-scl')
            # Install Google Cloud SDK from the upstream tar and create links for the
            # python27 SCL environment.
            logging.info('Installing python27 from SCL.')
            yum_install(g, 'python27')
            logging.info('Installing Google Cloud SDK from tar.')
            sdk_base_url = 'https://dl.google.com/dl/cloudsdk/channels/rapid'
            sdk_base_tar = '%s/google-cloud-sdk.tar.gz' % sdk_base_url
            tar = utils.HttpGet(sdk_base_tar)
            g.write('/tmp/google-cloud-sdk.tar.gz', tar)
            g.command(
                ['tar', 'xzf', '/tmp/google-cloud-sdk.tar.gz', '-C', '/tmp'])
            sdk_version = g.cat('/tmp/google-cloud-sdk/VERSION').strip()

            logging.info('Getting Cloud SDK Version %s', sdk_version)
            sdk_version_tar = 'google-cloud-sdk-%s-linux-x86_64.tar.gz' % sdk_version
            sdk_version_tar_url = '%s/downloads/%s' % (sdk_base_url,
                                                       sdk_version_tar)
            logging.info('Getting versioned Cloud SDK tar file from %s',
                         sdk_version_tar_url)
            tar = utils.HttpGet(sdk_version_tar_url)
            sdk_version_tar_file = os.path.join('/tmp', sdk_version_tar)
            g.write(sdk_version_tar_file, tar)
            g.mkdir_p('/usr/local/share/google')
            g.command([
                'tar', 'xzf', sdk_version_tar_file, '-C',
                '/usr/local/share/google', '--no-same-owner'
            ])

            logging.info('Creating CloudSDK SCL symlinks.')
            sdk_bin_path = '/usr/local/share/google/google-cloud-sdk/bin'
            g.ln_s(os.path.join(sdk_bin_path, 'git-credential-gcloud.sh'),
                   os.path.join('/usr/bin', 'git-credential-gcloud.sh'))
            for binary in ['bq', 'gcloud', 'gsutil']:
                binary_path = os.path.join(sdk_bin_path, binary)
                new_bin_path = os.path.join('/usr/bin', binary)
                bin_str = '#!/bin/bash\nsource /opt/rh/python27/enable\n%s $@' % \
                    binary_path
                g.write(new_bin_path, bin_str)
                g.chmod(0o755, new_bin_path)
        else:
            g.write_append('/etc/yum.repos.d/google-cloud.repo',
                           repo_sdk % el_release)
            yum_install(g, 'google-cloud-sdk')
        yum_install(g, 'google-compute-engine', 'google-osconfig-agent')

    logging.info('Updating initramfs')
    for kver in g.ls('/lib/modules'):
        # Although each directory in /lib/modules typically corresponds to a
        # kernel version  [1], that may not always be true.
        # kernel-abi-whitelists, for example, creates extra directories in
        # /lib/modules.
        #
        # Skip building initramfs if the directory doesn't look like a
        # kernel version. Emulates the version matching from depmod [2].
        #
        # 1. https://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/lib.html
        # 2. https://kernel.googlesource.com/pub/scm/linux/kernel/git/mmarek/kmod
        # /+/tip/tools/depmod.c#2537
        if not re.match(r'^\d+.\d+', kver):
            logging.debug(
                '/lib/modules/{} doesn\'t look like a kernel directory. '
                'Skipping creation of initramfs for it'.format(kver))
            continue
        if not g.exists(os.path.join('/lib/modules', kver, 'modules.dep')):
            try:
                g.command(['depmod', kver])
            except RuntimeError as e:
                logging.info(
                    'Failed to write initramfs for {kver}. If image fails to '
                    'boot, verify that depmod /lib/modules/{kver} runs on '
                    'the original machine'.format(kver=kver))
                logging.debug('depmod error: {}'.format(e))
                continue
        if el_release == '6':
            # Version 6 doesn't have option --kver
            g.command(['dracut', '-v', '-f', kver])
        else:
            g.command(['dracut', '--stdlog=1', '-f', '--kver', kver])

    logging.info('Update grub configuration')
    if el_release == '6':
        # Version 6 doesn't have grub2, file grub.conf needs to be updated by hand
        g.write('/tmp/grub_gce_generated', grub_cfg)
        g.sh(r'grep -P "^[\t ]*initrd|^[\t ]*root|^[\t ]*kernel|^[\t ]*title" '
             r'/boot/grub/grub.conf >> /tmp/grub_gce_generated;'
             r'sed -i "s/console=ttyS0[^ ]*//g" /tmp/grub_gce_generated;'
             r'sed -i "/^[\t ]*kernel/s/$/ console=ttyS0,38400n8/" '
             r'/tmp/grub_gce_generated;'
             r'mv /tmp/grub_gce_generated /boot/grub/grub.conf')
    else:
        g.write('/etc/default/grub', grub2_cfg)
        g.command(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'])

    # Reset network for DHCP.
    logging.info('Resetting network to DHCP for eth0.')
    # Remove NetworkManager-config-server if it's present. The package configures
    # NetworkManager to *not* use DHCP.
    #  https://access.redhat.com/solutions/894763
    g.command(['yum', 'remove', '-y', 'NetworkManager-config-server'])
    g.write('/etc/sysconfig/network-scripts/ifcfg-eth0', ifcfg_eth0)
    def _process(self, conn, vm, vmuuid):
        g = GuestFS()
        prettyvm = conn.get_uri() + ":" + vm.get_name()
        ignore = vmuuid

        disks = []
        for disk in vm.get_disk_devices():
            if (disk.path and
                (disk.type == "block" or disk.type == "file") and
                not disk.device == "cdrom"):
                disks.append(disk)

        if not disks:
            logging.debug("%s: nothing to inspect", prettyvm)
            return

        # Add the disks.  Note they *must* be added with readonly flag set.
        for disk in disks:
            path = disk.path
            driver_type = disk.driver_type

            if not (os.path.exists(path) and os.access(path, os.R_OK)):
                logging.debug("%s: cannot access '%s', skipping inspection",
                              prettyvm, path)
                return

            g.add_drive_opts(path, readonly=1, format=driver_type)

        g.launch()

        # Inspect the operating system.
        roots = g.inspect_os()
        if len(roots) == 0:
            logging.debug("%s: no operating systems found", prettyvm)
            return

        # Arbitrarily pick the first root device.
        root = roots[0]

        # Inspection results.
        typ = g.inspect_get_type(root) # eg. "linux"
        distro = g.inspect_get_distro(root) # eg. "fedora"
        major_version = g.inspect_get_major_version(root) # eg. 14
        minor_version = g.inspect_get_minor_version(root) # eg. 0
        hostname = g.inspect_get_hostname(root) # string
        product_name = g.inspect_get_product_name(root) # string

        # Added in libguestfs 1.9.13:
        product_variant = None
        if hasattr(g, "inspect_get_product_variant"):
            product_variant = g.inspect_get_product_variant(root) # string

        # For inspect_list_applications and inspect_get_icon we
        # require that the guest filesystems are mounted.  However
        # don't fail if this is not possible (I'm looking at you,
        # FreeBSD).
        filesystems_mounted = False
        try:
            # Mount up the disks, like guestfish --ro -i.

            # Sort keys by length, shortest first, so that we end up
            # mounting the filesystems in the correct order.
            mps = g.inspect_get_mountpoints(root)
            def compare(a, b):
                if len(a[0]) > len(b[0]):
                    return 1
                elif len(a[0]) == len(b[0]):
                    return 0
                else:
                    return -1
            mps.sort(compare)

            for mp_dev in mps:
                try:
                    g.mount_ro(mp_dev[1], mp_dev[0])
                except:
                    logging.exception("%s: exception mounting %s on %s "
                                      "(ignored)",
                                      prettyvm, mp_dev[1], mp_dev[0])

            filesystems_mounted = True
        except:
            logging.exception("%s: exception while mounting disks (ignored)",
                              prettyvm)

        icon = None
        apps = None
        if filesystems_mounted:
            # Added in libguestfs 1.11.12:
            if hasattr(g, "inspect_get_icon"):
                # string containing PNG data
                icon = g.inspect_get_icon(root, favicon=0, highquality=1)
                if icon == "":
                    icon = None

            # Inspection applications.
            apps = g.inspect_list_applications(root)

        # Force the libguestfs handle to close right now.
        del g

        # Log what we found.
        logging.debug("%s: detected operating system: %s %s %d.%d (%s)",
                      prettyvm, typ, distro, major_version, minor_version,
                      product_name)
        logging.debug("hostname: %s", hostname)
        if icon:
            logging.debug("icon: %d bytes", len(icon))
        if apps:
            logging.debug("# apps: %d", len(apps))

        data = vmmInspectionData()
        data.type = str(type)
        data.distro = str(distro)
        data.major_version = int(major_version)
        data.minor_version = int(minor_version)
        data.hostname = str(hostname)
        data.product_name = str(product_name)
        data.product_variant = str(product_variant)
        data.icon = str(icon)
        data.applications = list(apps)

        self._set_vm_inspection_data(vm, data)
Beispiel #21
0
def _install_virtio_drivers(g: guestfs.GuestFS):
  """Rebuilds initramfs to ensure that virtio drivers are present."""
  logging.info('Installing virtio drivers.')
  for kernel in g.ls('/lib/modules'):
    g.command(['dracut', '-v', '-f', '--kver', kernel])
Beispiel #22
0
def _refresh_zypper(g: guestfs.GuestFS):
  try:
    g.command(['zypper', '--debug', 'refresh'])
  except Exception as e:
    raise ValueError('Failed to call zypper refresh', e)
Beispiel #23
0
class FileSystem:
    """Convenience wrapper over GuestFS instance.

    Simplifies some common routines.

    Automatically translates paths according to the contained File System.

    """
    def __init__(self, disk_path):
        self._root = None
        self._handler = GuestFS()

        self.disk_path = disk_path

    def __enter__(self):
        self.mount()

        return self

    def __exit__(self, *_):
        self.umount()

    def __getattr__(self, attr):
        return getattr(self._handler, attr)

    @property
    def osname(self):
        """Returns the Operating System name."""
        return self._handler.inspect_get_type(self._root)

    @property
    def fsroot(self):
        """Returns the file system root."""
        if self.osname == 'windows':
            return '{}:\\'.format(
                self._handler.inspect_get_drive_mappings(self._root)[0][0])
        else:
            return self._handler.inspect_get_mountpoints(self._root)[0][0]

    def mount(self, readonly=True):
        """Mounts the given disk.
        It must be called before any other method.

        """
        self._handler.add_drive_opts(self.disk_path, readonly=True)
        self._handler.launch()

        for mountpoint, device in self._inspect_disk():
            if readonly:
                self._handler.mount_ro(device, mountpoint)
            else:
                self._handler.mount(device, mountpoint)

        if self._handler.inspect_get_type(self._root) == 'windows':
            self.path = self._windows_path
        else:
            self.path = posix_path

    def _inspect_disk(self):
        """Inspects the disk and returns the mountpoints mapping
        as a list which order is the supposed one for correct mounting.

        """
        roots = self._handler.inspect_os()

        if roots:
            self._root = roots[0]
            return sorted(self._handler.inspect_get_mountpoints(self._root),
                          key=lambda m: len(m[0]))
        else:
            raise RuntimeError("No OS found on the given disk image.")

    def umount(self):
        """Unmounts the disk.

        After this method is called no further action is allowed.

        """
        self._handler.close()

    def download(self, source, destination):
        """Downloads the file on the disk at source into destination."""
        self._handler.download(posix_path(source), destination)

    def ls(self, path):
        """Lists the content at the given path."""
        return self._handler.ls(posix_path(path))

    def nodes(self, path):
        """Iterates over the files and directories contained within the disk
        starting from the given path.

        Yields the path of the nodes.

        """
        path = posix_path(path)

        yield from (self.path(path, e) for e in self._handler.find(path))

    def checksum(self, path, hashtype='sha1'):
        """Returns the checksum of the given path."""
        return self._handler.checksum(hashtype, posix_path(path))

    def checksums(self, path, hashtype='sha1'):
        """Iterates over the files hashes contained within the disk
        starting from the given path.

        The hashtype keyword allows to choose the file hashing algorithm.

        Yields the following values:

            "C:\\Windows\\System32\\NTUSER.DAT", "hash" for windows
            "/home/user/text.txt", "hash" for other FS

        """
        with NamedTemporaryFile(buffering=0) as tempfile:
            self._handler.checksums_out(hashtype, posix_path(path),
                                        tempfile.name)

            yield from ((self.path(f[1].lstrip('.')), f[0])
                        for f in (l.decode('utf8').strip().split(None, 1)
                                  for l in tempfile))

    def stat(self, path):
        """Retrieves the status of the node at the given path.

        Returns a dictionary.

        """
        return self._handler.stat(posix_path(path))

    def file(self, path):
        """Analogous to Unix file command.
        Returns the type of node at the given path.

        """
        return self._handler.file(posix_path(path))

    def exists(self, path):
        """Returns whether the path exists."""
        return self._handler.exists(posix_path(path))

    def path(self, *segments):
        """Normalizes the path returned by guestfs in the File System format."""
        raise NotImplementedError("FileSystem needs to be mounted first")

    def _windows_path(self, *segments):
        drive = self._handler.inspect_get_drive_mappings(self._root)[0][0]

        return "%s:%s" % (drive, os.path.join(*segments).replace('/', '\\'))
Beispiel #24
0
    def __init__(self, disk_path):
        self._root = None
        self._handler = GuestFS()

        self.disk_path = disk_path
Beispiel #25
0
def _disambiguate_suseconnect_product_error(
    g: guestfs.GuestFS, product: str, error: Exception) -> Exception:
  """Creates a user-debuggable error after failing to add a product
     using SUSEConnect.

  Args:
      g: Mounted GuestFS instance.
      product: The product that failed to be added.
      error: The error returned from `SUSEConnect -p`.
  """
  statuses = []
  try:
    statuses = json.loads(g.command(['SUSEConnect', '--status']))
  except Exception as e:
    return ValueError(
        'Unable to communicate with SCC. Ensure the import '
        'is running in a network that allows internet access.', e)

  # `SUSEConnect --status` returns a list of status objects,
  # where the triple of (identifier, version, arch) uniquely
  # identifies a product in SCC. Below are two examples.
  #
  # Example 1: SLES for SAP 12.2, No subscription
  # [
  #    {
  #       "identifier":"SLES_SAP",
  #       "version":"12.2",
  #       "arch":"x86_64",
  #       "status":"Not Registered"
  #    }
  # ]
  #
  # Example 2: SLES 15.1, Active
  # [
  #    {
  #       "status":"Registered",
  #       "version":"15.1",
  #       "arch":"x86_64",
  #       "identifier":"SLES",
  #       "subscription_status":"ACTIVE"
  #    },
  #    {
  #       "status":"Registered",
  #       "version":"15.1",
  #       "arch":"x86_64",
  #       "identifier":"sle-module-basesystem"
  #    }
  # ]

  for status in statuses:
    if status.get('identifier') not in ('SLES', 'SLES_SAP'):
      continue

    if status.get('subscription_status') == 'ACTIVE':
      return ValueError(
          'Unable to add product "%s" using SUSEConnect. Please ensure that '
          'your subscription includes access to this product.' % product,
          error)

  return ValueError(
      'Unable to find an active SLES subscription. '
      'SCC returned: %s' % statuses)
def DistroSpecific(g: guestfs.GuestFS):
  el_release = utils.GetMetadataAttribute('el_release')
  install_gce = utils.GetMetadataAttribute('install_gce_packages')
  rhel_license = utils.GetMetadataAttribute('use_rhel_gce_license')

  # This must be performed prior to making network calls from the guest.
  # Otherwise, if /etc/resolv.conf is present, and has an immutable attribute,
  # guestfs will fail with:
  #
  #   rename: /sysroot/etc/resolv.conf to
  #     /sysroot/etc/i9r7obu6: Operation not permitted
  utils.common.ClearEtcResolv(g)

  # Some imported images haven't contained `/etc/yum.repos.d`.
  if not g.exists('/etc/yum.repos.d'):
    g.mkdir('/etc/yum.repos.d')

  if rhel_license == 'true':
    if 'Red Hat' in g.cat('/etc/redhat-release'):
      g.command(['yum', 'remove', '-y', '*rhui*'])
      logging.info('Adding in GCE RHUI package.')
      g.write('/etc/yum.repos.d/google-cloud.repo', repo_compute % el_release)
      yum_install(g, 'google-rhui-client-rhel' + el_release)

  if install_gce == 'true':
    logging.info('Installing GCE packages.')
    g.write('/etc/yum.repos.d/google-cloud.repo', repo_compute % el_release)
    if el_release == '6':
      if 'CentOS' in g.cat('/etc/redhat-release'):
        logging.info('Installing CentOS SCL.')
        g.command(['rm', '-f', '/etc/yum.repos.d/CentOS-SCL.repo'])
        yum_install(g, 'centos-release-scl')
      # Install Google Cloud SDK from the upstream tar and create links for the
      # python27 SCL environment.
      logging.info('Installing python27 from SCL.')
      yum_install(g, 'python27')
      logging.info('Installing Google Cloud SDK from tar.')
      sdk_base_url = 'https://dl.google.com/dl/cloudsdk/channels/rapid'
      sdk_base_tar = '%s/google-cloud-sdk.tar.gz' % sdk_base_url
      tar = utils.HttpGet(sdk_base_tar)
      g.write('/tmp/google-cloud-sdk.tar.gz', tar)
      g.command(['tar', 'xzf', '/tmp/google-cloud-sdk.tar.gz', '-C', '/tmp'])
      sdk_version = g.cat('/tmp/google-cloud-sdk/VERSION').strip()

      logging.info('Getting Cloud SDK Version %s', sdk_version)
      sdk_version_tar = 'google-cloud-sdk-%s-linux-x86_64.tar.gz' % sdk_version
      sdk_version_tar_url = '%s/downloads/%s' % (sdk_base_url, sdk_version_tar)
      logging.info('Getting versioned Cloud SDK tar file from %s',
                   sdk_version_tar_url)
      tar = utils.HttpGet(sdk_version_tar_url)
      sdk_version_tar_file = os.path.join('/tmp', sdk_version_tar)
      g.write(sdk_version_tar_file, tar)
      g.mkdir_p('/usr/local/share/google')
      g.command(['tar', 'xzf', sdk_version_tar_file, '-C',
                 '/usr/local/share/google', '--no-same-owner'])

      logging.info('Creating CloudSDK SCL symlinks.')
      sdk_bin_path = '/usr/local/share/google/google-cloud-sdk/bin'
      g.ln_s(os.path.join(sdk_bin_path, 'git-credential-gcloud.sh'),
             os.path.join('/usr/bin', 'git-credential-gcloud.sh'))
      for binary in ['bq', 'gcloud', 'gsutil']:
        binary_path = os.path.join(sdk_bin_path, binary)
        new_bin_path = os.path.join('/usr/bin', binary)
        bin_str = '#!/bin/bash\nsource /opt/rh/python27/enable\n%s $@' % \
            binary_path
        g.write(new_bin_path, bin_str)
        g.chmod(0o755, new_bin_path)
    else:
      g.write_append(
          '/etc/yum.repos.d/google-cloud.repo', repo_sdk % el_release)
      yum_install(g, 'google-cloud-sdk')
    yum_install(g, 'google-compute-engine', 'google-osconfig-agent')

  logging.info('Updating initramfs')
  for kver in g.ls('/lib/modules'):
    if not g.exists(os.path.join('/lib/modules', kver, 'modules.dep')):
      g.command(['depmod', kver])
    if el_release == '6':
      # Version 6 doesn't have option --kver
      g.command(['dracut', '-v', '-f', kver])
    else:
      g.command(['dracut', '--stdlog=1', '-f', '--kver', kver])

  logging.info('Update grub configuration')
  if el_release == '6':
    # Version 6 doesn't have grub2, file grub.conf needs to be updated by hand
    g.write('/tmp/grub_gce_generated', grub_cfg)
    g.sh(
        r'grep -P "^[\t ]*initrd|^[\t ]*root|^[\t ]*kernel|^[\t ]*title" '
        r'/boot/grub/grub.conf >> /tmp/grub_gce_generated;'
        r'sed -i "s/console=ttyS0[^ ]*//g" /tmp/grub_gce_generated;'
        r'sed -i "/^[\t ]*kernel/s/$/ console=ttyS0,38400n8/" '
        r'/tmp/grub_gce_generated;'
        r'mv /tmp/grub_gce_generated /boot/grub/grub.conf')
  else:
    g.write('/etc/default/grub', grub2_cfg)
    g.command(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'])

  # Reset network for DHCP.
  logging.info('Resetting network to DHCP for eth0.')
  # Remove NetworkManager-config-server if it's present. The package configures
  # NetworkManager to *not* use DHCP.
  #  https://access.redhat.com/solutions/894763
  g.command(['yum', 'remove', '-y', 'NetworkManager-config-server'])
  g.write('/etc/sysconfig/network-scripts/ifcfg-eth0', ifcfg_eth0)
Beispiel #27
0
 def __init__(self, disk_path):
     self._root = None
     self._handler = GuestFS()
     self._disk_path = disk_path
Beispiel #28
0
    def _process(self, conn, vm, vmuuid):
        if re.search(r"^guestfs-", vm.get_name()):
            logging.debug("ignore libvirt/guestfs temporary VM %s",
                          vm.get_name())
            return

        g = GuestFS()
        prettyvm = conn.get_uri() + ":" + vm.get_name()
        ignore = vmuuid

        disks = []
        for disk in vm.get_disk_devices():
            if (disk.path and (disk.type == "block" or disk.type == "file")
                    and not disk.device == "cdrom"):
                disks.append(disk)

        if not disks:
            logging.debug("%s: nothing to inspect", prettyvm)
            return

        # Add the disks.  Note they *must* be added with readonly flag set.
        for disk in disks:
            path = disk.path
            driver_type = disk.driver_type

            if not (os.path.exists(path) and os.access(path, os.R_OK)):
                logging.debug("%s: cannot access '%s', skipping inspection",
                              prettyvm, path)
                return

            g.add_drive_opts(path, readonly=1, format=driver_type)

        g.launch()

        # Inspect the operating system.
        roots = g.inspect_os()
        if len(roots) == 0:
            logging.debug("%s: no operating systems found", prettyvm)
            return

        # Arbitrarily pick the first root device.
        root = roots[0]

        # Inspection results.
        typ = g.inspect_get_type(root)  # eg. "linux"
        distro = g.inspect_get_distro(root)  # eg. "fedora"
        major_version = g.inspect_get_major_version(root)  # eg. 14
        minor_version = g.inspect_get_minor_version(root)  # eg. 0
        hostname = g.inspect_get_hostname(root)  # string
        product_name = g.inspect_get_product_name(root)  # string
        product_variant = g.inspect_get_product_variant(root)  # string

        # For inspect_list_applications and inspect_get_icon we
        # require that the guest filesystems are mounted.  However
        # don't fail if this is not possible (I'm looking at you,
        # FreeBSD).
        filesystems_mounted = False
        try:
            # Mount up the disks, like guestfish --ro -i.

            # Sort keys by length, shortest first, so that we end up
            # mounting the filesystems in the correct order.
            mps = list(g.inspect_get_mountpoints(root))

            def compare(a, b):
                if len(a[0]) > len(b[0]):
                    return 1
                elif len(a[0]) == len(b[0]):
                    return 0
                else:
                    return -1

            mps.sort(compare)

            for mp_dev in mps:
                try:
                    g.mount_ro(mp_dev[1], mp_dev[0])
                except:
                    logging.exception(
                        "%s: exception mounting %s on %s "
                        "(ignored)", prettyvm, mp_dev[1], mp_dev[0])

            filesystems_mounted = True
        except:
            logging.exception("%s: exception while mounting disks (ignored)",
                              prettyvm)

        icon = None
        apps = None
        if filesystems_mounted:
            # string containing PNG data
            icon = g.inspect_get_icon(root, favicon=0, highquality=1)
            if icon == "":
                icon = None

            # Inspection applications.
            apps = g.inspect_list_applications(root)

        # Force the libguestfs handle to close right now.
        del g

        # Log what we found.
        logging.debug("%s: detected operating system: %s %s %d.%d (%s)",
                      prettyvm, typ, distro, major_version, minor_version,
                      product_name)
        logging.debug("hostname: %s", hostname)
        if icon:
            logging.debug("icon: %d bytes", len(icon))
        if apps:
            logging.debug("# apps: %d", len(apps))

        data = vmmInspectionData()
        data.type = str(type)
        data.distro = str(distro)
        data.major_version = int(major_version)
        data.minor_version = int(minor_version)
        data.hostname = str(hostname)
        data.product_name = str(product_name)
        data.product_variant = str(product_variant)
        data.icon = icon
        data.applications = list(apps)

        self._set_vm_inspection_data(vm, data)
Beispiel #29
0
    def _process(self, conn, vm):
        if re.search(r"^guestfs-", vm.get_name()):
            logging.debug("ignore libvirt/guestfs temporary VM %s",
                          vm.get_name())
            return None

        g = GuestFS(close_on_exit=False)
        prettyvm = conn.get_uri() + ":" + vm.get_name()

        g.add_libvirt_dom(vm.get_backend(), readonly=1)

        g.launch()

        # Inspect the operating system.
        roots = g.inspect_os()
        if len(roots) == 0:
            logging.debug("%s: no operating systems found", prettyvm)
            return None

        # Arbitrarily pick the first root device.
        root = roots[0]

        # Inspection results.
        typ = g.inspect_get_type(root)  # eg. "linux"
        distro = g.inspect_get_distro(root)  # eg. "fedora"
        major_version = g.inspect_get_major_version(root)  # eg. 14
        minor_version = g.inspect_get_minor_version(root)  # eg. 0
        hostname = g.inspect_get_hostname(root)  # string
        product_name = g.inspect_get_product_name(root)  # string
        product_variant = g.inspect_get_product_variant(root)  # string

        # For inspect_list_applications and inspect_get_icon we
        # require that the guest filesystems are mounted.  However
        # don't fail if this is not possible (I'm looking at you,
        # FreeBSD).
        filesystems_mounted = False
        try:
            # Mount up the disks, like guestfish --ro -i.

            # Sort keys by length, shortest first, so that we end up
            # mounting the filesystems in the correct order.
            mps = list(g.inspect_get_mountpoints(root))
            def compare(a, b):
                if len(a[0]) > len(b[0]):
                    return 1
                elif len(a[0]) == len(b[0]):
                    return 0
                else:
                    return -1
            mps.sort(compare)

            for mp_dev in mps:
                try:
                    g.mount_ro(mp_dev[1], mp_dev[0])
                except:
                    logging.exception("%s: exception mounting %s on %s "
                                      "(ignored)",
                                      prettyvm, mp_dev[1], mp_dev[0])

            filesystems_mounted = True
        except:
            logging.exception("%s: exception while mounting disks (ignored)",
                              prettyvm)

        icon = None
        apps = None
        if filesystems_mounted:
            # string containing PNG data
            icon = g.inspect_get_icon(root, favicon=0, highquality=1)
            if icon == "":
                icon = None

            # Inspection applications.
            apps = g.inspect_list_applications(root)

        # Force the libguestfs handle to close right now.
        del g

        # Log what we found.
        logging.debug("%s: detected operating system: %s %s %d.%d (%s)",
                      prettyvm, typ, distro, major_version, minor_version,
                      product_name)
        logging.debug("hostname: %s", hostname)
        if icon:
            logging.debug("icon: %d bytes", len(icon))
        if apps:
            logging.debug("# apps: %d", len(apps))

        data = vmmInspectionData()
        data.type = str(type)
        data.distro = str(distro)
        data.major_version = int(major_version)
        data.minor_version = int(minor_version)
        data.hostname = str(hostname)
        data.product_name = str(product_name)
        data.product_variant = str(product_variant)
        data.icon = icon
        data.applications = list(apps)
        data.error = False

        return data