Merge ~smoser/cloud-init:feature/gce-main into cloud-init:master

Proposed by Scott Moser on 2017-08-22
Status: Merged
Approved by: Scott Moser on 2017-08-30
Approved revision: 16ef156113d8e4d21b6da2592b4f22ed2e21521b
Merged at revision: 44529c1de0098ccd684b46b0bc18d48312c4097c
Proposed branch: ~smoser/cloud-init:feature/gce-main
Merge into: cloud-init:master
Diff against target: 222 lines (+112/-70)
1 file modified
cloudinit/sources/DataSourceGCE.py (+112/-70)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve on 2017-08-29
Chad Smith Approve on 2017-08-29
Ryan Harper 2017-08-22 Approve on 2017-08-22
Review via email: mp+329397@code.launchpad.net

Commit Message

GCE: Add a main to the GCE Datasource.

This just adds a main to the GCE datasource so that it is easily
callable: python3 -m cloudinit.sources.DataSourceGCE

To post a comment you must log in.

PASSED: Continuous integration, rev:f3182aac49cb8d1df42775fb2fe26bb33f207425
https://jenkins.ubuntu.com/server/job/cloud-init-ci/184/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/184/rebuild

review: Approve (continuous-integration)
Ryan Harper (raharper) wrote :

Nice!

If we're in here, we could update the metadata header:

Metadata-Flavor: Google

https://cloud.google.com/compute/docs/storing-retrieving-metadata#querying

which says:

When you query for metadata, you must provide the following header in all of your requests:

Metadata-Flavor: Google

This header indicates that the request was sent with the intention of retrieving metadata values, rather than unintentionally from an insecure source, and allows the metadata server to return the data you requested. If you do not provide this header, the metadata server denies your request.

Note: Previously, the X-Google-Metadata-Request: True header was required in requests. Both of these headers are still supported but it is recommended that you use the Metadata-Flavor header rather than the X-Google-Metadata-Request: True header.

review: Approve
Chad Smith (chad.smith) wrote :

+1 with a nit inline.

review: Approve
~smoser/cloud-init:feature/gce-main updated on 2017-08-29
78332b6... by Scott Moser on 2017-08-29

address feedback from chad

16ef156... by Scott Moser on 2017-08-29

log timing on gce crawl.

Scott Moser (smoser) wrote :

I addressed your one nit, and then wrapped log_time around the read_md call.

Scott Moser (smoser) wrote :

and also tested that on a gce instance.

smoser@instance-1:~$ dpkg-query --show cloud-init
cloud-init 0.7.9-256-g16ef1561-1~bddeb
smoser@instance-1:~$ grep Crawl /var/log/cloud-init.log
2017-08-29 18:17:45,142 - util.py[DEBUG]: Crawl of GCE metadata service took 0.051 seconds

Chad Smith (chad.smith) wrote :

+1 on the additional event logging for analyze +1 when CI finishes

review: Approve

PASSED: Continuous integration, rev:78332b64356ef855f85f2d795fa125e1b0009eb3
https://jenkins.ubuntu.com/server/job/cloud-init-ci/224/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/224/rebuild

review: Approve (continuous-integration)

PASSED: Continuous integration, rev:16ef156113d8e4d21b6da2592b4f22ed2e21521b
https://jenkins.ubuntu.com/server/job/cloud-init-ci/225/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/225/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/sources/DataSourceGCE.py b/cloudinit/sources/DataSourceGCE.py
2index 684eac8..94484d6 100644
3--- a/cloudinit/sources/DataSourceGCE.py
4+++ b/cloudinit/sources/DataSourceGCE.py
5@@ -11,9 +11,8 @@ from cloudinit import util
6
7 LOG = logging.getLogger(__name__)
8
9-BUILTIN_DS_CONFIG = {
10- 'metadata_url': 'http://metadata.google.internal/computeMetadata/v1/'
11-}
12+MD_V1_URL = 'http://metadata.google.internal/computeMetadata/v1/'
13+BUILTIN_DS_CONFIG = {'metadata_url': MD_V1_URL}
14 REQUIRED_FIELDS = ('instance-id', 'availability-zone', 'local-hostname')
15
16
17@@ -51,75 +50,20 @@ class DataSourceGCE(sources.DataSource):
18 BUILTIN_DS_CONFIG])
19 self.metadata_address = self.ds_cfg['metadata_url']
20
21- # GCE takes sshKeys attribute in the format of '<user>:<public_key>'
22- # so we have to trim each key to remove the username part
23- def _trim_key(self, public_key):
24- try:
25- index = public_key.index(':')
26- if index > 0:
27- return public_key[(index + 1):]
28- except Exception:
29- return public_key
30-
31 def get_data(self):
32- if not platform_reports_gce():
33- return False
34+ ret = util.log_time(
35+ LOG.debug, 'Crawl of GCE metadata service',
36+ read_md, kwargs={'address': self.metadata_address})
37
38- # url_map: (our-key, path, required, is_text)
39- url_map = [
40- ('instance-id', ('instance/id',), True, True),
41- ('availability-zone', ('instance/zone',), True, True),
42- ('local-hostname', ('instance/hostname',), True, True),
43- ('public-keys', ('project/attributes/sshKeys',
44- 'instance/attributes/ssh-keys'), False, True),
45- ('user-data', ('instance/attributes/user-data',), False, False),
46- ('user-data-encoding', ('instance/attributes/user-data-encoding',),
47- False, True),
48- ]
49-
50- # if we cannot resolve the metadata server, then no point in trying
51- if not util.is_resolvable_url(self.metadata_address):
52- LOG.debug("%s is not resolvable", self.metadata_address)
53- return False
54-
55- metadata_fetcher = GoogleMetadataFetcher(self.metadata_address)
56- # iterate over url_map keys to get metadata items
57- running_on_gce = False
58- for (mkey, paths, required, is_text) in url_map:
59- value = None
60- for path in paths:
61- new_value = metadata_fetcher.get_value(path, is_text)
62- if new_value is not None:
63- value = new_value
64- if value:
65- running_on_gce = True
66- if required and value is None:
67- msg = "required key %s returned nothing. not GCE"
68- if not running_on_gce:
69- LOG.debug(msg, mkey)
70- else:
71- LOG.warning(msg, mkey)
72- return False
73- self.metadata[mkey] = value
74-
75- if self.metadata['public-keys']:
76- lines = self.metadata['public-keys'].splitlines()
77- self.metadata['public-keys'] = [self._trim_key(k) for k in lines]
78-
79- if self.metadata['availability-zone']:
80- self.metadata['availability-zone'] = self.metadata[
81- 'availability-zone'].split('/')[-1]
82-
83- encoding = self.metadata.get('user-data-encoding')
84- if encoding:
85- if encoding == 'base64':
86- self.metadata['user-data'] = b64decode(
87- self.metadata['user-data'])
88+ if not ret['success']:
89+ if ret['platform_reports_gce']:
90+ LOG.warning(ret['reason'])
91 else:
92- LOG.warning('unknown user-data-encoding: %s, ignoring',
93- encoding)
94-
95- return running_on_gce
96+ LOG.debug(ret['reason'])
97+ return False
98+ self.metadata = ret['meta-data']
99+ self.userdata = ret['user-data']
100+ return True
101
102 @property
103 def launch_index(self):
104@@ -137,7 +81,7 @@ class DataSourceGCE(sources.DataSource):
105 return self.metadata['local-hostname'].split('.')[0]
106
107 def get_userdata_raw(self):
108- return self.metadata['user-data']
109+ return self.userdata
110
111 @property
112 def availability_zone(self):
113@@ -148,6 +92,87 @@ class DataSourceGCE(sources.DataSource):
114 return self.availability_zone.rsplit('-', 1)[0]
115
116
117+def _trim_key(public_key):
118+ # GCE takes sshKeys attribute in the format of '<user>:<public_key>'
119+ # so we have to trim each key to remove the username part
120+ try:
121+ index = public_key.index(':')
122+ if index > 0:
123+ return public_key[(index + 1):]
124+ except Exception:
125+ return public_key
126+
127+
128+def read_md(address=None, platform_check=True):
129+
130+ if address is None:
131+ address = MD_V1_URL
132+
133+ ret = {'meta-data': None, 'user-data': None,
134+ 'success': False, 'reason': None}
135+ ret['platform_reports_gce'] = platform_reports_gce()
136+
137+ if platform_check and not ret['platform_reports_gce']:
138+ ret['reason'] = "Not running on GCE."
139+ return ret
140+
141+ # if we cannot resolve the metadata server, then no point in trying
142+ if not util.is_resolvable_url(address):
143+ LOG.debug("%s is not resolvable", address)
144+ ret['reason'] = 'address "%s" is not resolvable' % address
145+ return ret
146+
147+ # url_map: (our-key, path, required, is_text)
148+ url_map = [
149+ ('instance-id', ('instance/id',), True, True),
150+ ('availability-zone', ('instance/zone',), True, True),
151+ ('local-hostname', ('instance/hostname',), True, True),
152+ ('public-keys', ('project/attributes/sshKeys',
153+ 'instance/attributes/ssh-keys'), False, True),
154+ ('user-data', ('instance/attributes/user-data',), False, False),
155+ ('user-data-encoding', ('instance/attributes/user-data-encoding',),
156+ False, True),
157+ ]
158+
159+ metadata_fetcher = GoogleMetadataFetcher(address)
160+ md = {}
161+ # iterate over url_map keys to get metadata items
162+ for (mkey, paths, required, is_text) in url_map:
163+ value = None
164+ for path in paths:
165+ new_value = metadata_fetcher.get_value(path, is_text)
166+ if new_value is not None:
167+ value = new_value
168+ if required and value is None:
169+ msg = "required key %s returned nothing. not GCE"
170+ ret['reason'] = msg % mkey
171+ return ret
172+ md[mkey] = value
173+
174+ if md['public-keys']:
175+ lines = md['public-keys'].splitlines()
176+ md['public-keys'] = [_trim_key(k) for k in lines]
177+
178+ if md['availability-zone']:
179+ md['availability-zone'] = md['availability-zone'].split('/')[-1]
180+
181+ encoding = md.get('user-data-encoding')
182+ if encoding:
183+ if encoding == 'base64':
184+ md['user-data'] = b64decode(md['user-data'])
185+ else:
186+ LOG.warning('unknown user-data-encoding: %s, ignoring', encoding)
187+
188+ if 'user-data' in md:
189+ ret['user-data'] = md['user-data']
190+ del md['user-data']
191+
192+ ret['meta-data'] = md
193+ ret['success'] = True
194+
195+ return ret
196+
197+
198 def platform_reports_gce():
199 pname = util.read_dmi_data('system-product-name') or "N/A"
200 if pname == "Google Compute Engine":
201@@ -173,4 +198,21 @@ datasources = [
202 def get_datasource_list(depends):
203 return sources.list_from_depends(depends, datasources)
204
205+
206+if __name__ == "__main__":
207+ import argparse
208+ import json
209+
210+ parser = argparse.ArgumentParser(description='Query GCE Metadata Service')
211+ parser.add_argument("--endpoint", metavar="URL",
212+ help="The url of the metadata service.",
213+ default=MD_V1_URL)
214+ parser.add_argument("--no-platform-check", dest="platform_check",
215+ help="Ignore smbios platform check",
216+ action='store_false', default=True)
217+ args = parser.parse_args()
218+ print(json.dumps(
219+ read_md(address=args.endpoint, platform_check=args.platform_check),
220+ indent=1, sort_keys=True, separators=(',', ': ')))
221+
222 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches

to all changes: