blob: c084e92cbcbd0bef150aebf522f4aedf6135863e [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):
128 LOG.debug("Getting encoded data for key=%s, enc=%s", key, enc_type)
129 if enc_type == "gzip+base64" or enc_type == "gz+b64":
130 LOG.debug("Decoding %s format %s", enc_type, key)
131 return zlib.decompress(base64.b64decode(data), zlib.MAX_WBITS | 16)
132 elif enc_type == "base64" or enc_type == "b64":
133 LOG.debug("Decoding %s format %s", enc_type, key)
134 return base64.b64decode(data)
135 else:
136 LOG.debug("Plain-text data %s", key)
137 return data
138
139 def _get_guestinfo_value(self, key):
140 NOVAL = "No value found"
141 LOG.debug("Getting guestinfo value for key %s", key)
142 try:
143 (stdout, stderr) = util.subp([self.vmtoolsd, "--cmd", "info-get guestinfo." + key])
144 if stderr == NOVAL:
145 LOG.debug("No value found for key %s", key)
146 elif not stdout:
147 LOG.error("Failed to get guestinfo value for key %s", key)
148 else:
149 return stdout.rstrip()
150 except util.ProcessExecutionError as error:
151 if error.stderr == NOVAL:
152 LOG.debug("No value found for key %s", key)
153 else:
154 util.logexc(LOG,"Failed to get guestinfo value for key %s: %s", key, error)
155 except Exception:
156 util.logexc(LOG,"Unexpected error while trying to get guestinfo value for key %s", key)
157 return None
158
akutz77457a62018-08-22 16:07:21 -0500159def get_datasource_list(depends):
160 """
161 Return a list of data sources that match this set of dependencies
162 """
Andrew Kutz4f66b8b2018-09-16 18:28:59 -0500163 return [DataSourceVMwareGuestInfo]