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