blob: 8131253a56cfa115eef4e3f93de9ba192123087a [file] [log] [blame]
akutz77457a62018-08-22 16:07:21 -05001# vi: ts=4 expandtab
2#
Andrew Kutz4f66b8b2018-09-16 18:28:59 -05003# Copyright (C) 2018 VMware Inc.
akutz77457a62018-08-22 16:07:21 -05004#
akutz6501f902018-08-24 12:19:05 -05005# Authors: Anish Swaminathan <anishs@vmware.com>
6# Andrew Kutz <akutz@vmware.com>
akutz77457a62018-08-22 16:07:21 -05007#
8import os
9import base64
akutz6501f902018-08-24 12:19:05 -050010import zlib
11import json
akutz77457a62018-08-22 16:07:21 -050012
13from cloudinit import log as logging
14from cloudinit import sources
15from cloudinit import util
akutz6501f902018-08-24 12:19:05 -050016from cloudinit import safeyaml
akutz77457a62018-08-22 16:07:21 -050017
18from distutils.spawn import find_executable
19
20LOG = logging.getLogger(__name__)
21
Andrew Kutz4f66b8b2018-09-16 18:28:59 -050022# This cloud-init datasource was designed for use with CentOS 7,
23# which uses cloud-init 0.7.9. However, this datasource should
24# work with any Linux distribution for which cloud-init is
25# avaialble.
26#
27# The documentation for cloud-init 0.7.9's datasource is
28# available at http://bit.ly/cloudinit-datasource-0-7-9. The
29# current documentation for cloud-init is found at
30# https://cloudinit.readthedocs.io/en/latest/.
akutz6501f902018-08-24 12:19:05 -050031#
32# Setting the hostname:
33# The hostname is set by way of the metadata key "local-hostname".
34#
35# Setting the instance ID:
36# The instance ID may be set by way of the metadata key "instance-id".
37# However, if this value is absent then then the instance ID is
38# read from the file /sys/class/dmi/id/product_uuid.
39#
40# Configuring the network:
41# The network is configured by setting the metadata key "network"
42# with a value consistent with Network Config Versions 1 or 2,
43# depending on the Linux distro's version of cloud-init:
44#
45# Network Config Version 1 - http://bit.ly/cloudinit-net-conf-v1
46# Network Config Version 2 - http://bit.ly/cloudinit-net-conf-v2
47#
48# For example, CentOS 7's official cloud-init package is version
49# 0.7.9 and does not support Network Config Version 2. However,
50# this datasource still supports supplying Network Config Version 2
51# data as long as the Linux distro's cloud-init package is new
52# enough to parse the data.
53#
54# The metadata key "network.encoding" may be used to indicate the
55# format of the metadata key "network". Valid encodings are base64
56# and gzip+base64.
Andrew Kutz4f66b8b2018-09-16 18:28:59 -050057class DataSourceVMwareGuestInfo(sources.DataSource):
akutz77457a62018-08-22 16:07:21 -050058 def __init__(self, sys_cfg, distro, paths, ud_proc=None):
59 sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc)
akutz77457a62018-08-22 16:07:21 -050060 self.vmtoolsd = find_executable("vmtoolsd")
61 if not self.vmtoolsd:
62 LOG.error("Failed to find vmtoolsd")
63
64 def get_data(self):
65 if not self.vmtoolsd:
66 LOG.error("vmtoolsd is required to fetch guestinfo value")
67 return False
akutz6501f902018-08-24 12:19:05 -050068
69 # Get the JSON metadata. Can be plain-text, base64, or gzip+base64.
70 metadata = self._get_encoded_guestinfo_data('metadata')
71 if metadata:
72 self.metadata = json.loads(metadata)
73
74 # Get the YAML userdata. Can be plain-text, base64, or gzip+base64.
75 self.userdata_raw = self._get_encoded_guestinfo_data('userdata')
76
77 # Get the YAML vendordata. Can be plain-text, base64, or gzip+base64.
78 self.vendordata_raw = self._get_encoded_guestinfo_data('vendordata')
79
akutz77457a62018-08-22 16:07:21 -050080 return True
81
akutz6501f902018-08-24 12:19:05 -050082 @property
83 def network_config(self):
84 # Pull the network configuration out of the metadata.
85 if self.metadata and 'network' in self.metadata:
86 data = self._get_encoded_metadata('network')
87 if data:
88 # Load the YAML-formatted network data into an object
89 # and return it.
90 net_config = safeyaml.load(data)
91 LOG.debug("Loaded network config: %s", net_config)
92 return net_config
93 return None
akutz77457a62018-08-22 16:07:21 -050094
95 def get_instance_id(self):
akutz6501f902018-08-24 12:19:05 -050096 # Pull the instance ID out of the metadata if present. Otherwise
97 # read the file /sys/class/dmi/id/product_uuid for the instance ID.
98 if self.metadata and 'instance-id' in self.metadata:
99 return self.metadata['instance-id']
akutz77457a62018-08-22 16:07:21 -0500100 with open('/sys/class/dmi/id/product_uuid', 'r') as id_file:
101 return str(id_file.read()).rstrip()
102
akutz6501f902018-08-24 12:19:05 -0500103 def _get_encoded_guestinfo_data(self, key):
104 data = self._get_guestinfo_value(key)
105 if not data:
106 return None
107 enc_type = self._get_guestinfo_value(key + '.encoding')
108 return self._get_encoded_data('guestinfo.' + key, enc_type, data)
109
110 def _get_encoded_metadata(self, key):
111 if not self.metadata or not key in self.metadata:
112 return None
113 data = self.metadata[key]
114 enc_type = self.metadata.get(key + '.encoding')
115 return self._get_encoded_data('metadata.' + key, enc_type, data)
116
117 def _get_encoded_data(self, key, enc_type, data):
118 LOG.debug("Getting encoded data for key=%s, enc=%s", key, enc_type)
119 if enc_type == "gzip+base64" or enc_type == "gz+b64":
120 LOG.debug("Decoding %s format %s", enc_type, key)
121 return zlib.decompress(base64.b64decode(data), zlib.MAX_WBITS | 16)
122 elif enc_type == "base64" or enc_type == "b64":
123 LOG.debug("Decoding %s format %s", enc_type, key)
124 return base64.b64decode(data)
125 else:
126 LOG.debug("Plain-text data %s", key)
127 return data
128
129 def _get_guestinfo_value(self, key):
130 NOVAL = "No value found"
131 LOG.debug("Getting guestinfo value for key %s", key)
132 try:
133 (stdout, stderr) = util.subp([self.vmtoolsd, "--cmd", "info-get guestinfo." + key])
134 if stderr == NOVAL:
135 LOG.debug("No value found for key %s", key)
136 elif not stdout:
137 LOG.error("Failed to get guestinfo value for key %s", key)
138 else:
139 return stdout.rstrip()
140 except util.ProcessExecutionError as error:
141 if error.stderr == NOVAL:
142 LOG.debug("No value found for key %s", key)
143 else:
144 util.logexc(LOG,"Failed to get guestinfo value for key %s: %s", key, error)
145 except Exception:
146 util.logexc(LOG,"Unexpected error while trying to get guestinfo value for key %s", key)
147 return None
148
akutz77457a62018-08-22 16:07:21 -0500149def get_datasource_list(depends):
150 """
151 Return a list of data sources that match this set of dependencies
152 """
Andrew Kutz4f66b8b2018-09-16 18:28:59 -0500153 return [DataSourceVMwareGuestInfo]