blob: c633c1db61da93ff996c4b5fc4c78fad06ac0cc0 [file] [log] [blame]
akutz77457a62018-08-22 16:07:21 -05001# vi: ts=4 expandtab
2#
akutzdd794a42018-09-18 10:04:21 -05003# Cloud-Init Datasource for VMware Guestinfo
4#
5# Copyright (c) 2018 VMware, Inc. All Rights Reserved.
6#
7# This product is licensed to you under the Apache 2.0 license (the "License").
8# You may not use this product except in compliance with the Apache 2.0 License.
9#
10# This product may include a number of subcomponents with separate copyright
11# notices and license terms. Your use of these subcomponents is subject to the
12# terms and conditions of the subcomponent's license, as noted in the LICENSE
13# file.
akutz77457a62018-08-22 16:07:21 -050014#
akutz6501f902018-08-24 12:19:05 -050015# Authors: Anish Swaminathan <anishs@vmware.com>
16# Andrew Kutz <akutz@vmware.com>
akutz77457a62018-08-22 16:07:21 -050017#
18import os
19import base64
akutz6501f902018-08-24 12:19:05 -050020import zlib
21import json
akutz77457a62018-08-22 16:07:21 -050022
23from cloudinit import log as logging
24from cloudinit import sources
25from cloudinit import util
akutz6501f902018-08-24 12:19:05 -050026from cloudinit import safeyaml
akutz77457a62018-08-22 16:07:21 -050027
28from distutils.spawn import find_executable
29
30LOG = logging.getLogger(__name__)
31
Andrew Kutz4f66b8b2018-09-16 18:28:59 -050032# This cloud-init datasource was designed for use with CentOS 7,
33# which uses cloud-init 0.7.9. However, this datasource should
34# work with any Linux distribution for which cloud-init is
35# avaialble.
36#
37# The documentation for cloud-init 0.7.9's datasource is
38# available at http://bit.ly/cloudinit-datasource-0-7-9. The
39# current documentation for cloud-init is found at
40# https://cloudinit.readthedocs.io/en/latest/.
akutz6501f902018-08-24 12:19:05 -050041#
42# Setting the hostname:
43# The hostname is set by way of the metadata key "local-hostname".
44#
45# Setting the instance ID:
46# The instance ID may be set by way of the metadata key "instance-id".
47# However, if this value is absent then then the instance ID is
48# read from the file /sys/class/dmi/id/product_uuid.
49#
50# Configuring the network:
51# The network is configured by setting the metadata key "network"
52# with a value consistent with Network Config Versions 1 or 2,
53# depending on the Linux distro's version of cloud-init:
54#
55# Network Config Version 1 - http://bit.ly/cloudinit-net-conf-v1
56# Network Config Version 2 - http://bit.ly/cloudinit-net-conf-v2
57#
58# For example, CentOS 7's official cloud-init package is version
59# 0.7.9 and does not support Network Config Version 2. However,
60# this datasource still supports supplying Network Config Version 2
61# data as long as the Linux distro's cloud-init package is new
62# enough to parse the data.
63#
64# The metadata key "network.encoding" may be used to indicate the
65# format of the metadata key "network". Valid encodings are base64
66# and gzip+base64.
Andrew Kutz4f66b8b2018-09-16 18:28:59 -050067class DataSourceVMwareGuestInfo(sources.DataSource):
akutz77457a62018-08-22 16:07:21 -050068 def __init__(self, sys_cfg, distro, paths, ud_proc=None):
69 sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc)
akutz77457a62018-08-22 16:07:21 -050070 self.vmtoolsd = find_executable("vmtoolsd")
71 if not self.vmtoolsd:
72 LOG.error("Failed to find vmtoolsd")
73
74 def get_data(self):
75 if not self.vmtoolsd:
76 LOG.error("vmtoolsd is required to fetch guestinfo value")
77 return False
akutz6501f902018-08-24 12:19:05 -050078
79 # Get the JSON metadata. Can be plain-text, base64, or gzip+base64.
80 metadata = self._get_encoded_guestinfo_data('metadata')
81 if metadata:
82 self.metadata = json.loads(metadata)
83
84 # Get the YAML userdata. Can be plain-text, base64, or gzip+base64.
85 self.userdata_raw = self._get_encoded_guestinfo_data('userdata')
86
87 # Get the YAML vendordata. Can be plain-text, base64, or gzip+base64.
88 self.vendordata_raw = self._get_encoded_guestinfo_data('vendordata')
89
akutz77457a62018-08-22 16:07:21 -050090 return True
91
akutz6501f902018-08-24 12:19:05 -050092 @property
93 def network_config(self):
94 # Pull the network configuration out of the metadata.
95 if self.metadata and 'network' in self.metadata:
96 data = self._get_encoded_metadata('network')
97 if data:
98 # Load the YAML-formatted network data into an object
99 # and return it.
100 net_config = safeyaml.load(data)
101 LOG.debug("Loaded network config: %s", net_config)
102 return net_config
103 return None
akutz77457a62018-08-22 16:07:21 -0500104
105 def get_instance_id(self):
akutz6501f902018-08-24 12:19:05 -0500106 # Pull the instance ID out of the metadata if present. Otherwise
107 # read the file /sys/class/dmi/id/product_uuid for the instance ID.
108 if self.metadata and 'instance-id' in self.metadata:
109 return self.metadata['instance-id']
akutz77457a62018-08-22 16:07:21 -0500110 with open('/sys/class/dmi/id/product_uuid', 'r') as id_file:
111 return str(id_file.read()).rstrip()
112
akutz6501f902018-08-24 12:19:05 -0500113 def _get_encoded_guestinfo_data(self, key):
114 data = self._get_guestinfo_value(key)
115 if not data:
116 return None
117 enc_type = self._get_guestinfo_value(key + '.encoding')
118 return self._get_encoded_data('guestinfo.' + key, enc_type, data)
119
120 def _get_encoded_metadata(self, key):
121 if not self.metadata or not key in self.metadata:
122 return None
123 data = self.metadata[key]
124 enc_type = self.metadata.get(key + '.encoding')
125 return self._get_encoded_data('metadata.' + key, enc_type, data)
126
127 def _get_encoded_data(self, key, enc_type, data):
Sidharth Surana3a421682018-10-10 15:42:08 -0700128 '''
129 The _get_encoded_data would always return a str
130 ----
131 In py 2.7:
132 json.loads method takes string as input
133 zlib.decompress takes and returns a string
134 base64.b64decode takes and returns a string
135 -----
136 In py 3.6 and newer:
137 json.loads method takes bytes or string as input
138 zlib.decompress takes and returns a bytes
139 base64.b64decode takes bytes or string and returns bytes
140 -----
141 In py > 3, < 3.6:
142 json.loads method takes string as input
143 zlib.decompress takes and returns a bytes
144 base64.b64decode takes bytes or string and returns bytes
145 -----
146 Given the above conditions the output from zlib.decompress and
147 base64.b64decode would be bytes with newer python and str in older
148 version. Thus we would covert the output to str before returning
149 '''
150 rawdata = self._get_encoded_data_raw(key, enc_type, data)
151 if type(rawdata) == bytes:
152 return rawdata.decode('utf-8')
153 return rawdata
154
155 def _get_encoded_data_raw(self, key, enc_type, data):
akutz6501f902018-08-24 12:19:05 -0500156 LOG.debug("Getting encoded data for key=%s, enc=%s", key, enc_type)
157 if enc_type == "gzip+base64" or enc_type == "gz+b64":
158 LOG.debug("Decoding %s format %s", enc_type, key)
159 return zlib.decompress(base64.b64decode(data), zlib.MAX_WBITS | 16)
160 elif enc_type == "base64" or enc_type == "b64":
161 LOG.debug("Decoding %s format %s", enc_type, key)
162 return base64.b64decode(data)
163 else:
164 LOG.debug("Plain-text data %s", key)
165 return data
166
167 def _get_guestinfo_value(self, key):
168 NOVAL = "No value found"
169 LOG.debug("Getting guestinfo value for key %s", key)
170 try:
171 (stdout, stderr) = util.subp([self.vmtoolsd, "--cmd", "info-get guestinfo." + key])
172 if stderr == NOVAL:
173 LOG.debug("No value found for key %s", key)
174 elif not stdout:
175 LOG.error("Failed to get guestinfo value for key %s", key)
176 else:
177 return stdout.rstrip()
178 except util.ProcessExecutionError as error:
179 if error.stderr == NOVAL:
180 LOG.debug("No value found for key %s", key)
181 else:
182 util.logexc(LOG,"Failed to get guestinfo value for key %s: %s", key, error)
183 except Exception:
184 util.logexc(LOG,"Unexpected error while trying to get guestinfo value for key %s", key)
185 return None
186
akutz77457a62018-08-22 16:07:21 -0500187def get_datasource_list(depends):
188 """
189 Return a list of data sources that match this set of dependencies
190 """
Andrew Kutz4f66b8b2018-09-16 18:28:59 -0500191 return [DataSourceVMwareGuestInfo]