sailesh1993 / rpms / cloud-init

Forked from rpms/cloud-init a year ago
Clone
42024e
From 078f3a218394eef3b28a2a061d836efe42b6c9ed Mon Sep 17 00:00:00 2001
42024e
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
42024e
Date: Fri, 14 Jan 2022 16:49:28 +0100
42024e
Subject: [PATCH 1/5] Datasource for VMware (#953)
42024e
42024e
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
42024e
RH-MergeRequest: 17: Datasource for VMware
42024e
RH-Commit: [1/5] 7b47334ec524dcf1b8edd02b65df7d0ff5a366e0 (eesposit/cloud-init-centos-)
42024e
RH-Bugzilla: 2040090
42024e
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
42024e
RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
42024e
42024e
commit 8b4a9bc7b81e61943af873bad92e2133f8275b0b
42024e
Author: Andrew Kutz <101085+akutz@users.noreply.github.com>
42024e
Date:   Mon Aug 9 21:24:07 2021 -0500
42024e
42024e
    Datasource for VMware (#953)
42024e
42024e
    This patch finally introduces the Cloud-Init Datasource for VMware
42024e
    GuestInfo as a part of cloud-init proper. This datasource has existed
42024e
    since 2018, and rapidly became the de facto datasource for developers
42024e
    working with Packer, Terraform, for projects like kube-image-builder,
42024e
    and the de jure datasource for Photon OS.
42024e
42024e
    The major change to the datasource from its previous incarnation is
42024e
    the name. Now named DatasourceVMware, this new version of the
42024e
    datasource will allow multiple transport types in addition to
42024e
    GuestInfo keys.
42024e
42024e
    This datasource includes several unique features developed to address
42024e
    real-world situations:
42024e
42024e
      * Support for reading any key (metadata, userdata, vendordata) both
42024e
        from the guestinfo table when running on a VM in vSphere as well as
42024e
        from an environment variable when running inside of a container,
42024e
        useful for rapid dev/test.
42024e
42024e
      * Allows booting with DHCP while still providing full participation
42024e
        in Cloud-Init instance data and Jinja queries. The netifaces library
42024e
        provides the ability to inspect the network after it is online,
42024e
        and the runtime network configuration is then merged into the
42024e
        existing metadata and persisted to disk.
42024e
42024e
      * Advertises the local_ipv4 and local_ipv6 addresses via guestinfo
42024e
        as well. This is useful as Guest Tools is not always able to
42024e
        identify what would be considered the local address.
42024e
42024e
    The primary author and current steward of this datasource spoke at
42024e
    Cloud-Init Con 2020 where there was interest in contributing this datasource
42024e
    to the Cloud-Init codebase.
42024e
42024e
    The datasource currently lives in its own GitHub repository at
42024e
    https://github.com/vmware/cloud-init-vmware-guestinfo. Once the datasource
42024e
    is merged into Cloud-Init, the old repository will be deprecated.
42024e
42024e
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
42024e
---
42024e
 README.md                                     |   2 +-
42024e
 cloudinit/settings.py                         |   1 +
42024e
 cloudinit/sources/DataSourceVMware.py         | 871 ++++++++++++++++++
42024e
 doc/rtd/topics/availability.rst               |   1 +
42024e
 doc/rtd/topics/datasources.rst                |   2 +-
42024e
 doc/rtd/topics/datasources/vmware.rst         | 359 ++++++++
42024e
 requirements.txt                              |  12 +
42024e
 .../unittests/test_datasource/test_common.py  |   3 +
42024e
 .../unittests/test_datasource/test_vmware.py  | 377 ++++++++
42024e
 tests/unittests/test_ds_identify.py           | 279 +++++-
42024e
 tools/.github-cla-signers                     |   1 +
42024e
 tools/ds-identify                             |  76 +-
42024e
 12 files changed, 1980 insertions(+), 4 deletions(-)
42024e
 create mode 100644 cloudinit/sources/DataSourceVMware.py
42024e
 create mode 100644 doc/rtd/topics/datasources/vmware.rst
42024e
 create mode 100644 tests/unittests/test_datasource/test_vmware.py
42024e
42024e
diff --git a/README.md b/README.md
42024e
index 435405da..aa4fad63 100644
42024e
--- a/README.md
42024e
+++ b/README.md
42024e
@@ -39,7 +39,7 @@ get in contact with that distribution and send them our way!
42024e
 
42024e
 | Supported OSes | Supported Public Clouds | Supported Private Clouds |
42024e
 | --- | --- | --- |
42024e
-| Alpine Linux
ArchLinux
Debian
Fedora
FreeBSD
Gentoo Linux
NetBSD
OpenBSD
RHEL/CentOS
SLES/openSUSE
Ubuntu










| Amazon Web Services
Microsoft Azure
Google Cloud Platform
Oracle Cloud Infrastructure
Softlayer
Rackspace Public Cloud
IBM Cloud
Digital Ocean
Bigstep
Hetzner
Joyent
CloudSigma
Alibaba Cloud
OVH
OpenNebula
Exoscale
Scaleway
CloudStack
AltCloud
SmartOS
HyperOne
Rootbox
| Bare metal installs
OpenStack
LXD
KVM
Metal-as-a-Service (MAAS)















|
42024e
+| Alpine Linux
ArchLinux
Debian
Fedora
FreeBSD
Gentoo Linux
NetBSD
OpenBSD
RHEL/CentOS
SLES/openSUSE
Ubuntu










| Amazon Web Services
Microsoft Azure
Google Cloud Platform
Oracle Cloud Infrastructure
Softlayer
Rackspace Public Cloud
IBM Cloud
Digital Ocean
Bigstep
Hetzner
Joyent
CloudSigma
Alibaba Cloud
OVH
OpenNebula
Exoscale
Scaleway
CloudStack
AltCloud
SmartOS
HyperOne
Rootbox
| Bare metal installs
OpenStack
LXD
KVM
Metal-as-a-Service (MAAS)
VMware















|
42024e
 
42024e
 ## To start developing cloud-init
42024e
 
42024e
diff --git a/cloudinit/settings.py b/cloudinit/settings.py
42024e
index 2acf2615..d5f32dbb 100644
42024e
--- a/cloudinit/settings.py
42024e
+++ b/cloudinit/settings.py
42024e
@@ -42,6 +42,7 @@ CFG_BUILTIN = {
42024e
         'Exoscale',
42024e
         'RbxCloud',
42024e
         'UpCloud',
42024e
+        'VMware',
42024e
         # At the end to act as a 'catch' when none of the above work...
42024e
         'None',
42024e
     ],
42024e
diff --git a/cloudinit/sources/DataSourceVMware.py b/cloudinit/sources/DataSourceVMware.py
42024e
new file mode 100644
42024e
index 00000000..22ca63de
42024e
--- /dev/null
42024e
+++ b/cloudinit/sources/DataSourceVMware.py
42024e
@@ -0,0 +1,871 @@
42024e
+# Cloud-Init DataSource for VMware
42024e
+#
42024e
+# Copyright (c) 2018-2021 VMware, Inc. All Rights Reserved.
42024e
+#
42024e
+# Authors: Anish Swaminathan <anishs@vmware.com>
42024e
+#          Andrew Kutz <akutz@vmware.com>
42024e
+#
42024e
+# This file is part of cloud-init. See LICENSE file for license information.
42024e
+
42024e
+"""Cloud-Init DataSource for VMware
42024e
+
42024e
+This module provides a cloud-init datasource for VMware systems and supports
42024e
+multiple transports types, including:
42024e
+
42024e
+    * EnvVars
42024e
+    * GuestInfo
42024e
+
42024e
+Netifaces (https://github.com/al45tair/netifaces)
42024e
+
42024e
+    Please note this module relies on the netifaces project to introspect the
42024e
+    runtime, network configuration of the host on which this datasource is
42024e
+    running. This is in contrast to the rest of cloud-init which uses the
42024e
+    cloudinit/netinfo module.
42024e
+
42024e
+    The reasons for using netifaces include:
42024e
+
42024e
+        * Netifaces is built in C and is more portable across multiple systems
42024e
+          and more deterministic than shell exec'ing local network commands and
42024e
+          parsing their output.
42024e
+
42024e
+        * Netifaces provides a stable way to determine the view of the host's
42024e
+          network after DHCP has brought the network online. Unlike most other
42024e
+          datasources, this datasource still provides support for JINJA queries
42024e
+          based on networking information even when the network is based on a
42024e
+          DHCP lease. While this does not tie this datasource directly to
42024e
+          netifaces, it does mean the ability to consistently obtain the
42024e
+          correct information is paramount.
42024e
+
42024e
+        * It is currently possible to execute this datasource on macOS
42024e
+          (which many developers use today) to print the output of the
42024e
+          get_host_info function. This function calls netifaces to obtain
42024e
+          the same runtime network configuration that the datasource would
42024e
+          persist to the local system's instance data.
42024e
+
42024e
+          However, the netinfo module fails on macOS. The result is either a
42024e
+          hung operation that requires a SIGINT to return control to the user,
42024e
+          or, if brew is used to install iproute2mac, the ip commands are used
42024e
+          but produce output the netinfo module is unable to parse.
42024e
+
42024e
+          While macOS is not a target of cloud-init, this feature is quite
42024e
+          useful when working on this datasource.
42024e
+
42024e
+          For more information about this behavior, please see the following
42024e
+          PR comment, https://bit.ly/3fG7OVh.
42024e
+
42024e
+    The authors of this datasource are not opposed to moving away from
42024e
+    netifaces. The goal may be to eventually do just that. This proviso was
42024e
+    added to the top of this module as a way to remind future-us and others
42024e
+    why netifaces was used in the first place in order to either smooth the
42024e
+    transition away from netifaces or embrace it further up the cloud-init
42024e
+    stack.
42024e
+"""
42024e
+
42024e
+import collections
42024e
+import copy
42024e
+from distutils.spawn import find_executable
42024e
+import ipaddress
42024e
+import json
42024e
+import os
42024e
+import socket
42024e
+import time
42024e
+
42024e
+from cloudinit import dmi, log as logging
42024e
+from cloudinit import sources
42024e
+from cloudinit import util
42024e
+from cloudinit.subp import subp, ProcessExecutionError
42024e
+
42024e
+import netifaces
42024e
+
42024e
+
42024e
+PRODUCT_UUID_FILE_PATH = "/sys/class/dmi/id/product_uuid"
42024e
+
42024e
+LOG = logging.getLogger(__name__)
42024e
+NOVAL = "No value found"
42024e
+
42024e
+DATA_ACCESS_METHOD_ENVVAR = "envvar"
42024e
+DATA_ACCESS_METHOD_GUESTINFO = "guestinfo"
42024e
+
42024e
+VMWARE_RPCTOOL = find_executable("vmware-rpctool")
42024e
+REDACT = "redact"
42024e
+CLEANUP_GUESTINFO = "cleanup-guestinfo"
42024e
+VMX_GUESTINFO = "VMX_GUESTINFO"
42024e
+GUESTINFO_EMPTY_YAML_VAL = "---"
42024e
+
42024e
+LOCAL_IPV4 = "local-ipv4"
42024e
+LOCAL_IPV6 = "local-ipv6"
42024e
+WAIT_ON_NETWORK = "wait-on-network"
42024e
+WAIT_ON_NETWORK_IPV4 = "ipv4"
42024e
+WAIT_ON_NETWORK_IPV6 = "ipv6"
42024e
+
42024e
+
42024e
+class DataSourceVMware(sources.DataSource):
42024e
+    """
42024e
+    Setting the hostname:
42024e
+        The hostname is set by way of the metadata key "local-hostname".
42024e
+
42024e
+    Setting the instance ID:
42024e
+        The instance ID may be set by way of the metadata key "instance-id".
42024e
+        However, if this value is absent then the instance ID is read
42024e
+        from the file /sys/class/dmi/id/product_uuid.
42024e
+
42024e
+    Configuring the network:
42024e
+        The network is configured by setting the metadata key "network"
42024e
+        with a value consistent with Network Config Versions 1 or 2,
42024e
+        depending on the Linux distro's version of cloud-init:
42024e
+
42024e
+            Network Config Version 1 - http://bit.ly/cloudinit-net-conf-v1
42024e
+            Network Config Version 2 - http://bit.ly/cloudinit-net-conf-v2
42024e
+
42024e
+        For example, CentOS 7's official cloud-init package is version
42024e
+        0.7.9 and does not support Network Config Version 2. However,
42024e
+        this datasource still supports supplying Network Config Version 2
42024e
+        data as long as the Linux distro's cloud-init package is new
42024e
+        enough to parse the data.
42024e
+
42024e
+        The metadata key "network.encoding" may be used to indicate the
42024e
+        format of the metadata key "network". Valid encodings are base64
42024e
+        and gzip+base64.
42024e
+    """
42024e
+
42024e
+    dsname = "VMware"
42024e
+
42024e
+    def __init__(self, sys_cfg, distro, paths, ud_proc=None):
42024e
+        sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc)
42024e
+
42024e
+        self.data_access_method = None
42024e
+        self.vmware_rpctool = VMWARE_RPCTOOL
42024e
+
42024e
+    def _get_data(self):
42024e
+        """
42024e
+        _get_data loads the metadata, userdata, and vendordata from one of
42024e
+        the following locations in the given order:
42024e
+
42024e
+            * envvars
42024e
+            * guestinfo
42024e
+
42024e
+        Please note when updating this function with support for new data
42024e
+        transports, the order should match the order in the dscheck_VMware
42024e
+        function from the file ds-identify.
42024e
+        """
42024e
+
42024e
+        # Initialize the locally scoped metadata, userdata, and vendordata
42024e
+        # variables. They are assigned below depending on the detected data
42024e
+        # access method.
42024e
+        md, ud, vd = None, None, None
42024e
+
42024e
+        # First check to see if there is data via env vars.
42024e
+        if os.environ.get(VMX_GUESTINFO, ""):
42024e
+            md = guestinfo_envvar("metadata")
42024e
+            ud = guestinfo_envvar("userdata")
42024e
+            vd = guestinfo_envvar("vendordata")
42024e
+
42024e
+            if md or ud or vd:
42024e
+                self.data_access_method = DATA_ACCESS_METHOD_ENVVAR
42024e
+
42024e
+        # At this point, all additional data transports are valid only on
42024e
+        # a VMware platform.
42024e
+        if not self.data_access_method:
42024e
+            system_type = dmi.read_dmi_data("system-product-name")
42024e
+            if system_type is None:
42024e
+                LOG.debug("No system-product-name found")
42024e
+                return False
42024e
+            if "vmware" not in system_type.lower():
42024e
+                LOG.debug("Not a VMware platform")
42024e
+                return False
42024e
+
42024e
+        # If no data was detected, check the guestinfo transport next.
42024e
+        if not self.data_access_method:
42024e
+            if self.vmware_rpctool:
42024e
+                md = guestinfo("metadata", self.vmware_rpctool)
42024e
+                ud = guestinfo("userdata", self.vmware_rpctool)
42024e
+                vd = guestinfo("vendordata", self.vmware_rpctool)
42024e
+
42024e
+                if md or ud or vd:
42024e
+                    self.data_access_method = DATA_ACCESS_METHOD_GUESTINFO
42024e
+
42024e
+        if not self.data_access_method:
42024e
+            LOG.error("failed to find a valid data access method")
42024e
+            return False
42024e
+
42024e
+        LOG.info("using data access method %s", self._get_subplatform())
42024e
+
42024e
+        # Get the metadata.
42024e
+        self.metadata = process_metadata(load_json_or_yaml(md))
42024e
+
42024e
+        # Get the user data.
42024e
+        self.userdata_raw = ud
42024e
+
42024e
+        # Get the vendor data.
42024e
+        self.vendordata_raw = vd
42024e
+
42024e
+        # Redact any sensitive information.
42024e
+        self.redact_keys()
42024e
+
42024e
+        # get_data returns true if there is any available metadata,
42024e
+        # userdata, or vendordata.
42024e
+        if self.metadata or self.userdata_raw or self.vendordata_raw:
42024e
+            return True
42024e
+        else:
42024e
+            return False
42024e
+
42024e
+    def setup(self, is_new_instance):
42024e
+        """setup(is_new_instance)
42024e
+
42024e
+        This is called before user-data and vendor-data have been processed.
42024e
+
42024e
+        Unless the datasource has set mode to 'local', then networking
42024e
+        per 'fallback' or per 'network_config' will have been written and
42024e
+        brought up the OS at this point.
42024e
+        """
42024e
+
42024e
+        host_info = wait_on_network(self.metadata)
42024e
+        LOG.info("got host-info: %s", host_info)
42024e
+
42024e
+        # Reflect any possible local IPv4 or IPv6 addresses in the guest
42024e
+        # info.
42024e
+        advertise_local_ip_addrs(host_info)
42024e
+
42024e
+        # Ensure the metadata gets updated with information about the
42024e
+        # host, including the network interfaces, default IP addresses,
42024e
+        # etc.
42024e
+        self.metadata = util.mergemanydict([self.metadata, host_info])
42024e
+
42024e
+        # Persist the instance data for versions of cloud-init that support
42024e
+        # doing so. This occurs here rather than in the get_data call in
42024e
+        # order to ensure that the network interfaces are up and can be
42024e
+        # persisted with the metadata.
42024e
+        self.persist_instance_data()
42024e
+
42024e
+    def _get_subplatform(self):
42024e
+        get_key_name_fn = None
42024e
+        if self.data_access_method == DATA_ACCESS_METHOD_ENVVAR:
42024e
+            get_key_name_fn = get_guestinfo_envvar_key_name
42024e
+        elif self.data_access_method == DATA_ACCESS_METHOD_GUESTINFO:
42024e
+            get_key_name_fn = get_guestinfo_key_name
42024e
+        else:
42024e
+            return sources.METADATA_UNKNOWN
42024e
+
42024e
+        return "%s (%s)" % (
42024e
+            self.data_access_method,
42024e
+            get_key_name_fn("metadata"),
42024e
+        )
42024e
+
42024e
+    @property
42024e
+    def network_config(self):
42024e
+        if "network" in self.metadata:
42024e
+            LOG.debug("using metadata network config")
42024e
+        else:
42024e
+            LOG.debug("using fallback network config")
42024e
+            self.metadata["network"] = {
42024e
+                "config": self.distro.generate_fallback_config(),
42024e
+            }
42024e
+        return self.metadata["network"]["config"]
42024e
+
42024e
+    def get_instance_id(self):
42024e
+        # Pull the instance ID out of the metadata if present. Otherwise
42024e
+        # read the file /sys/class/dmi/id/product_uuid for the instance ID.
42024e
+        if self.metadata and "instance-id" in self.metadata:
42024e
+            return self.metadata["instance-id"]
42024e
+        with open(PRODUCT_UUID_FILE_PATH, "r") as id_file:
42024e
+            self.metadata["instance-id"] = str(id_file.read()).rstrip().lower()
42024e
+            return self.metadata["instance-id"]
42024e
+
42024e
+    def get_public_ssh_keys(self):
42024e
+        for key_name in (
42024e
+            "public-keys-data",
42024e
+            "public_keys_data",
42024e
+            "public-keys",
42024e
+            "public_keys",
42024e
+        ):
42024e
+            if key_name in self.metadata:
42024e
+                return sources.normalize_pubkey_data(self.metadata[key_name])
42024e
+        return []
42024e
+
42024e
+    def redact_keys(self):
42024e
+        # Determine if there are any keys to redact.
42024e
+        keys_to_redact = None
42024e
+        if REDACT in self.metadata:
42024e
+            keys_to_redact = self.metadata[REDACT]
42024e
+        elif CLEANUP_GUESTINFO in self.metadata:
42024e
+            # This is for backwards compatibility.
42024e
+            keys_to_redact = self.metadata[CLEANUP_GUESTINFO]
42024e
+
42024e
+        if self.data_access_method == DATA_ACCESS_METHOD_GUESTINFO:
42024e
+            guestinfo_redact_keys(keys_to_redact, self.vmware_rpctool)
42024e
+
42024e
+
42024e
+def decode(key, enc_type, data):
42024e
+    """
42024e
+    decode returns the decoded string value of data
42024e
+    key is a string used to identify the data being decoded in log messages
42024e
+    """
42024e
+    LOG.debug("Getting encoded data for key=%s, enc=%s", key, enc_type)
42024e
+
42024e
+    raw_data = None
42024e
+    if enc_type in ["gzip+base64", "gz+b64"]:
42024e
+        LOG.debug("Decoding %s format %s", enc_type, key)
42024e
+        raw_data = util.decomp_gzip(util.b64d(data))
42024e
+    elif enc_type in ["base64", "b64"]:
42024e
+        LOG.debug("Decoding %s format %s", enc_type, key)
42024e
+        raw_data = util.b64d(data)
42024e
+    else:
42024e
+        LOG.debug("Plain-text data %s", key)
42024e
+        raw_data = data
42024e
+
42024e
+    return util.decode_binary(raw_data)
42024e
+
42024e
+
42024e
+def get_none_if_empty_val(val):
42024e
+    """
42024e
+    get_none_if_empty_val returns None if the provided value, once stripped
42024e
+    of its trailing whitespace, is empty or equal to GUESTINFO_EMPTY_YAML_VAL.
42024e
+
42024e
+    The return value is always a string, regardless of whether the input is
42024e
+    a bytes class or a string.
42024e
+    """
42024e
+
42024e
+    # If the provided value is a bytes class, convert it to a string to
42024e
+    # simplify the rest of this function's logic.
42024e
+    val = util.decode_binary(val)
42024e
+    val = val.rstrip()
42024e
+    if len(val) == 0 or val == GUESTINFO_EMPTY_YAML_VAL:
42024e
+        return None
42024e
+    return val
42024e
+
42024e
+
42024e
+def advertise_local_ip_addrs(host_info):
42024e
+    """
42024e
+    advertise_local_ip_addrs gets the local IP address information from
42024e
+    the provided host_info map and sets the addresses in the guestinfo
42024e
+    namespace
42024e
+    """
42024e
+    if not host_info:
42024e
+        return
42024e
+
42024e
+    # Reflect any possible local IPv4 or IPv6 addresses in the guest
42024e
+    # info.
42024e
+    local_ipv4 = host_info.get(LOCAL_IPV4)
42024e
+    if local_ipv4:
42024e
+        guestinfo_set_value(LOCAL_IPV4, local_ipv4)
42024e
+        LOG.info("advertised local ipv4 address %s in guestinfo", local_ipv4)
42024e
+
42024e
+    local_ipv6 = host_info.get(LOCAL_IPV6)
42024e
+    if local_ipv6:
42024e
+        guestinfo_set_value(LOCAL_IPV6, local_ipv6)
42024e
+        LOG.info("advertised local ipv6 address %s in guestinfo", local_ipv6)
42024e
+
42024e
+
42024e
+def handle_returned_guestinfo_val(key, val):
42024e
+    """
42024e
+    handle_returned_guestinfo_val returns the provided value if it is
42024e
+    not empty or set to GUESTINFO_EMPTY_YAML_VAL, otherwise None is
42024e
+    returned
42024e
+    """
42024e
+    val = get_none_if_empty_val(val)
42024e
+    if val:
42024e
+        return val
42024e
+    LOG.debug("No value found for key %s", key)
42024e
+    return None
42024e
+
42024e
+
42024e
+def get_guestinfo_key_name(key):
42024e
+    return "guestinfo." + key
42024e
+
42024e
+
42024e
+def get_guestinfo_envvar_key_name(key):
42024e
+    return ("vmx." + get_guestinfo_key_name(key)).upper().replace(".", "_", -1)
42024e
+
42024e
+
42024e
+def guestinfo_envvar(key):
42024e
+    val = guestinfo_envvar_get_value(key)
42024e
+    if not val:
42024e
+        return None
42024e
+    enc_type = guestinfo_envvar_get_value(key + ".encoding")
42024e
+    return decode(get_guestinfo_envvar_key_name(key), enc_type, val)
42024e
+
42024e
+
42024e
+def guestinfo_envvar_get_value(key):
42024e
+    env_key = get_guestinfo_envvar_key_name(key)
42024e
+    return handle_returned_guestinfo_val(key, os.environ.get(env_key, ""))
42024e
+
42024e
+
42024e
+def guestinfo(key, vmware_rpctool=VMWARE_RPCTOOL):
42024e
+    """
42024e
+    guestinfo returns the guestinfo value for the provided key, decoding
42024e
+    the value when required
42024e
+    """
42024e
+    val = guestinfo_get_value(key, vmware_rpctool)
42024e
+    if not val:
42024e
+        return None
42024e
+    enc_type = guestinfo_get_value(key + ".encoding", vmware_rpctool)
42024e
+    return decode(get_guestinfo_key_name(key), enc_type, val)
42024e
+
42024e
+
42024e
+def guestinfo_get_value(key, vmware_rpctool=VMWARE_RPCTOOL):
42024e
+    """
42024e
+    Returns a guestinfo value for the specified key.
42024e
+    """
42024e
+    LOG.debug("Getting guestinfo value for key %s", key)
42024e
+
42024e
+    try:
42024e
+        (stdout, stderr) = subp(
42024e
+            [
42024e
+                vmware_rpctool,
42024e
+                "info-get " + get_guestinfo_key_name(key),
42024e
+            ]
42024e
+        )
42024e
+        if stderr == NOVAL:
42024e
+            LOG.debug("No value found for key %s", key)
42024e
+        elif not stdout:
42024e
+            LOG.error("Failed to get guestinfo value for key %s", key)
42024e
+        return handle_returned_guestinfo_val(key, stdout)
42024e
+    except ProcessExecutionError as error:
42024e
+        if error.stderr == NOVAL:
42024e
+            LOG.debug("No value found for key %s", key)
42024e
+        else:
42024e
+            util.logexc(
42024e
+                LOG,
42024e
+                "Failed to get guestinfo value for key %s: %s",
42024e
+                key,
42024e
+                error,
42024e
+            )
42024e
+    except Exception:
42024e
+        util.logexc(
42024e
+            LOG,
42024e
+            "Unexpected error while trying to get "
42024e
+            + "guestinfo value for key %s",
42024e
+            key,
42024e
+        )
42024e
+
42024e
+    return None
42024e
+
42024e
+
42024e
+def guestinfo_set_value(key, value, vmware_rpctool=VMWARE_RPCTOOL):
42024e
+    """
42024e
+    Sets a guestinfo value for the specified key. Set value to an empty string
42024e
+    to clear an existing guestinfo key.
42024e
+    """
42024e
+
42024e
+    # If value is an empty string then set it to a single space as it is not
42024e
+    # possible to set a guestinfo key to an empty string. Setting a guestinfo
42024e
+    # key to a single space is as close as it gets to clearing an existing
42024e
+    # guestinfo key.
42024e
+    if value == "":
42024e
+        value = " "
42024e
+
42024e
+    LOG.debug("Setting guestinfo key=%s to value=%s", key, value)
42024e
+
42024e
+    try:
42024e
+        subp(
42024e
+            [
42024e
+                vmware_rpctool,
42024e
+                ("info-set %s %s" % (get_guestinfo_key_name(key), value)),
42024e
+            ]
42024e
+        )
42024e
+        return True
42024e
+    except ProcessExecutionError as error:
42024e
+        util.logexc(
42024e
+            LOG,
42024e
+            "Failed to set guestinfo key=%s to value=%s: %s",
42024e
+            key,
42024e
+            value,
42024e
+            error,
42024e
+        )
42024e
+    except Exception:
42024e
+        util.logexc(
42024e
+            LOG,
42024e
+            "Unexpected error while trying to set "
42024e
+            + "guestinfo key=%s to value=%s",
42024e
+            key,
42024e
+            value,
42024e
+        )
42024e
+
42024e
+    return None
42024e
+
42024e
+
42024e
+def guestinfo_redact_keys(keys, vmware_rpctool=VMWARE_RPCTOOL):
42024e
+    """
42024e
+    guestinfo_redact_keys redacts guestinfo of all of the keys in the given
42024e
+    list. each key will have its value set to "---". Since the value is valid
42024e
+    YAML, cloud-init can still read it if it tries.
42024e
+    """
42024e
+    if not keys:
42024e
+        return
42024e
+    if not type(keys) in (list, tuple):
42024e
+        keys = [keys]
42024e
+    for key in keys:
42024e
+        key_name = get_guestinfo_key_name(key)
42024e
+        LOG.info("clearing %s", key_name)
42024e
+        if not guestinfo_set_value(
42024e
+            key, GUESTINFO_EMPTY_YAML_VAL, vmware_rpctool
42024e
+        ):
42024e
+            LOG.error("failed to clear %s", key_name)
42024e
+        LOG.info("clearing %s.encoding", key_name)
42024e
+        if not guestinfo_set_value(key + ".encoding", "", vmware_rpctool):
42024e
+            LOG.error("failed to clear %s.encoding", key_name)
42024e
+
42024e
+
42024e
+def load_json_or_yaml(data):
42024e
+    """
42024e
+    load first attempts to unmarshal the provided data as JSON, and if
42024e
+    that fails then attempts to unmarshal the data as YAML. If data is
42024e
+    None then a new dictionary is returned.
42024e
+    """
42024e
+    if not data:
42024e
+        return {}
42024e
+    try:
42024e
+        return util.load_json(data)
42024e
+    except (json.JSONDecodeError, TypeError):
42024e
+        return util.load_yaml(data)
42024e
+
42024e
+
42024e
+def process_metadata(data):
42024e
+    """
42024e
+    process_metadata processes metadata and loads the optional network
42024e
+    configuration.
42024e
+    """
42024e
+    network = None
42024e
+    if "network" in data:
42024e
+        network = data["network"]
42024e
+        del data["network"]
42024e
+
42024e
+    network_enc = None
42024e
+    if "network.encoding" in data:
42024e
+        network_enc = data["network.encoding"]
42024e
+        del data["network.encoding"]
42024e
+
42024e
+    if network:
42024e
+        if isinstance(network, collections.abc.Mapping):
42024e
+            LOG.debug("network data copied to 'config' key")
42024e
+            network = {"config": copy.deepcopy(network)}
42024e
+        else:
42024e
+            LOG.debug("network data to be decoded %s", network)
42024e
+            dec_net = decode("metadata.network", network_enc, network)
42024e
+            network = {
42024e
+                "config": load_json_or_yaml(dec_net),
42024e
+            }
42024e
+
42024e
+        LOG.debug("network data %s", network)
42024e
+        data["network"] = network
42024e
+
42024e
+    return data
42024e
+
42024e
+
42024e
+# Used to match classes to dependencies
42024e
+datasources = [
42024e
+    (DataSourceVMware, (sources.DEP_FILESYSTEM,)),  # Run at init-local
42024e
+    (DataSourceVMware, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
42024e
+]
42024e
+
42024e
+
42024e
+def get_datasource_list(depends):
42024e
+    """
42024e
+    Return a list of data sources that match this set of dependencies
42024e
+    """
42024e
+    return sources.list_from_depends(depends, datasources)
42024e
+
42024e
+
42024e
+def get_default_ip_addrs():
42024e
+    """
42024e
+    Returns the default IPv4 and IPv6 addresses based on the device(s) used for
42024e
+    the default route. Please note that None may be returned for either address
42024e
+    family if that family has no default route or if there are multiple
42024e
+    addresses associated with the device used by the default route for a given
42024e
+    address.
42024e
+    """
42024e
+    # TODO(promote and use netifaces in cloudinit.net* modules)
42024e
+    gateways = netifaces.gateways()
42024e
+    if "default" not in gateways:
42024e
+        return None, None
42024e
+
42024e
+    default_gw = gateways["default"]
42024e
+    if (
42024e
+        netifaces.AF_INET not in default_gw
42024e
+        and netifaces.AF_INET6 not in default_gw
42024e
+    ):
42024e
+        return None, None
42024e
+
42024e
+    ipv4 = None
42024e
+    ipv6 = None
42024e
+
42024e
+    gw4 = default_gw.get(netifaces.AF_INET)
42024e
+    if gw4:
42024e
+        _, dev4 = gw4
42024e
+        addr4_fams = netifaces.ifaddresses(dev4)
42024e
+        if addr4_fams:
42024e
+            af_inet4 = addr4_fams.get(netifaces.AF_INET)
42024e
+            if af_inet4:
42024e
+                if len(af_inet4) > 1:
42024e
+                    LOG.warning(
42024e
+                        "device %s has more than one ipv4 address: %s",
42024e
+                        dev4,
42024e
+                        af_inet4,
42024e
+                    )
42024e
+                elif "addr" in af_inet4[0]:
42024e
+                    ipv4 = af_inet4[0]["addr"]
42024e
+
42024e
+    # Try to get the default IPv6 address by first seeing if there is a default
42024e
+    # IPv6 route.
42024e
+    gw6 = default_gw.get(netifaces.AF_INET6)
42024e
+    if gw6:
42024e
+        _, dev6 = gw6
42024e
+        addr6_fams = netifaces.ifaddresses(dev6)
42024e
+        if addr6_fams:
42024e
+            af_inet6 = addr6_fams.get(netifaces.AF_INET6)
42024e
+            if af_inet6:
42024e
+                if len(af_inet6) > 1:
42024e
+                    LOG.warning(
42024e
+                        "device %s has more than one ipv6 address: %s",
42024e
+                        dev6,
42024e
+                        af_inet6,
42024e
+                    )
42024e
+                elif "addr" in af_inet6[0]:
42024e
+                    ipv6 = af_inet6[0]["addr"]
42024e
+
42024e
+    # If there is a default IPv4 address but not IPv6, then see if there is a
42024e
+    # single IPv6 address associated with the same device associated with the
42024e
+    # default IPv4 address.
42024e
+    if ipv4 and not ipv6:
42024e
+        af_inet6 = addr4_fams.get(netifaces.AF_INET6)
42024e
+        if af_inet6:
42024e
+            if len(af_inet6) > 1:
42024e
+                LOG.warning(
42024e
+                    "device %s has more than one ipv6 address: %s",
42024e
+                    dev4,
42024e
+                    af_inet6,
42024e
+                )
42024e
+            elif "addr" in af_inet6[0]:
42024e
+                ipv6 = af_inet6[0]["addr"]
42024e
+
42024e
+    # If there is a default IPv6 address but not IPv4, then see if there is a
42024e
+    # single IPv4 address associated with the same device associated with the
42024e
+    # default IPv6 address.
42024e
+    if not ipv4 and ipv6:
42024e
+        af_inet4 = addr6_fams.get(netifaces.AF_INET)
42024e
+        if af_inet4:
42024e
+            if len(af_inet4) > 1:
42024e
+                LOG.warning(
42024e
+                    "device %s has more than one ipv4 address: %s",
42024e
+                    dev6,
42024e
+                    af_inet4,
42024e
+                )
42024e
+            elif "addr" in af_inet4[0]:
42024e
+                ipv4 = af_inet4[0]["addr"]
42024e
+
42024e
+    return ipv4, ipv6
42024e
+
42024e
+
42024e
+# patched socket.getfqdn() - see https://bugs.python.org/issue5004
42024e
+
42024e
+
42024e
+def getfqdn(name=""):
42024e
+    """Get fully qualified domain name from name.
42024e
+    An empty argument is interpreted as meaning the local host.
42024e
+    """
42024e
+    # TODO(may want to promote this function to util.getfqdn)
42024e
+    # TODO(may want to extend util.get_hostname to accept fqdn=True param)
42024e
+    name = name.strip()
42024e
+    if not name or name == "0.0.0.0":
42024e
+        name = util.get_hostname()
42024e
+    try:
42024e
+        addrs = socket.getaddrinfo(
42024e
+            name, None, 0, socket.SOCK_DGRAM, 0, socket.AI_CANONNAME
42024e
+        )
42024e
+    except socket.error:
42024e
+        pass
42024e
+    else:
42024e
+        for addr in addrs:
42024e
+            if addr[3]:
42024e
+                name = addr[3]
42024e
+                break
42024e
+    return name
42024e
+
42024e
+
42024e
+def is_valid_ip_addr(val):
42024e
+    """
42024e
+    Returns false if the address is loopback, link local or unspecified;
42024e
+    otherwise true is returned.
42024e
+    """
42024e
+    # TODO(extend cloudinit.net.is_ip_addr exclude link_local/loopback etc)
42024e
+    # TODO(migrate to use cloudinit.net.is_ip_addr)#
42024e
+
42024e
+    addr = None
42024e
+    try:
42024e
+        addr = ipaddress.ip_address(val)
42024e
+    except ipaddress.AddressValueError:
42024e
+        addr = ipaddress.ip_address(str(val))
42024e
+    except Exception:
42024e
+        return None
42024e
+
42024e
+    if addr.is_link_local or addr.is_loopback or addr.is_unspecified:
42024e
+        return False
42024e
+    return True
42024e
+
42024e
+
42024e
+def get_host_info():
42024e
+    """
42024e
+    Returns host information such as the host name and network interfaces.
42024e
+    """
42024e
+    # TODO(look to promote netifices use up in cloud-init netinfo funcs)
42024e
+    host_info = {
42024e
+        "network": {
42024e
+            "interfaces": {
42024e
+                "by-mac": collections.OrderedDict(),
42024e
+                "by-ipv4": collections.OrderedDict(),
42024e
+                "by-ipv6": collections.OrderedDict(),
42024e
+            },
42024e
+        },
42024e
+    }
42024e
+    hostname = getfqdn(util.get_hostname())
42024e
+    if hostname:
42024e
+        host_info["hostname"] = hostname
42024e
+        host_info["local-hostname"] = hostname
42024e
+        host_info["local_hostname"] = hostname
42024e
+
42024e
+    default_ipv4, default_ipv6 = get_default_ip_addrs()
42024e
+    if default_ipv4:
42024e
+        host_info[LOCAL_IPV4] = default_ipv4
42024e
+    if default_ipv6:
42024e
+        host_info[LOCAL_IPV6] = default_ipv6
42024e
+
42024e
+    by_mac = host_info["network"]["interfaces"]["by-mac"]
42024e
+    by_ipv4 = host_info["network"]["interfaces"]["by-ipv4"]
42024e
+    by_ipv6 = host_info["network"]["interfaces"]["by-ipv6"]
42024e
+
42024e
+    ifaces = netifaces.interfaces()
42024e
+    for dev_name in ifaces:
42024e
+        addr_fams = netifaces.ifaddresses(dev_name)
42024e
+        af_link = addr_fams.get(netifaces.AF_LINK)
42024e
+        af_inet4 = addr_fams.get(netifaces.AF_INET)
42024e
+        af_inet6 = addr_fams.get(netifaces.AF_INET6)
42024e
+
42024e
+        mac = None
42024e
+        if af_link and "addr" in af_link[0]:
42024e
+            mac = af_link[0]["addr"]
42024e
+
42024e
+        # Do not bother recording localhost
42024e
+        if mac == "00:00:00:00:00:00":
42024e
+            continue
42024e
+
42024e
+        if mac and (af_inet4 or af_inet6):
42024e
+            key = mac
42024e
+            val = {}
42024e
+            if af_inet4:
42024e
+                af_inet4_vals = []
42024e
+                for ip_info in af_inet4:
42024e
+                    if not is_valid_ip_addr(ip_info["addr"]):
42024e
+                        continue
42024e
+                    af_inet4_vals.append(ip_info)
42024e
+                val["ipv4"] = af_inet4_vals
42024e
+            if af_inet6:
42024e
+                af_inet6_vals = []
42024e
+                for ip_info in af_inet6:
42024e
+                    if not is_valid_ip_addr(ip_info["addr"]):
42024e
+                        continue
42024e
+                    af_inet6_vals.append(ip_info)
42024e
+                val["ipv6"] = af_inet6_vals
42024e
+            by_mac[key] = val
42024e
+
42024e
+        if af_inet4:
42024e
+            for ip_info in af_inet4:
42024e
+                key = ip_info["addr"]
42024e
+                if not is_valid_ip_addr(key):
42024e
+                    continue
42024e
+                val = copy.deepcopy(ip_info)
42024e
+                del val["addr"]
42024e
+                if mac:
42024e
+                    val["mac"] = mac
42024e
+                by_ipv4[key] = val
42024e
+
42024e
+        if af_inet6:
42024e
+            for ip_info in af_inet6:
42024e
+                key = ip_info["addr"]
42024e
+                if not is_valid_ip_addr(key):
42024e
+                    continue
42024e
+                val = copy.deepcopy(ip_info)
42024e
+                del val["addr"]
42024e
+                if mac:
42024e
+                    val["mac"] = mac
42024e
+                by_ipv6[key] = val
42024e
+
42024e
+    return host_info
42024e
+
42024e
+
42024e
+def wait_on_network(metadata):
42024e
+    # Determine whether we need to wait on the network coming online.
42024e
+    wait_on_ipv4 = False
42024e
+    wait_on_ipv6 = False
42024e
+    if WAIT_ON_NETWORK in metadata:
42024e
+        wait_on_network = metadata[WAIT_ON_NETWORK]
42024e
+        if WAIT_ON_NETWORK_IPV4 in wait_on_network:
42024e
+            wait_on_ipv4_val = wait_on_network[WAIT_ON_NETWORK_IPV4]
42024e
+            if isinstance(wait_on_ipv4_val, bool):
42024e
+                wait_on_ipv4 = wait_on_ipv4_val
42024e
+            else:
42024e
+                wait_on_ipv4 = util.translate_bool(wait_on_ipv4_val)
42024e
+        if WAIT_ON_NETWORK_IPV6 in wait_on_network:
42024e
+            wait_on_ipv6_val = wait_on_network[WAIT_ON_NETWORK_IPV6]
42024e
+            if isinstance(wait_on_ipv6_val, bool):
42024e
+                wait_on_ipv6 = wait_on_ipv6_val
42024e
+            else:
42024e
+                wait_on_ipv6 = util.translate_bool(wait_on_ipv6_val)
42024e
+
42024e
+    # Get information about the host.
42024e
+    host_info = None
42024e
+    while host_info is None:
42024e
+        # This loop + sleep results in two logs every second while waiting
42024e
+        # for either ipv4 or ipv6 up. Do we really need to log each iteration
42024e
+        # or can we log once and log on successful exit?
42024e
+        host_info = get_host_info()
42024e
+
42024e
+        network = host_info.get("network") or {}
42024e
+        interfaces = network.get("interfaces") or {}
42024e
+        by_ipv4 = interfaces.get("by-ipv4") or {}
42024e
+        by_ipv6 = interfaces.get("by-ipv6") or {}
42024e
+
42024e
+        if wait_on_ipv4:
42024e
+            ipv4_ready = len(by_ipv4) > 0 if by_ipv4 else False
42024e
+            if not ipv4_ready:
42024e
+                host_info = None
42024e
+
42024e
+        if wait_on_ipv6:
42024e
+            ipv6_ready = len(by_ipv6) > 0 if by_ipv6 else False
42024e
+            if not ipv6_ready:
42024e
+                host_info = None
42024e
+
42024e
+        if host_info is None:
42024e
+            LOG.debug(
42024e
+                "waiting on network: wait4=%s, ready4=%s, wait6=%s, ready6=%s",
42024e
+                wait_on_ipv4,
42024e
+                ipv4_ready,
42024e
+                wait_on_ipv6,
42024e
+                ipv6_ready,
42024e
+            )
42024e
+            time.sleep(1)
42024e
+
42024e
+    LOG.debug("waiting on network complete")
42024e
+    return host_info
42024e
+
42024e
+
42024e
+def main():
42024e
+    """
42024e
+    Executed when this file is used as a program.
42024e
+    """
42024e
+    try:
42024e
+        logging.setupBasicLogging()
42024e
+    except Exception:
42024e
+        pass
42024e
+    metadata = {
42024e
+        "wait-on-network": {"ipv4": True, "ipv6": "false"},
42024e
+        "network": {"config": {"dhcp": True}},
42024e
+    }
42024e
+    host_info = wait_on_network(metadata)
42024e
+    metadata = util.mergemanydict([metadata, host_info])
42024e
+    print(util.json_dumps(metadata))
42024e
+
42024e
+
42024e
+if __name__ == "__main__":
42024e
+    main()
42024e
+
42024e
+# vi: ts=4 expandtab
42024e
diff --git a/doc/rtd/topics/availability.rst b/doc/rtd/topics/availability.rst
42024e
index f58b2b38..6606367c 100644
42024e
--- a/doc/rtd/topics/availability.rst
42024e
+++ b/doc/rtd/topics/availability.rst
42024e
@@ -64,5 +64,6 @@ Additionally, cloud-init is supported on these private clouds:
42024e
 - LXD
42024e
 - KVM
42024e
 - Metal-as-a-Service (MAAS)
42024e
+- VMware
42024e
 
42024e
 .. vi: textwidth=79
42024e
diff --git a/doc/rtd/topics/datasources.rst b/doc/rtd/topics/datasources.rst
42024e
index 228173d2..8afed470 100644
42024e
--- a/doc/rtd/topics/datasources.rst
42024e
+++ b/doc/rtd/topics/datasources.rst
42024e
@@ -49,7 +49,7 @@ The following is a list of documents for each supported datasource:
42024e
    datasources/smartos.rst
42024e
    datasources/upcloud.rst
42024e
    datasources/zstack.rst
42024e
-
42024e
+   datasources/vmware.rst
42024e
 
42024e
 Creation
42024e
 ========
42024e
diff --git a/doc/rtd/topics/datasources/vmware.rst b/doc/rtd/topics/datasources/vmware.rst
42024e
new file mode 100644
42024e
index 00000000..996eb61f
42024e
--- /dev/null
42024e
+++ b/doc/rtd/topics/datasources/vmware.rst
42024e
@@ -0,0 +1,359 @@
42024e
+.. _datasource_vmware:
42024e
+
42024e
+VMware
42024e
+======
42024e
+
42024e
+This datasource is for use with systems running on a VMware platform such as
42024e
+vSphere and currently supports the following data transports:
42024e
+
42024e
+
42024e
+* `GuestInfo <https://github.com/vmware/govmomi/blob/master/govc/USAGE.md#vmchange>`_ keys
42024e
+
42024e
+Configuration
42024e
+-------------
42024e
+
42024e
+The configuration method is dependent upon the transport:
42024e
+
42024e
+GuestInfo Keys
42024e
+^^^^^^^^^^^^^^
42024e
+
42024e
+One method of providing meta, user, and vendor data is by setting the following
42024e
+key/value pairs on a VM's ``extraConfig`` `property <https://vdc-repo.vmware.com/vmwb-repository/dcr-public/723e7f8b-4f21-448b-a830-5f22fd931b01/5a8257bd-7f41-4423-9a73-03307535bd42/doc/vim.vm.ConfigInfo.html>`_ :
42024e
+
42024e
+.. list-table::
42024e
+   :header-rows: 1
42024e
+
42024e
+   * - Property
42024e
+     - Description
42024e
+   * - ``guestinfo.metadata``
42024e
+     - A YAML or JSON document containing the cloud-init metadata.
42024e
+   * - ``guestinfo.metadata.encoding``
42024e
+     - The encoding type for ``guestinfo.metadata``.
42024e
+   * - ``guestinfo.userdata``
42024e
+     - A YAML document containing the cloud-init user data.
42024e
+   * - ``guestinfo.userdata.encoding``
42024e
+     - The encoding type for ``guestinfo.userdata``.
42024e
+   * - ``guestinfo.vendordata``
42024e
+     - A YAML document containing the cloud-init vendor data.
42024e
+   * - ``guestinfo.vendordata.encoding``
42024e
+     - The encoding type for ``guestinfo.vendordata``.
42024e
+
42024e
+
42024e
+All ``guestinfo.*.encoding`` values may be set to ``base64`` or
42024e
+``gzip+base64``.
42024e
+
42024e
+Features
42024e
+--------
42024e
+
42024e
+This section reviews several features available in this datasource, regardless
42024e
+of how the meta, user, and vendor data was discovered.
42024e
+
42024e
+Instance data and lazy networks
42024e
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42024e
+
42024e
+One of the hallmarks of cloud-init is `its use of instance-data and JINJA
42024e
+queries <../instancedata.html#using-instance-data>`_
42024e
+-- the ability to write queries in user and vendor data that reference runtime
42024e
+information present in ``/run/cloud-init/instance-data.json``. This works well
42024e
+when the metadata provides all of the information up front, such as the network
42024e
+configuration. For systems that rely on DHCP, however, this information may not
42024e
+be available when the metadata is persisted to disk.
42024e
+
42024e
+This datasource ensures that even if the instance is using DHCP to configure
42024e
+networking, the same details about the configured network are available in
42024e
+``/run/cloud-init/instance-data.json`` as if static networking was used. This
42024e
+information collected at runtime is easy to demonstrate by executing the
42024e
+datasource on the command line. From the root of this repository, run the
42024e
+following command:
42024e
+
42024e
+.. code-block:: bash
42024e
+
42024e
+   PYTHONPATH="$(pwd)" python3 cloudinit/sources/DataSourceVMware.py
42024e
+
42024e
+The above command will result in output similar to the below JSON:
42024e
+
42024e
+.. code-block:: json
42024e
+
42024e
+   {
42024e
+       "hostname": "akutz.localhost",
42024e
+       "local-hostname": "akutz.localhost",
42024e
+       "local-ipv4": "192.168.0.188",
42024e
+       "local_hostname": "akutz.localhost",
42024e
+       "network": {
42024e
+           "config": {
42024e
+               "dhcp": true
42024e
+           },
42024e
+           "interfaces": {
42024e
+               "by-ipv4": {
42024e
+                   "172.0.0.2": {
42024e
+                       "netmask": "255.255.255.255",
42024e
+                       "peer": "172.0.0.2"
42024e
+                   },
42024e
+                   "192.168.0.188": {
42024e
+                       "broadcast": "192.168.0.255",
42024e
+                       "mac": "64:4b:f0:18:9a:21",
42024e
+                       "netmask": "255.255.255.0"
42024e
+                   }
42024e
+               },
42024e
+               "by-ipv6": {
42024e
+                   "fd8e:d25e:c5b6:1:1f5:b2fd:8973:22f2": {
42024e
+                       "flags": 208,
42024e
+                       "mac": "64:4b:f0:18:9a:21",
42024e
+                       "netmask": "ffff:ffff:ffff:ffff::/64"
42024e
+                   }
42024e
+               },
42024e
+               "by-mac": {
42024e
+                   "64:4b:f0:18:9a:21": {
42024e
+                       "ipv4": [
42024e
+                           {
42024e
+                               "addr": "192.168.0.188",
42024e
+                               "broadcast": "192.168.0.255",
42024e
+                               "netmask": "255.255.255.0"
42024e
+                           }
42024e
+                       ],
42024e
+                       "ipv6": [
42024e
+                           {
42024e
+                               "addr": "fd8e:d25e:c5b6:1:1f5:b2fd:8973:22f2",
42024e
+                               "flags": 208,
42024e
+                               "netmask": "ffff:ffff:ffff:ffff::/64"
42024e
+                           }
42024e
+                       ]
42024e
+                   },
42024e
+                   "ac:de:48:00:11:22": {
42024e
+                       "ipv6": []
42024e
+                   }
42024e
+               }
42024e
+           }
42024e
+       },
42024e
+       "wait-on-network": {
42024e
+           "ipv4": true,
42024e
+           "ipv6": "false"
42024e
+       }
42024e
+   }
42024e
+
42024e
+
42024e
+Redacting sensitive information
42024e
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42024e
+
42024e
+Sometimes the cloud-init userdata might contain sensitive information, and it
42024e
+may be desirable to have the ``guestinfo.userdata`` key (or other guestinfo
42024e
+keys) redacted as soon as its data is read by the datasource. This is possible
42024e
+by adding the following to the metadata:
42024e
+
42024e
+.. code-block:: yaml
42024e
+
42024e
+   redact: # formerly named cleanup-guestinfo, which will also work
42024e
+   - userdata
42024e
+   - vendordata
42024e
+
42024e
+When the above snippet is added to the metadata, the datasource will iterate
42024e
+over the elements in the ``redact`` array and clear each of the keys. For
42024e
+example, when the guestinfo transport is used, the above snippet will cause
42024e
+the following commands to be executed:
42024e
+
42024e
+.. code-block:: shell
42024e
+
42024e
+   vmware-rpctool "info-set guestinfo.userdata ---"
42024e
+   vmware-rpctool "info-set guestinfo.userdata.encoding  "
42024e
+   vmware-rpctool "info-set guestinfo.vendordata ---"
42024e
+   vmware-rpctool "info-set guestinfo.vendordata.encoding  "
42024e
+
42024e
+Please note that keys are set to the valid YAML string ``---`` as it is not
42024e
+possible remove an existing key from the guestinfo key-space. A key's analogous
42024e
+encoding property will be set to a single white-space character, causing the
42024e
+datasource to treat the actual key value as plain-text, thereby loading it as
42024e
+an empty YAML doc (hence the aforementioned ``---``\ ).
42024e
+
42024e
+Reading the local IP addresses
42024e
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42024e
+
42024e
+This datasource automatically discovers the local IPv4 and IPv6 addresses for
42024e
+a guest operating system based on the default routes. However, when inspecting
42024e
+a VM externally, it's not possible to know what the *default* IP address is for
42024e
+the guest OS. That's why this datasource sets the discovered, local IPv4 and
42024e
+IPv6 addresses back in the guestinfo namespace as the following keys:
42024e
+
42024e
+
42024e
+* ``guestinfo.local-ipv4``
42024e
+* ``guestinfo.local-ipv6``
42024e
+
42024e
+It is possible that a host may not have any default, local IP addresses. It's
42024e
+also possible the reported, local addresses are link-local addresses. But these
42024e
+two keys may be used to discover what this datasource determined were the local
42024e
+IPv4 and IPv6 addresses for a host.
42024e
+
42024e
+Waiting on the network
42024e
+^^^^^^^^^^^^^^^^^^^^^^
42024e
+
42024e
+Sometimes cloud-init may bring up the network, but it will not finish coming
42024e
+online before the datasource's ``setup`` function is called, resulting in an
42024e
+``/var/run/cloud-init/instance-data.json`` file that does not have the correct
42024e
+network information. It is possible to instruct the datasource to wait until an
42024e
+IPv4 or IPv6 address is available before writing the instance data with the
42024e
+following metadata properties:
42024e
+
42024e
+.. code-block:: yaml
42024e
+
42024e
+   wait-on-network:
42024e
+     ipv4: true
42024e
+     ipv6: true
42024e
+
42024e
+If either of the above values are true, then the datasource will sleep for a
42024e
+second, check the network status, and repeat until one or both addresses from
42024e
+the specified families are available.
42024e
+
42024e
+Walkthrough
42024e
+-----------
42024e
+
42024e
+The following series of steps is a demonstration on how to configure a VM with
42024e
+this datasource:
42024e
+
42024e
+
42024e
+#. Create the metadata file for the VM. Save the following YAML to a file named
42024e
+   ``metadata.yaml``\ :
42024e
+
42024e
+   .. code-block:: yaml
42024e
+
42024e
+       instance-id: cloud-vm
42024e
+       local-hostname: cloud-vm
42024e
+       network:
42024e
+         version: 2
42024e
+         ethernets:
42024e
+           nics:
42024e
+             match:
42024e
+               name: ens*
42024e
+             dhcp4: yes
42024e
+
42024e
+#. Create the userdata file ``userdata.yaml``\ :
42024e
+
42024e
+   .. code-block:: yaml
42024e
+
42024e
+       #cloud-config
42024e
+
42024e
+       users:
42024e
+       - default
42024e
+       - name: akutz
42024e
+           primary_group: akutz
42024e
+           sudo: ALL=(ALL) NOPASSWD:ALL
42024e
+           groups: sudo, wheel
42024e
+           ssh_import_id: None
42024e
+           lock_passwd: true
42024e
+           ssh_authorized_keys:
42024e
+           - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDE0c5FczvcGSh/tG4iw+Fhfi/O5/EvUM/96js65tly4++YTXK1d9jcznPS5ruDlbIZ30oveCBd3kT8LLVFwzh6hepYTf0YmCTpF4eDunyqmpCXDvVscQYRXyasEm5olGmVe05RrCJSeSShAeptv4ueIn40kZKOghinGWLDSZG4+FFfgrmcMCpx5YSCtX2gvnEYZJr0czt4rxOZuuP7PkJKgC/mt2PcPjooeX00vAj81jjU2f3XKrjjz2u2+KIt9eba+vOQ6HiC8c2IzRkUAJ5i1atLy8RIbejo23+0P4N2jjk17QySFOVHwPBDTYb0/0M/4ideeU74EN/CgVsvO6JrLsPBR4dojkV5qNbMNxIVv5cUwIy2ThlLgqpNCeFIDLCWNZEFKlEuNeSQ2mPtIO7ETxEL2Cz5y/7AIuildzYMc6wi2bofRC8HmQ7rMXRWdwLKWsR0L7SKjHblIwarxOGqLnUI+k2E71YoP7SZSlxaKi17pqkr0OMCF+kKqvcvHAQuwGqyumTEWOlH6TCx1dSPrW+pVCZSHSJtSTfDW2uzL6y8k10MT06+pVunSrWo5LHAXcS91htHV1M1UrH/tZKSpjYtjMb5+RonfhaFRNzvj7cCE1f3Kp8UVqAdcGBTtReoE8eRUT63qIxjw03a7VwAyB2w+9cu1R9/vAo8SBeRqw== sakutz@gmail.com
42024e
+
42024e
+#. Please note this step requires that the VM be powered off. All of the
42024e
+   commands below use the VMware CLI tool, `govc <https://github.com/vmware/govmomi/blob/master/govc>`_.
42024e
+
42024e
+   Go ahead and assign the path to the VM to the environment variable ``VM``\ :
42024e
+
42024e
+   .. code-block:: shell
42024e
+
42024e
+      export VM="/inventory/path/to/the/vm"
42024e
+
42024e
+#. Power off the VM:
42024e
+
42024e
+   .. raw:: html
42024e
+
42024e
+      
42024e
+
42024e
+      ⚠️ First Boot Mode
42024e
+
42024e
+   To ensure the next power-on operation results in a first-boot scenario for
42024e
+   cloud-init, it may be necessary to run the following command just before
42024e
+   powering off the VM:
42024e
+
42024e
+   .. code-block:: bash
42024e
+
42024e
+      cloud-init clean
42024e
+
42024e
+   Otherwise cloud-init may not run in first-boot mode. For more information
42024e
+   on how the boot mode is determined, please see the
42024e
+   `First Boot Documentation <../boot.html#first-boot-determination>`_.
42024e
+
42024e
+   .. raw:: html
42024e
+
42024e
+      
42024e
+
42024e
+   .. code-block:: shell
42024e
+
42024e
+      govc vm.power -off "${VM}"
42024e
+
42024e
+#.
42024e
+   Export the environment variables that contain the cloud-init metadata and
42024e
+   userdata:
42024e
+
42024e
+   .. code-block:: shell
42024e
+
42024e
+      export METADATA=$(gzip -c9 <metadata.yaml | { base64 -w0 2>/dev/null || base64; }) \
42024e
+           USERDATA=$(gzip -c9 <userdata.yaml | { base64 -w0 2>/dev/null || base64; })
42024e
+
42024e
+#.
42024e
+   Assign the metadata and userdata to the VM:
42024e
+
42024e
+   .. code-block:: shell
42024e
+
42024e
+       govc vm.change -vm "${VM}" \
42024e
+       -e guestinfo.metadata="${METADATA}" \
42024e
+       -e guestinfo.metadata.encoding="gzip+base64" \
42024e
+       -e guestinfo.userdata="${USERDATA}" \
42024e
+       -e guestinfo.userdata.encoding="gzip+base64"
42024e
+
42024e
+   Please note the above commands include specifying the encoding for the
42024e
+   properties. This is important as it informs the datasource how to decode
42024e
+   the data for cloud-init. Valid values for ``metadata.encoding`` and
42024e
+   ``userdata.encoding`` include:
42024e
+
42024e
+
42024e
+   * ``base64``
42024e
+   * ``gzip+base64``
42024e
+
42024e
+#.
42024e
+   Power on the VM:
42024e
+
42024e
+   .. code-block:: shell
42024e
+
42024e
+       govc vm.power -vm "${VM}" -on
42024e
+
42024e
+If all went according to plan, the CentOS box is:
42024e
+
42024e
+* Locked down, allowing SSH access only for the user in the userdata
42024e
+* Configured for a dynamic IP address via DHCP
42024e
+* Has a hostname of ``cloud-vm``
42024e
+
42024e
+Examples
42024e
+--------
42024e
+
42024e
+This section reviews common configurations:
42024e
+
42024e
+Setting the hostname
42024e
+^^^^^^^^^^^^^^^^^^^^
42024e
+
42024e
+The hostname is set by way of the metadata key ``local-hostname``.
42024e
+
42024e
+Setting the instance ID
42024e
+^^^^^^^^^^^^^^^^^^^^^^^
42024e
+
42024e
+The instance ID may be set by way of the metadata key ``instance-id``. However,
42024e
+if this value is absent then then the instance ID is read from the file
42024e
+``/sys/class/dmi/id/product_uuid``.
42024e
+
42024e
+Providing public SSH keys
42024e
+^^^^^^^^^^^^^^^^^^^^^^^^^
42024e
+
42024e
+The public SSH keys may be set by way of the metadata key ``public-keys-data``.
42024e
+Each newline-terminated string will be interpreted as a separate SSH public
42024e
+key, which will be placed in distro's default user's
42024e
+``~/.ssh/authorized_keys``. If the value is empty or absent, then nothing will
42024e
+be written to ``~/.ssh/authorized_keys``.
42024e
+
42024e
+Configuring the network
42024e
+^^^^^^^^^^^^^^^^^^^^^^^
42024e
+
42024e
+The network is configured by setting the metadata key ``network`` with a value
42024e
+consistent with Network Config Versions
42024e
+`1 <../network-config-format-v1.html>`_ or
42024e
+`2 <../network-config-format-v2.html>`_\ , depending on the Linux
42024e
+distro's version of cloud-init.
42024e
+
42024e
+The metadata key ``network.encoding`` may be used to indicate the format of
42024e
+the metadata key "network". Valid encodings are ``base64`` and ``gzip+base64``.
42024e
diff --git a/requirements.txt b/requirements.txt
42024e
index 5b8becd7..41d01d62 100644
42024e
--- a/requirements.txt
42024e
+++ b/requirements.txt
42024e
@@ -29,3 +29,15 @@ requests
42024e
 
42024e
 # For patching pieces of cloud-config together
42024e
 jsonpatch
42024e
+
42024e
+# For validating cloud-config sections per schema definitions
42024e
+jsonschema
42024e
+
42024e
+# Used by DataSourceVMware to inspect the host's network configuration during
42024e
+# the "setup()" function.
42024e
+#
42024e
+# This allows a host that uses DHCP to bring up the network during BootLocal
42024e
+# and still participate in instance-data by gathering the network in detail at
42024e
+# runtime and merge that information into the metadata and repersist that to
42024e
+# disk.
42024e
+netifaces>=0.10.9
42024e
diff --git a/tests/unittests/test_datasource/test_common.py b/tests/unittests/test_datasource/test_common.py
42024e
index 5912f7ee..475a2cf8 100644
42024e
--- a/tests/unittests/test_datasource/test_common.py
42024e
+++ b/tests/unittests/test_datasource/test_common.py
42024e
@@ -28,6 +28,7 @@ from cloudinit.sources import (
42024e
     DataSourceScaleway as Scaleway,
42024e
     DataSourceSmartOS as SmartOS,
42024e
     DataSourceUpCloud as UpCloud,
42024e
+    DataSourceVMware as VMware,
42024e
 )
42024e
 from cloudinit.sources import DataSourceNone as DSNone
42024e
 
42024e
@@ -50,6 +51,7 @@ DEFAULT_LOCAL = [
42024e
     RbxCloud.DataSourceRbxCloud,
42024e
     Scaleway.DataSourceScaleway,
42024e
     UpCloud.DataSourceUpCloudLocal,
42024e
+    VMware.DataSourceVMware,
42024e
 ]
42024e
 
42024e
 DEFAULT_NETWORK = [
42024e
@@ -66,6 +68,7 @@ DEFAULT_NETWORK = [
42024e
     OpenStack.DataSourceOpenStack,
42024e
     OVF.DataSourceOVFNet,
42024e
     UpCloud.DataSourceUpCloud,
42024e
+    VMware.DataSourceVMware,
42024e
 ]
42024e
 
42024e
 
42024e
diff --git a/tests/unittests/test_datasource/test_vmware.py b/tests/unittests/test_datasource/test_vmware.py
42024e
new file mode 100644
42024e
index 00000000..597db7c8
42024e
--- /dev/null
42024e
+++ b/tests/unittests/test_datasource/test_vmware.py
42024e
@@ -0,0 +1,377 @@
42024e
+# Copyright (c) 2021 VMware, Inc. All Rights Reserved.
42024e
+#
42024e
+# Authors: Andrew Kutz <akutz@vmware.com>
42024e
+#
42024e
+# This file is part of cloud-init. See LICENSE file for license information.
42024e
+
42024e
+import base64
42024e
+import gzip
42024e
+from cloudinit import dmi, helpers, safeyaml
42024e
+from cloudinit import settings
42024e
+from cloudinit.sources import DataSourceVMware
42024e
+from cloudinit.tests.helpers import (
42024e
+    mock,
42024e
+    CiTestCase,
42024e
+    FilesystemMockingTestCase,
42024e
+    populate_dir,
42024e
+)
42024e
+
42024e
+import os
42024e
+
42024e
+PRODUCT_NAME_FILE_PATH = "/sys/class/dmi/id/product_name"
42024e
+PRODUCT_NAME = "VMware7,1"
42024e
+PRODUCT_UUID = "82343CED-E4C7-423B-8F6B-0D34D19067AB"
42024e
+REROOT_FILES = {
42024e
+    DataSourceVMware.PRODUCT_UUID_FILE_PATH: PRODUCT_UUID,
42024e
+    PRODUCT_NAME_FILE_PATH: PRODUCT_NAME,
42024e
+}
42024e
+
42024e
+VMW_MULTIPLE_KEYS = [
42024e
+    "ssh-rsa AAAAB3NzaC1yc2EAAAA... test1@vmw.com",
42024e
+    "ssh-rsa AAAAB3NzaC1yc2EAAAA... test2@vmw.com",
42024e
+]
42024e
+VMW_SINGLE_KEY = "ssh-rsa AAAAB3NzaC1yc2EAAAA... test@vmw.com"
42024e
+
42024e
+VMW_METADATA_YAML = """instance-id: cloud-vm
42024e
+local-hostname: cloud-vm
42024e
+network:
42024e
+  version: 2
42024e
+  ethernets:
42024e
+    nics:
42024e
+      match:
42024e
+        name: ens*
42024e
+      dhcp4: yes
42024e
+"""
42024e
+
42024e
+VMW_USERDATA_YAML = """## template: jinja
42024e
+#cloud-config
42024e
+users:
42024e
+- default
42024e
+"""
42024e
+
42024e
+VMW_VENDORDATA_YAML = """## template: jinja
42024e
+#cloud-config
42024e
+runcmd:
42024e
+- echo "Hello, world."
42024e
+"""
42024e
+
42024e
+
42024e
+class TestDataSourceVMware(CiTestCase):
42024e
+    """
42024e
+    Test common functionality that is not transport specific.
42024e
+    """
42024e
+
42024e
+    def setUp(self):
42024e
+        super(TestDataSourceVMware, self).setUp()
42024e
+        self.tmp = self.tmp_dir()
42024e
+
42024e
+    def test_no_data_access_method(self):
42024e
+        ds = get_ds(self.tmp)
42024e
+        ds.vmware_rpctool = None
42024e
+        ret = ds.get_data()
42024e
+        self.assertFalse(ret)
42024e
+
42024e
+    def test_get_host_info(self):
42024e
+        host_info = DataSourceVMware.get_host_info()
42024e
+        self.assertTrue(host_info)
42024e
+        self.assertTrue(host_info["hostname"])
42024e
+        self.assertTrue(host_info["local-hostname"])
42024e
+        self.assertTrue(host_info["local_hostname"])
42024e
+        self.assertTrue(host_info[DataSourceVMware.LOCAL_IPV4])
42024e
+
42024e
+
42024e
+class TestDataSourceVMwareEnvVars(FilesystemMockingTestCase):
42024e
+    """
42024e
+    Test the envvar transport.
42024e
+    """
42024e
+
42024e
+    def setUp(self):
42024e
+        super(TestDataSourceVMwareEnvVars, self).setUp()
42024e
+        self.tmp = self.tmp_dir()
42024e
+        os.environ[DataSourceVMware.VMX_GUESTINFO] = "1"
42024e
+        self.create_system_files()
42024e
+
42024e
+    def tearDown(self):
42024e
+        del os.environ[DataSourceVMware.VMX_GUESTINFO]
42024e
+        return super(TestDataSourceVMwareEnvVars, self).tearDown()
42024e
+
42024e
+    def create_system_files(self):
42024e
+        rootd = self.tmp_dir()
42024e
+        populate_dir(
42024e
+            rootd,
42024e
+            {
42024e
+                DataSourceVMware.PRODUCT_UUID_FILE_PATH: PRODUCT_UUID,
42024e
+            },
42024e
+        )
42024e
+        self.assertTrue(self.reRoot(rootd))
42024e
+
42024e
+    def assert_get_data_ok(self, m_fn, m_fn_call_count=6):
42024e
+        ds = get_ds(self.tmp)
42024e
+        ds.vmware_rpctool = None
42024e
+        ret = ds.get_data()
42024e
+        self.assertTrue(ret)
42024e
+        self.assertEqual(m_fn_call_count, m_fn.call_count)
42024e
+        self.assertEqual(
42024e
+            ds.data_access_method, DataSourceVMware.DATA_ACCESS_METHOD_ENVVAR
42024e
+        )
42024e
+        return ds
42024e
+
42024e
+    def assert_metadata(self, metadata, m_fn, m_fn_call_count=6):
42024e
+        ds = self.assert_get_data_ok(m_fn, m_fn_call_count)
42024e
+        assert_metadata(self, ds, metadata)
42024e
+
42024e
+    @mock.patch(
42024e
+        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
42024e
+    )
42024e
+    def test_get_subplatform(self, m_fn):
42024e
+        m_fn.side_effect = [VMW_METADATA_YAML, "", "", "", "", ""]
42024e
+        ds = self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+        self.assertEqual(
42024e
+            ds.subplatform,
42024e
+            "%s (%s)"
42024e
+            % (
42024e
+                DataSourceVMware.DATA_ACCESS_METHOD_ENVVAR,
42024e
+                DataSourceVMware.get_guestinfo_envvar_key_name("metadata"),
42024e
+            ),
42024e
+        )
42024e
+
42024e
+    @mock.patch(
42024e
+        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
42024e
+    )
42024e
+    def test_get_data_metadata_only(self, m_fn):
42024e
+        m_fn.side_effect = [VMW_METADATA_YAML, "", "", "", "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch(
42024e
+        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
42024e
+    )
42024e
+    def test_get_data_userdata_only(self, m_fn):
42024e
+        m_fn.side_effect = ["", VMW_USERDATA_YAML, "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch(
42024e
+        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
42024e
+    )
42024e
+    def test_get_data_vendordata_only(self, m_fn):
42024e
+        m_fn.side_effect = ["", "", VMW_VENDORDATA_YAML, ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch(
42024e
+        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
42024e
+    )
42024e
+    def test_get_data_metadata_base64(self, m_fn):
42024e
+        data = base64.b64encode(VMW_METADATA_YAML.encode("utf-8"))
42024e
+        m_fn.side_effect = [data, "base64", "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch(
42024e
+        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
42024e
+    )
42024e
+    def test_get_data_metadata_b64(self, m_fn):
42024e
+        data = base64.b64encode(VMW_METADATA_YAML.encode("utf-8"))
42024e
+        m_fn.side_effect = [data, "b64", "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch(
42024e
+        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
42024e
+    )
42024e
+    def test_get_data_metadata_gzip_base64(self, m_fn):
42024e
+        data = VMW_METADATA_YAML.encode("utf-8")
42024e
+        data = gzip.compress(data)
42024e
+        data = base64.b64encode(data)
42024e
+        m_fn.side_effect = [data, "gzip+base64", "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch(
42024e
+        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
42024e
+    )
42024e
+    def test_get_data_metadata_gz_b64(self, m_fn):
42024e
+        data = VMW_METADATA_YAML.encode("utf-8")
42024e
+        data = gzip.compress(data)
42024e
+        data = base64.b64encode(data)
42024e
+        m_fn.side_effect = [data, "gz+b64", "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch(
42024e
+        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
42024e
+    )
42024e
+    def test_metadata_single_ssh_key(self, m_fn):
42024e
+        metadata = DataSourceVMware.load_json_or_yaml(VMW_METADATA_YAML)
42024e
+        metadata["public_keys"] = VMW_SINGLE_KEY
42024e
+        metadata_yaml = safeyaml.dumps(metadata)
42024e
+        m_fn.side_effect = [metadata_yaml, "", "", ""]
42024e
+        self.assert_metadata(metadata, m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch(
42024e
+        "cloudinit.sources.DataSourceVMware.guestinfo_envvar_get_value"
42024e
+    )
42024e
+    def test_metadata_multiple_ssh_keys(self, m_fn):
42024e
+        metadata = DataSourceVMware.load_json_or_yaml(VMW_METADATA_YAML)
42024e
+        metadata["public_keys"] = VMW_MULTIPLE_KEYS
42024e
+        metadata_yaml = safeyaml.dumps(metadata)
42024e
+        m_fn.side_effect = [metadata_yaml, "", "", ""]
42024e
+        self.assert_metadata(metadata, m_fn, m_fn_call_count=4)
42024e
+
42024e
+
42024e
+class TestDataSourceVMwareGuestInfo(FilesystemMockingTestCase):
42024e
+    """
42024e
+    Test the guestinfo transport on a VMware platform.
42024e
+    """
42024e
+
42024e
+    def setUp(self):
42024e
+        super(TestDataSourceVMwareGuestInfo, self).setUp()
42024e
+        self.tmp = self.tmp_dir()
42024e
+        self.create_system_files()
42024e
+
42024e
+    def create_system_files(self):
42024e
+        rootd = self.tmp_dir()
42024e
+        populate_dir(
42024e
+            rootd,
42024e
+            {
42024e
+                DataSourceVMware.PRODUCT_UUID_FILE_PATH: PRODUCT_UUID,
42024e
+                PRODUCT_NAME_FILE_PATH: PRODUCT_NAME,
42024e
+            },
42024e
+        )
42024e
+        self.assertTrue(self.reRoot(rootd))
42024e
+
42024e
+    def assert_get_data_ok(self, m_fn, m_fn_call_count=6):
42024e
+        ds = get_ds(self.tmp)
42024e
+        ds.vmware_rpctool = "vmware-rpctool"
42024e
+        ret = ds.get_data()
42024e
+        self.assertTrue(ret)
42024e
+        self.assertEqual(m_fn_call_count, m_fn.call_count)
42024e
+        self.assertEqual(
42024e
+            ds.data_access_method,
42024e
+            DataSourceVMware.DATA_ACCESS_METHOD_GUESTINFO,
42024e
+        )
42024e
+        return ds
42024e
+
42024e
+    def assert_metadata(self, metadata, m_fn, m_fn_call_count=6):
42024e
+        ds = self.assert_get_data_ok(m_fn, m_fn_call_count)
42024e
+        assert_metadata(self, ds, metadata)
42024e
+
42024e
+    def test_ds_valid_on_vmware_platform(self):
42024e
+        system_type = dmi.read_dmi_data("system-product-name")
42024e
+        self.assertEqual(system_type, PRODUCT_NAME)
42024e
+
42024e
+    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
42024e
+    def test_get_subplatform(self, m_fn):
42024e
+        m_fn.side_effect = [VMW_METADATA_YAML, "", "", "", "", ""]
42024e
+        ds = self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+        self.assertEqual(
42024e
+            ds.subplatform,
42024e
+            "%s (%s)"
42024e
+            % (
42024e
+                DataSourceVMware.DATA_ACCESS_METHOD_GUESTINFO,
42024e
+                DataSourceVMware.get_guestinfo_key_name("metadata"),
42024e
+            ),
42024e
+        )
42024e
+
42024e
+    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
42024e
+    def test_get_data_userdata_only(self, m_fn):
42024e
+        m_fn.side_effect = ["", VMW_USERDATA_YAML, "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
42024e
+    def test_get_data_vendordata_only(self, m_fn):
42024e
+        m_fn.side_effect = ["", "", VMW_VENDORDATA_YAML, ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
42024e
+    def test_metadata_single_ssh_key(self, m_fn):
42024e
+        metadata = DataSourceVMware.load_json_or_yaml(VMW_METADATA_YAML)
42024e
+        metadata["public_keys"] = VMW_SINGLE_KEY
42024e
+        metadata_yaml = safeyaml.dumps(metadata)
42024e
+        m_fn.side_effect = [metadata_yaml, "", "", ""]
42024e
+        self.assert_metadata(metadata, m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
42024e
+    def test_metadata_multiple_ssh_keys(self, m_fn):
42024e
+        metadata = DataSourceVMware.load_json_or_yaml(VMW_METADATA_YAML)
42024e
+        metadata["public_keys"] = VMW_MULTIPLE_KEYS
42024e
+        metadata_yaml = safeyaml.dumps(metadata)
42024e
+        m_fn.side_effect = [metadata_yaml, "", "", ""]
42024e
+        self.assert_metadata(metadata, m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
42024e
+    def test_get_data_metadata_base64(self, m_fn):
42024e
+        data = base64.b64encode(VMW_METADATA_YAML.encode("utf-8"))
42024e
+        m_fn.side_effect = [data, "base64", "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
42024e
+    def test_get_data_metadata_b64(self, m_fn):
42024e
+        data = base64.b64encode(VMW_METADATA_YAML.encode("utf-8"))
42024e
+        m_fn.side_effect = [data, "b64", "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
42024e
+    def test_get_data_metadata_gzip_base64(self, m_fn):
42024e
+        data = VMW_METADATA_YAML.encode("utf-8")
42024e
+        data = gzip.compress(data)
42024e
+        data = base64.b64encode(data)
42024e
+        m_fn.side_effect = [data, "gzip+base64", "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
42024e
+    def test_get_data_metadata_gz_b64(self, m_fn):
42024e
+        data = VMW_METADATA_YAML.encode("utf-8")
42024e
+        data = gzip.compress(data)
42024e
+        data = base64.b64encode(data)
42024e
+        m_fn.side_effect = [data, "gz+b64", "", ""]
42024e
+        self.assert_get_data_ok(m_fn, m_fn_call_count=4)
42024e
+
42024e
+
42024e
+class TestDataSourceVMwareGuestInfo_InvalidPlatform(FilesystemMockingTestCase):
42024e
+    """
42024e
+    Test the guestinfo transport on a non-VMware platform.
42024e
+    """
42024e
+
42024e
+    def setUp(self):
42024e
+        super(TestDataSourceVMwareGuestInfo_InvalidPlatform, self).setUp()
42024e
+        self.tmp = self.tmp_dir()
42024e
+        self.create_system_files()
42024e
+
42024e
+    def create_system_files(self):
42024e
+        rootd = self.tmp_dir()
42024e
+        populate_dir(
42024e
+            rootd,
42024e
+            {
42024e
+                DataSourceVMware.PRODUCT_UUID_FILE_PATH: PRODUCT_UUID,
42024e
+            },
42024e
+        )
42024e
+        self.assertTrue(self.reRoot(rootd))
42024e
+
42024e
+    @mock.patch("cloudinit.sources.DataSourceVMware.guestinfo_get_value")
42024e
+    def test_ds_invalid_on_non_vmware_platform(self, m_fn):
42024e
+        system_type = dmi.read_dmi_data("system-product-name")
42024e
+        self.assertEqual(system_type, None)
42024e
+
42024e
+        m_fn.side_effect = [VMW_METADATA_YAML, "", "", "", "", ""]
42024e
+        ds = get_ds(self.tmp)
42024e
+        ds.vmware_rpctool = "vmware-rpctool"
42024e
+        ret = ds.get_data()
42024e
+        self.assertFalse(ret)
42024e
+
42024e
+
42024e
+def assert_metadata(test_obj, ds, metadata):
42024e
+    test_obj.assertEqual(metadata.get("instance-id"), ds.get_instance_id())
42024e
+    test_obj.assertEqual(metadata.get("local-hostname"), ds.get_hostname())
42024e
+
42024e
+    expected_public_keys = metadata.get("public_keys")
42024e
+    if not isinstance(expected_public_keys, list):
42024e
+        expected_public_keys = [expected_public_keys]
42024e
+
42024e
+    test_obj.assertEqual(expected_public_keys, ds.get_public_ssh_keys())
42024e
+    test_obj.assertIsInstance(ds.get_public_ssh_keys(), list)
42024e
+
42024e
+
42024e
+def get_ds(temp_dir):
42024e
+    ds = DataSourceVMware.DataSourceVMware(
42024e
+        settings.CFG_BUILTIN, None, helpers.Paths({"run_dir": temp_dir})
42024e
+    )
42024e
+    ds.vmware_rpctool = "vmware-rpctool"
42024e
+    return ds
42024e
+
42024e
+
42024e
+# vi: ts=4 expandtab
42024e
diff --git a/tests/unittests/test_ds_identify.py b/tests/unittests/test_ds_identify.py
42024e
index 1d8aaf18..8617d7bd 100644
42024e
--- a/tests/unittests/test_ds_identify.py
42024e
+++ b/tests/unittests/test_ds_identify.py
42024e
@@ -649,6 +649,50 @@ class TestDsIdentify(DsIdentifyBase):
42024e
         """EC2: bobrightbox.com in product_serial is not brightbox'"""
42024e
         self._test_ds_not_found('Ec2-E24Cloud-negative')
42024e
 
42024e
+    def test_vmware_no_valid_transports(self):
42024e
+        """VMware: no valid transports"""
42024e
+        self._test_ds_not_found('VMware-NoValidTransports')
42024e
+
42024e
+    def test_vmware_envvar_no_data(self):
42024e
+        """VMware: envvar transport no data"""
42024e
+        self._test_ds_not_found('VMware-EnvVar-NoData')
42024e
+
42024e
+    def test_vmware_envvar_no_virt_id(self):
42024e
+        """VMware: envvar transport success if no virt id"""
42024e
+        self._test_ds_found('VMware-EnvVar-NoVirtID')
42024e
+
42024e
+    def test_vmware_envvar_activated_by_metadata(self):
42024e
+        """VMware: envvar transport activated by metadata"""
42024e
+        self._test_ds_found('VMware-EnvVar-Metadata')
42024e
+
42024e
+    def test_vmware_envvar_activated_by_userdata(self):
42024e
+        """VMware: envvar transport activated by userdata"""
42024e
+        self._test_ds_found('VMware-EnvVar-Userdata')
42024e
+
42024e
+    def test_vmware_envvar_activated_by_vendordata(self):
42024e
+        """VMware: envvar transport activated by vendordata"""
42024e
+        self._test_ds_found('VMware-EnvVar-Vendordata')
42024e
+
42024e
+    def test_vmware_guestinfo_no_data(self):
42024e
+        """VMware: guestinfo transport no data"""
42024e
+        self._test_ds_not_found('VMware-GuestInfo-NoData')
42024e
+
42024e
+    def test_vmware_guestinfo_no_virt_id(self):
42024e
+        """VMware: guestinfo transport fails if no virt id"""
42024e
+        self._test_ds_not_found('VMware-GuestInfo-NoVirtID')
42024e
+
42024e
+    def test_vmware_guestinfo_activated_by_metadata(self):
42024e
+        """VMware: guestinfo transport activated by metadata"""
42024e
+        self._test_ds_found('VMware-GuestInfo-Metadata')
42024e
+
42024e
+    def test_vmware_guestinfo_activated_by_userdata(self):
42024e
+        """VMware: guestinfo transport activated by userdata"""
42024e
+        self._test_ds_found('VMware-GuestInfo-Userdata')
42024e
+
42024e
+    def test_vmware_guestinfo_activated_by_vendordata(self):
42024e
+        """VMware: guestinfo transport activated by vendordata"""
42024e
+        self._test_ds_found('VMware-GuestInfo-Vendordata')
42024e
+
42024e
 
42024e
 class TestBSDNoSys(DsIdentifyBase):
42024e
     """Test *BSD code paths
42024e
@@ -1136,7 +1180,240 @@ VALID_CFG = {
42024e
     'Ec2-E24Cloud-negative': {
42024e
         'ds': 'Ec2',
42024e
         'files': {P_SYS_VENDOR: 'e24cloudyday\n'},
42024e
-    }
42024e
+    },
42024e
+    'VMware-NoValidTransports': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            MOCK_VIRT_IS_VMWARE,
42024e
+        ],
42024e
+    },
42024e
+    'VMware-EnvVar-NoData': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo',
42024e
+                'ret': 0,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_metadata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_userdata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_vendordata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            MOCK_VIRT_IS_VMWARE,
42024e
+        ],
42024e
+    },
42024e
+    'VMware-EnvVar-NoVirtID': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo',
42024e
+                'ret': 0,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_metadata',
42024e
+                'ret': 0,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_userdata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_vendordata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+        ],
42024e
+    },
42024e
+    'VMware-EnvVar-Metadata': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo',
42024e
+                'ret': 0,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_metadata',
42024e
+                'ret': 0,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_userdata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_vendordata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            MOCK_VIRT_IS_VMWARE,
42024e
+        ],
42024e
+    },
42024e
+    'VMware-EnvVar-Userdata': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo',
42024e
+                'ret': 0,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_metadata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_userdata',
42024e
+                'ret': 0,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_vendordata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            MOCK_VIRT_IS_VMWARE,
42024e
+        ],
42024e
+    },
42024e
+    'VMware-EnvVar-Vendordata': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo',
42024e
+                'ret': 0,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_metadata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_userdata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_has_envvar_vmx_guestinfo_vendordata',
42024e
+                'ret': 0,
42024e
+            },
42024e
+            MOCK_VIRT_IS_VMWARE,
42024e
+        ],
42024e
+    },
42024e
+    'VMware-GuestInfo-NoData': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            {
42024e
+                'name': 'vmware_has_rpctool',
42024e
+                'ret': 0,
42024e
+                'out': '/usr/bin/vmware-rpctool',
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_metadata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_userdata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_vendordata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            MOCK_VIRT_IS_VMWARE,
42024e
+        ],
42024e
+    },
42024e
+    'VMware-GuestInfo-NoVirtID': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            {
42024e
+                'name': 'vmware_has_rpctool',
42024e
+                'ret': 0,
42024e
+                'out': '/usr/bin/vmware-rpctool',
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_metadata',
42024e
+                'ret': 0,
42024e
+                'out': '---',
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_userdata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_vendordata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+        ],
42024e
+    },
42024e
+    'VMware-GuestInfo-Metadata': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            {
42024e
+                'name': 'vmware_has_rpctool',
42024e
+                'ret': 0,
42024e
+                'out': '/usr/bin/vmware-rpctool',
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_metadata',
42024e
+                'ret': 0,
42024e
+                'out': '---',
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_userdata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_vendordata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            MOCK_VIRT_IS_VMWARE,
42024e
+        ],
42024e
+    },
42024e
+    'VMware-GuestInfo-Userdata': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            {
42024e
+                'name': 'vmware_has_rpctool',
42024e
+                'ret': 0,
42024e
+                'out': '/usr/bin/vmware-rpctool',
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_metadata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_userdata',
42024e
+                'ret': 0,
42024e
+                'out': '---',
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_vendordata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            MOCK_VIRT_IS_VMWARE,
42024e
+        ],
42024e
+    },
42024e
+    'VMware-GuestInfo-Vendordata': {
42024e
+        'ds': 'VMware',
42024e
+        'mocks': [
42024e
+            {
42024e
+                'name': 'vmware_has_rpctool',
42024e
+                'ret': 0,
42024e
+                'out': '/usr/bin/vmware-rpctool',
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_metadata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_userdata',
42024e
+                'ret': 1,
42024e
+            },
42024e
+            {
42024e
+                'name': 'vmware_rpctool_guestinfo_vendordata',
42024e
+                'ret': 0,
42024e
+                'out': '---',
42024e
+            },
42024e
+            MOCK_VIRT_IS_VMWARE,
42024e
+        ],
42024e
+    },
42024e
 }
42024e
 
42024e
 # vi: ts=4 expandtab
42024e
diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers
42024e
index 689d7902..cbfa883c 100644
42024e
--- a/tools/.github-cla-signers
42024e
+++ b/tools/.github-cla-signers
42024e
@@ -1,5 +1,6 @@
42024e
 ader1990
42024e
 ajmyyra
42024e
+akutz
42024e
 AlexBaranowski
42024e
 Aman306
42024e
 andrewbogott
42024e
diff --git a/tools/ds-identify b/tools/ds-identify
42024e
index 2f2486f7..c01eae3d 100755
42024e
--- a/tools/ds-identify
42024e
+++ b/tools/ds-identify
42024e
@@ -125,7 +125,7 @@ DI_DSNAME=""
42024e
 # be searched if there is no setting found in config.
42024e
 DI_DSLIST_DEFAULT="MAAS ConfigDrive NoCloud AltCloud Azure Bigstep \
42024e
 CloudSigma CloudStack DigitalOcean AliYun Ec2 GCE OpenNebula OpenStack \
42024e
-OVF SmartOS Scaleway Hetzner IBMCloud Oracle Exoscale RbxCloud UpCloud"
42024e
+OVF SmartOS Scaleway Hetzner IBMCloud Oracle Exoscale RbxCloud UpCloud VMware"
42024e
 DI_DSLIST=""
42024e
 DI_MODE=""
42024e
 DI_ON_FOUND=""
42024e
@@ -1350,6 +1350,80 @@ dscheck_IBMCloud() {
42024e
     return ${DS_NOT_FOUND}
42024e
 }
42024e
 
42024e
+vmware_has_envvar_vmx_guestinfo() {
42024e
+    [ -n "${VMX_GUESTINFO:-}" ]
42024e
+}
42024e
+
42024e
+vmware_has_envvar_vmx_guestinfo_metadata() {
42024e
+    [ -n "${VMX_GUESTINFO_METADATA:-}" ]
42024e
+}
42024e
+
42024e
+vmware_has_envvar_vmx_guestinfo_userdata() {
42024e
+    [ -n "${VMX_GUESTINFO_USERDATA:-}" ]
42024e
+}
42024e
+
42024e
+vmware_has_envvar_vmx_guestinfo_vendordata() {
42024e
+    [ -n "${VMX_GUESTINFO_VENDORDATA:-}" ]
42024e
+}
42024e
+
42024e
+vmware_has_rpctool() {
42024e
+    command -v vmware-rpctool >/dev/null 2>&1
42024e
+}
42024e
+
42024e
+vmware_rpctool_guestinfo_metadata() {
42024e
+    vmware-rpctool "info-get guestinfo.metadata"
42024e
+}
42024e
+
42024e
+vmware_rpctool_guestinfo_userdata() {
42024e
+    vmware-rpctool "info-get guestinfo.userdata"
42024e
+}
42024e
+
42024e
+vmware_rpctool_guestinfo_vendordata() {
42024e
+    vmware-rpctool "info-get guestinfo.vendordata"
42024e
+}
42024e
+
42024e
+dscheck_VMware() {
42024e
+    # Checks to see if there is valid data for the VMware datasource.
42024e
+    # The data transports are checked in the following order:
42024e
+    #
42024e
+    #   * envvars
42024e
+    #   * guestinfo
42024e
+    #
42024e
+    # Please note when updating this function with support for new data
42024e
+    # transports, the order should match the order in the _get_data
42024e
+    # function from the file DataSourceVMware.py.
42024e
+
42024e
+    # Check to see if running in a container and the VMware
42024e
+    # datasource is configured via environment variables.
42024e
+    if vmware_has_envvar_vmx_guestinfo; then
42024e
+        if vmware_has_envvar_vmx_guestinfo_metadata || \
42024e
+            vmware_has_envvar_vmx_guestinfo_userdata || \
42024e
+            vmware_has_envvar_vmx_guestinfo_vendordata; then
42024e
+            return "${DS_FOUND}"
42024e
+        fi
42024e
+    fi
42024e
+
42024e
+    # Do not proceed unless the detected platform is VMware.
42024e
+    if [ ! "${DI_VIRT}" = "vmware" ]; then
42024e
+        return "${DS_NOT_FOUND}"
42024e
+    fi
42024e
+
42024e
+    # Do not proceed if the vmware-rpctool command is not present.
42024e
+    if ! vmware_has_rpctool; then
42024e
+        return "${DS_NOT_FOUND}"
42024e
+    fi
42024e
+
42024e
+    # Activate the VMware datasource only if any of the fields used
42024e
+    # by the datasource are present in the guestinfo table.
42024e
+    if { vmware_rpctool_guestinfo_metadata || \
42024e
+         vmware_rpctool_guestinfo_userdata || \
42024e
+         vmware_rpctool_guestinfo_vendordata; } >/dev/null 2>&1; then
42024e
+        return "${DS_FOUND}"
42024e
+    fi
42024e
+
42024e
+    return "${DS_NOT_FOUND}"
42024e
+}
42024e
+
42024e
 collect_info() {
42024e
     read_uname_info
42024e
     read_virt
42024e
-- 
42024e
2.27.0
42024e