~cloud-init-dev/cloud-init/trunk

« back to all changes in this revision

Viewing changes to cloudinit/sources/DataSourceGCE.py

  • Committer: Scott Moser
  • Date: 2016-08-10 15:06:15 UTC
  • Revision ID: smoser@ubuntu.com-20160810150615-ma2fv107w3suy1ma
README: Mention move of revision control to git.

cloud-init development has moved its revision control to git.
It is available at 
  https://code.launchpad.net/cloud-init

Clone with 
  git clone https://git.launchpad.net/cloud-init
or
  git clone git+ssh://git.launchpad.net/cloud-init

For more information see
  https://git.launchpad.net/cloud-init/tree/HACKING.rst

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# vi: ts=4 expandtab
2
 
#
3
 
#    Author: Vaidas Jablonskis <jablonskis@gmail.com>
4
 
#
5
 
#    This program is free software: you can redistribute it and/or modify
6
 
#    it under the terms of the GNU General Public License version 3, as
7
 
#    published by the Free Software Foundation.
8
 
#
9
 
#    This program is distributed in the hope that it will be useful,
10
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
#    GNU General Public License for more details.
13
 
#
14
 
#    You should have received a copy of the GNU General Public License
15
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 
 
17
 
 
18
 
from base64 import b64decode
19
 
 
20
 
from cloudinit import log as logging
21
 
from cloudinit import sources
22
 
from cloudinit import url_helper
23
 
from cloudinit import util
24
 
 
25
 
LOG = logging.getLogger(__name__)
26
 
 
27
 
BUILTIN_DS_CONFIG = {
28
 
    'metadata_url': 'http://metadata.google.internal/computeMetadata/v1/'
29
 
}
30
 
REQUIRED_FIELDS = ('instance-id', 'availability-zone', 'local-hostname')
31
 
 
32
 
 
33
 
class GoogleMetadataFetcher(object):
34
 
    headers = {'X-Google-Metadata-Request': True}
35
 
 
36
 
    def __init__(self, metadata_address):
37
 
        self.metadata_address = metadata_address
38
 
 
39
 
    def get_value(self, path, is_text):
40
 
        value = None
41
 
        try:
42
 
            resp = url_helper.readurl(url=self.metadata_address + path,
43
 
                                      headers=self.headers)
44
 
        except url_helper.UrlError as exc:
45
 
            msg = "url %s raised exception %s"
46
 
            LOG.debug(msg, path, exc)
47
 
        else:
48
 
            if resp.code == 200:
49
 
                if is_text:
50
 
                    value = util.decode_binary(resp.contents)
51
 
                else:
52
 
                    value = resp.contents
53
 
            else:
54
 
                LOG.debug("url %s returned code %s", path, resp.code)
55
 
        return value
56
 
 
57
 
 
58
 
class DataSourceGCE(sources.DataSource):
59
 
    def __init__(self, sys_cfg, distro, paths):
60
 
        sources.DataSource.__init__(self, sys_cfg, distro, paths)
61
 
        self.metadata = dict()
62
 
        self.ds_cfg = util.mergemanydict([
63
 
            util.get_cfg_by_path(sys_cfg, ["datasource", "GCE"], {}),
64
 
            BUILTIN_DS_CONFIG])
65
 
        self.metadata_address = self.ds_cfg['metadata_url']
66
 
 
67
 
    # GCE takes sshKeys attribute in the format of '<user>:<public_key>'
68
 
    # so we have to trim each key to remove the username part
69
 
    def _trim_key(self, public_key):
70
 
        try:
71
 
            index = public_key.index(':')
72
 
            if index > 0:
73
 
                return public_key[(index + 1):]
74
 
        except Exception:
75
 
            return public_key
76
 
 
77
 
    def get_data(self):
78
 
        # url_map: (our-key, path, required, is_text)
79
 
        url_map = [
80
 
            ('instance-id', ('instance/id',), True, True),
81
 
            ('availability-zone', ('instance/zone',), True, True),
82
 
            ('local-hostname', ('instance/hostname',), True, True),
83
 
            ('public-keys', ('project/attributes/sshKeys',
84
 
                             'instance/attributes/sshKeys'), False, True),
85
 
            ('user-data', ('instance/attributes/user-data',), False, False),
86
 
            ('user-data-encoding', ('instance/attributes/user-data-encoding',),
87
 
             False, True),
88
 
        ]
89
 
 
90
 
        # if we cannot resolve the metadata server, then no point in trying
91
 
        if not util.is_resolvable_url(self.metadata_address):
92
 
            LOG.debug("%s is not resolvable", self.metadata_address)
93
 
            return False
94
 
 
95
 
        metadata_fetcher = GoogleMetadataFetcher(self.metadata_address)
96
 
        # iterate over url_map keys to get metadata items
97
 
        running_on_gce = False
98
 
        for (mkey, paths, required, is_text) in url_map:
99
 
            value = None
100
 
            for path in paths:
101
 
                new_value = metadata_fetcher.get_value(path, is_text)
102
 
                if new_value is not None:
103
 
                    value = new_value
104
 
            if value:
105
 
                running_on_gce = True
106
 
            if required and value is None:
107
 
                msg = "required key %s returned nothing. not GCE"
108
 
                if not running_on_gce:
109
 
                    LOG.debug(msg, mkey)
110
 
                else:
111
 
                    LOG.warn(msg, mkey)
112
 
                return False
113
 
            self.metadata[mkey] = value
114
 
 
115
 
        if self.metadata['public-keys']:
116
 
            lines = self.metadata['public-keys'].splitlines()
117
 
            self.metadata['public-keys'] = [self._trim_key(k) for k in lines]
118
 
 
119
 
        if self.metadata['availability-zone']:
120
 
            self.metadata['availability-zone'] = self.metadata[
121
 
                'availability-zone'].split('/')[-1]
122
 
 
123
 
        encoding = self.metadata.get('user-data-encoding')
124
 
        if encoding:
125
 
            if encoding == 'base64':
126
 
                self.metadata['user-data'] = b64decode(
127
 
                    self.metadata['user-data'])
128
 
            else:
129
 
                LOG.warn('unknown user-data-encoding: %s, ignoring', encoding)
130
 
 
131
 
        return running_on_gce
132
 
 
133
 
    @property
134
 
    def launch_index(self):
135
 
        # GCE does not provide lauch_index property
136
 
        return None
137
 
 
138
 
    def get_instance_id(self):
139
 
        return self.metadata['instance-id']
140
 
 
141
 
    def get_public_ssh_keys(self):
142
 
        return self.metadata['public-keys']
143
 
 
144
 
    def get_hostname(self, fqdn=False, resolve_ip=False):
145
 
        # GCE has long FDQN's and has asked for short hostnames
146
 
        return self.metadata['local-hostname'].split('.')[0]
147
 
 
148
 
    def get_userdata_raw(self):
149
 
        return self.metadata['user-data']
150
 
 
151
 
    @property
152
 
    def availability_zone(self):
153
 
        return self.metadata['availability-zone']
154
 
 
155
 
    @property
156
 
    def region(self):
157
 
        return self.availability_zone.rsplit('-', 1)[0]
158
 
 
159
 
# Used to match classes to dependencies
160
 
datasources = [
161
 
    (DataSourceGCE, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
162
 
]
163
 
 
164
 
 
165
 
# Return a list of data sources that match this set of dependencies
166
 
def get_datasource_list(depends):
167
 
    return sources.list_from_depends(depends, datasources)