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















|
088c30
+| 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















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