googlecloud 0.0.2 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/CHANGELOG +4 -0
- data/LICENSE +674 -0
- data/Manifest +111 -0
- data/README.md +4 -3
- data/bin/gcutil +53 -0
- data/googlecloud.gemspec +4 -3
- data/packages/gcutil-1.7.1/CHANGELOG +197 -0
- data/packages/gcutil-1.7.1/LICENSE +202 -0
- data/packages/gcutil-1.7.1/VERSION +1 -0
- data/packages/gcutil-1.7.1/gcutil +53 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/LICENSE +23 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/__init__.py +1 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/discovery.py +743 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/errors.py +123 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/ext/__init__.py +0 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/http.py +1443 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/mimeparse.py +172 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/model.py +385 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/schema.py +303 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/__init__.py +1 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/anyjson.py +32 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/appengine.py +528 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/client.py +1139 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/clientsecrets.py +105 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/crypt.py +244 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/django_orm.py +124 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/file.py +107 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/locked_file.py +343 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/multistore_file.py +379 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/tools.py +174 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/uritemplate/__init__.py +147 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/LICENSE +202 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/__init__.py +3 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/__init__.py +3 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/app.py +356 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/appcommands.py +783 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/basetest.py +1260 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/datelib.py +421 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/debug.py +60 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/file_util.py +181 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/resources.py +67 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/run_script_module.py +217 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/setup_command.py +159 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/shellutil.py +49 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/stopwatch.py +204 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/__init__.py +0 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auth_helper.py +140 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auth_helper_test.py +149 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auto_auth.py +130 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auto_auth_test.py +75 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/basic_cmds.py +128 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/basic_cmds_test.py +111 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/command_base.py +1808 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/command_base_test.py +1651 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/compute/v1beta13.json +2851 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/compute/v1beta14.json +3361 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/disk_cmds.py +342 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/disk_cmds_test.py +474 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/firewall_cmds.py +344 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/firewall_cmds_test.py +231 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/flags_cache.py +274 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/gcutil +89 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/gcutil_logging.py +69 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/image_cmds.py +262 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/image_cmds_test.py +172 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/instance_cmds.py +1506 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/instance_cmds_test.py +1904 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/kernel_cmds.py +91 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/kernel_cmds_test.py +56 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/machine_type_cmds.py +106 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/machine_type_cmds_test.py +59 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata.py +96 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata_lib.py +357 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata_test.py +84 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/mock_api.py +420 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/mock_metadata.py +58 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/move_cmds.py +824 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/move_cmds_test.py +307 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/network_cmds.py +178 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/network_cmds_test.py +133 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/operation_cmds.py +181 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/operation_cmds_test.py +196 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/path_initializer.py +38 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/project_cmds.py +173 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/project_cmds_test.py +111 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/scopes.py +61 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/scopes_test.py +50 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/snapshot_cmds.py +276 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/snapshot_cmds_test.py +260 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/ssh_keys.py +266 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/ssh_keys_test.py +128 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/table_formatter.py +563 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/thread_pool.py +188 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/thread_pool_test.py +88 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/utils.py +208 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/utils_test.py +193 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version.py +17 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version_checker.py +246 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version_checker_test.py +271 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/zone_cmds.py +151 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/zone_cmds_test.py +60 -0
- data/packages/gcutil-1.7.1/lib/httplib2/LICENSE +21 -0
- data/packages/gcutil-1.7.1/lib/httplib2/httplib2/__init__.py +1630 -0
- data/packages/gcutil-1.7.1/lib/httplib2/httplib2/cacerts.txt +714 -0
- data/packages/gcutil-1.7.1/lib/httplib2/httplib2/iri2uri.py +110 -0
- data/packages/gcutil-1.7.1/lib/httplib2/httplib2/socks.py +438 -0
- data/packages/gcutil-1.7.1/lib/iso8601/LICENSE +20 -0
- data/packages/gcutil-1.7.1/lib/iso8601/iso8601/__init__.py +1 -0
- data/packages/gcutil-1.7.1/lib/iso8601/iso8601/iso8601.py +102 -0
- data/packages/gcutil-1.7.1/lib/iso8601/iso8601/test_iso8601.py +111 -0
- data/packages/gcutil-1.7.1/lib/python_gflags/AUTHORS +2 -0
- data/packages/gcutil-1.7.1/lib/python_gflags/LICENSE +28 -0
- data/packages/gcutil-1.7.1/lib/python_gflags/gflags.py +2862 -0
- data/packages/gcutil-1.7.1/lib/python_gflags/gflags2man.py +544 -0
- data/packages/gcutil-1.7.1/lib/python_gflags/gflags_validators.py +187 -0
- metadata +118 -5
- metadata.gz.sig +0 -0
@@ -0,0 +1,193 @@
|
|
1
|
+
"""Unit tests for the utils module."""
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
import path_initializer
|
6
|
+
path_initializer.InitializeSysPath()
|
7
|
+
|
8
|
+
import unittest
|
9
|
+
|
10
|
+
from gcutil import mock_api
|
11
|
+
from gcutil import utils
|
12
|
+
|
13
|
+
|
14
|
+
class FlattenListTests(unittest.TestCase):
|
15
|
+
"""Tests for utils.FlattenList."""
|
16
|
+
test_cases = (
|
17
|
+
([[1], [2], [3]], [1, 2, 3]),
|
18
|
+
([[1, 2, 3], [4, 5], [6]], [1, 2, 3, 4, 5, 6]),
|
19
|
+
([['a'], [' b '], ['c ']], ['a', ' b ', 'c ']),
|
20
|
+
)
|
21
|
+
|
22
|
+
def testFlattenList(self):
|
23
|
+
for arg, expected in self.test_cases:
|
24
|
+
self.assertEqual(utils.FlattenList(arg), expected)
|
25
|
+
|
26
|
+
|
27
|
+
class GlobsToFilterTests(unittest.TestCase):
|
28
|
+
"""Tests for utils.RegexesToFilterExpression."""
|
29
|
+
test_cases = (
|
30
|
+
(None, None),
|
31
|
+
([], None),
|
32
|
+
(['instance-1'], 'name eq instance-1'),
|
33
|
+
(['instance-1 instance-2'], 'name eq instance-1|instance-2'),
|
34
|
+
(['instance-1', 'i.*'], 'name eq instance-1|i.*'),
|
35
|
+
(['a', 'b', 'c'], 'name eq a|b|c'),
|
36
|
+
(['a b c'], 'name eq a|b|c'),
|
37
|
+
(['a b c', 'd e f'], 'name eq a|b|c|d|e|f'),
|
38
|
+
(['instance-[0-9]+'], 'name eq instance-[0-9]+'),
|
39
|
+
(['a-[0-9]+', 'b-[0-9]+'], 'name eq a-[0-9]+|b-[0-9]+'),
|
40
|
+
([' a-[0-9]+ b-[0-9]+ '], 'name eq a-[0-9]+|b-[0-9]+'),
|
41
|
+
)
|
42
|
+
|
43
|
+
def testRegexesToFilterExpression(self):
|
44
|
+
for arg, expected in self.test_cases:
|
45
|
+
self.assertEqual(utils.RegexesToFilterExpression(arg), expected)
|
46
|
+
|
47
|
+
|
48
|
+
class ProtocolPortsTests(unittest.TestCase):
|
49
|
+
|
50
|
+
def testParseProtocolFailures(self):
|
51
|
+
failure_cases = (
|
52
|
+
None, '', 'foo'
|
53
|
+
)
|
54
|
+
for failure_case in failure_cases:
|
55
|
+
self.assertRaises(ValueError, utils.ParseProtocol, failure_case)
|
56
|
+
|
57
|
+
def testParseProtocolSuccesses(self):
|
58
|
+
test_cases = (
|
59
|
+
(6, 6),
|
60
|
+
('6', 6),
|
61
|
+
('tcp', 6),
|
62
|
+
('udp', 17)
|
63
|
+
)
|
64
|
+
for arg, expected in test_cases:
|
65
|
+
self.assertEqual(utils.ParseProtocol(arg), expected)
|
66
|
+
|
67
|
+
def testReplacePortNamesFailures(self):
|
68
|
+
failure_cases = (
|
69
|
+
None, 22, '', 'foo', 'foo-bar', '24-42-2442'
|
70
|
+
)
|
71
|
+
for failure_case in failure_cases:
|
72
|
+
self.assertRaises(ValueError, utils.ReplacePortNames, failure_case)
|
73
|
+
|
74
|
+
def testReplacePortNameSuccesses(self):
|
75
|
+
test_cases = (
|
76
|
+
('ssh', '22'),
|
77
|
+
('22', '22'),
|
78
|
+
('ssh-http', '22-80'),
|
79
|
+
('22-http', '22-80'),
|
80
|
+
('ssh-80', '22-80'),
|
81
|
+
('22-80', '22-80')
|
82
|
+
)
|
83
|
+
for arg, expected in test_cases:
|
84
|
+
self.assertEqual(utils.ReplacePortNames(arg), expected)
|
85
|
+
|
86
|
+
|
87
|
+
class SingularizeTests(unittest.TestCase):
|
88
|
+
"""Tests for utils.Singularize."""
|
89
|
+
|
90
|
+
test_cases = (
|
91
|
+
('instances', 'instance'),
|
92
|
+
('disks', 'disk'),
|
93
|
+
('firewalls', 'firewall'),
|
94
|
+
('snapshots', 'snapshot'),
|
95
|
+
('operations', 'operation'),
|
96
|
+
('images', 'image'),
|
97
|
+
('kernels', 'kernel'),
|
98
|
+
('networks', 'network'),
|
99
|
+
('machineTypes', 'machineType'),
|
100
|
+
('backendGroups', 'backendGroup'),
|
101
|
+
('publicEndpoints', 'publicEndpoint'),
|
102
|
+
)
|
103
|
+
|
104
|
+
def testSingularize(self):
|
105
|
+
for arg, expected in self.test_cases:
|
106
|
+
self.assertEqual(utils.Singularize(arg), expected)
|
107
|
+
self.assertEqual(utils.Singularize(expected), expected)
|
108
|
+
|
109
|
+
|
110
|
+
class AllTests(unittest.TestCase):
|
111
|
+
"""Tests for utils.All."""
|
112
|
+
|
113
|
+
def setUp(self):
|
114
|
+
self._page = 0
|
115
|
+
|
116
|
+
def testArgumentPlumbing(self):
|
117
|
+
|
118
|
+
def mockFunc(project=None, maxResults=None, filter=None, pageToken=None):
|
119
|
+
self.assertEqual(project, 'my-project')
|
120
|
+
self.assertEqual(maxResults, 651)
|
121
|
+
self.assertEqual(filter, 'name eq my-instance')
|
122
|
+
self.assertEqual(pageToken, None)
|
123
|
+
return mock_api.MockRequest(
|
124
|
+
{'kind': 'numbers', 'items': [1, 2, 3]})
|
125
|
+
|
126
|
+
utils.All(mockFunc, 'my-project',
|
127
|
+
max_results=651,
|
128
|
+
filter='name eq my-instance')
|
129
|
+
|
130
|
+
def testWithZones(self):
|
131
|
+
def mockFunc(project=None, maxResults=None, filter=None, pageToken=None,
|
132
|
+
zone=None):
|
133
|
+
self.assertEqual('some-zone', zone)
|
134
|
+
return mock_api.MockRequest(
|
135
|
+
{'kind': 'numbers', 'items': [1, 2, 3]})
|
136
|
+
|
137
|
+
utils.All(mockFunc, 'my-project', zone='some-zone')
|
138
|
+
|
139
|
+
def testWithEmptyResponse(self):
|
140
|
+
|
141
|
+
def mockFunc(project=None, maxResults=None, filter=None, pageToken=None):
|
142
|
+
return mock_api.MockRequest({'kind': 'numbers', 'items': []})
|
143
|
+
|
144
|
+
self.assertEqual(utils.All(mockFunc, 'my-project'),
|
145
|
+
{'kind': 'numbers', 'items': []})
|
146
|
+
|
147
|
+
def testWithNoPaging(self):
|
148
|
+
|
149
|
+
def mockFunc(project=None, maxResults=None, filter=None, pageToken=None):
|
150
|
+
return mock_api.MockRequest({'kind': 'numbers', 'items': [1, 2, 3]})
|
151
|
+
|
152
|
+
self.assertEqual(utils.All(mockFunc, 'my-project'),
|
153
|
+
{'kind': 'numbers', 'items': [1, 2, 3]})
|
154
|
+
|
155
|
+
def testWithPaging(self):
|
156
|
+
responses = [
|
157
|
+
mock_api.MockRequest(
|
158
|
+
{'kind': 'numbers', 'items': [1, 2, 3], 'nextPageToken': 'abc'}),
|
159
|
+
mock_api.MockRequest(
|
160
|
+
{'kind': 'numbers', 'items': [4, 5, 6]})]
|
161
|
+
|
162
|
+
def mockFunc(project=None, maxResults=None, filter=None, pageToken=None):
|
163
|
+
self._page += 1
|
164
|
+
return responses[self._page - 1]
|
165
|
+
|
166
|
+
self.assertEqual(utils.All(mockFunc, 'my-project'),
|
167
|
+
{'kind': 'numbers', 'items': [1, 2, 3, 4, 5, 6]})
|
168
|
+
|
169
|
+
def testWithNoPagingAndSlicing(self):
|
170
|
+
|
171
|
+
def mockFunc(project=None, maxResults=None, filter=None, pageToken=None):
|
172
|
+
return mock_api.MockRequest({'kind': 'numbers', 'items': [1, 2, 3]})
|
173
|
+
|
174
|
+
self.assertEqual(utils.All(mockFunc, 'my-project', max_results=2),
|
175
|
+
{'kind': 'numbers', 'items': [1, 2]})
|
176
|
+
|
177
|
+
def testWithPagingAndSlicing(self):
|
178
|
+
responses = [
|
179
|
+
mock_api.MockRequest(
|
180
|
+
{'kind': 'numbers', 'items': [1, 2, 3], 'nextPageToken': 'abc'}),
|
181
|
+
mock_api.MockRequest(
|
182
|
+
{'kind': 'numbers', 'items': [4, 5, 6]})]
|
183
|
+
|
184
|
+
def mockFunc(project=None, maxResults=None, filter=None, pageToken=None):
|
185
|
+
self._page += 1
|
186
|
+
return responses[self._page - 1]
|
187
|
+
|
188
|
+
self.assertEqual(utils.All(mockFunc, 'my-project', max_results=5),
|
189
|
+
{'kind': 'numbers', 'items': [1, 2, 3, 4, 5]})
|
190
|
+
|
191
|
+
|
192
|
+
if __name__ == '__main__':
|
193
|
+
unittest.main()
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Copyright 2012 Google Inc. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
__version__ = '1.7.1'
|
16
|
+
__supported_api_versions__ = ['v1beta13', 'v1beta14']
|
17
|
+
__default_api_version__ = 'v1beta14'
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# Copyright 2012 Google Inc. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
"""A module can check for new versions of gcutil.
|
16
|
+
|
17
|
+
A JSON file located at VERSION_INFO_URL contains the version number of
|
18
|
+
the latest version of gcutil.
|
19
|
+
"""
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
import json
|
24
|
+
import logging
|
25
|
+
import os
|
26
|
+
import time
|
27
|
+
|
28
|
+
import httplib2
|
29
|
+
|
30
|
+
import gflags as flags
|
31
|
+
|
32
|
+
from gcutil import gcutil_logging
|
33
|
+
from gcutil import version
|
34
|
+
|
35
|
+
LOGGER = gcutil_logging.LOGGER
|
36
|
+
VERSION_INFO_URL = 'http://dl.google.com/compute/latest-version.json'
|
37
|
+
VERSION_CACHE_FILE = '~/.gcutil.version'
|
38
|
+
SETUP_DOC_URL = 'https://developers.google.com/compute/docs/gcutil'
|
39
|
+
TIMEOUT_IN_SEC = 1
|
40
|
+
|
41
|
+
# The minimum amount of time that can pass between visits to
|
42
|
+
# VERSION_INFO_URL to grab the latest version string.
|
43
|
+
CACHE_TTL_SEC = 24 * 60 * 60
|
44
|
+
|
45
|
+
FLAGS = flags.FLAGS
|
46
|
+
|
47
|
+
flags.DEFINE_boolean('check_for_new_version',
|
48
|
+
True,
|
49
|
+
'Perform an update check.')
|
50
|
+
|
51
|
+
|
52
|
+
class VersionChecker(object):
|
53
|
+
"""A class that encapsulates the logic for performing version checks."""
|
54
|
+
|
55
|
+
def __init__(
|
56
|
+
self,
|
57
|
+
perform_check=FLAGS.check_for_new_version,
|
58
|
+
cache_path=VERSION_CACHE_FILE,
|
59
|
+
cache_ttl_sec=CACHE_TTL_SEC,
|
60
|
+
current_version=version.__version__):
|
61
|
+
"""Constructs a new VersionChecker.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
perform_check: Skips the check if False.
|
65
|
+
cache_path: The path to a file that caches the results of
|
66
|
+
fetching VERSION_INFO_URL.
|
67
|
+
cache_ttl_sec: The maximum amount of time the cache is considered
|
68
|
+
valid.
|
69
|
+
"""
|
70
|
+
self._perform_check = perform_check
|
71
|
+
self._cache_path = os.path.expanduser(cache_path)
|
72
|
+
self._cache_ttl_sec = cache_ttl_sec
|
73
|
+
self._current_version = current_version
|
74
|
+
|
75
|
+
@staticmethod
|
76
|
+
def _IsCacheMalformed(cache):
|
77
|
+
"""Returns True if the given cache is not in its expected form."""
|
78
|
+
if ('last_check' not in cache or
|
79
|
+
'current_version' not in cache or
|
80
|
+
'last_checked_version' not in cache):
|
81
|
+
return True
|
82
|
+
|
83
|
+
if not isinstance(cache['last_check'], float):
|
84
|
+
return True
|
85
|
+
|
86
|
+
try:
|
87
|
+
VersionChecker._ParseVersionString(cache['current_version'])
|
88
|
+
VersionChecker._ParseVersionString(cache['last_checked_version'])
|
89
|
+
except BaseException:
|
90
|
+
return True
|
91
|
+
|
92
|
+
return False
|
93
|
+
|
94
|
+
def _IsCacheStale(self, cache, current_time=None):
|
95
|
+
"""Returns True if the cache is stale."""
|
96
|
+
if VersionChecker._IsCacheMalformed(cache):
|
97
|
+
LOGGER.debug('Encountered malformed or empty cache: %s', cache)
|
98
|
+
return True
|
99
|
+
|
100
|
+
# If the gcutil version has changed since the last cache write, then
|
101
|
+
# the cache is stale.
|
102
|
+
if cache['current_version'] != self._current_version:
|
103
|
+
return True
|
104
|
+
|
105
|
+
current_time = time.time() if current_time is None else current_time
|
106
|
+
|
107
|
+
# If the cache is old, then it's stale.
|
108
|
+
if cache['last_check'] + self._cache_ttl_sec <= current_time:
|
109
|
+
return True
|
110
|
+
|
111
|
+
# If for some reason the current time is less than the last time
|
112
|
+
# the cache was written to (e.g., the user changed his or her
|
113
|
+
# system time), then the safest thing to do is to assume the cache
|
114
|
+
# is stale.
|
115
|
+
if cache['last_check'] > current_time:
|
116
|
+
return True
|
117
|
+
|
118
|
+
return False
|
119
|
+
|
120
|
+
@staticmethod
|
121
|
+
def _ParseVersionString(version_string):
|
122
|
+
"""Converts a version string into a tuple of its components.
|
123
|
+
|
124
|
+
For example, '1.2.0' -> (1, 2, 0).
|
125
|
+
|
126
|
+
Args:
|
127
|
+
version_string: The input.
|
128
|
+
|
129
|
+
Raises:
|
130
|
+
ValueError: If any of the version components are not integers.
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
A tuple of the version components.
|
134
|
+
"""
|
135
|
+
try:
|
136
|
+
return tuple([int(i) for i in version_string.split('.')])
|
137
|
+
except ValueError as e:
|
138
|
+
raise ValueError('Could not parse version string %s: %s' %
|
139
|
+
(version_string, e))
|
140
|
+
|
141
|
+
@staticmethod
|
142
|
+
def _CompareVersions(left, right):
|
143
|
+
"""Returns True if the left version is less than the right version."""
|
144
|
+
return (VersionChecker._ParseVersionString(left) <
|
145
|
+
VersionChecker._ParseVersionString(right))
|
146
|
+
|
147
|
+
def _UpdateCache(self, cache, http=None, current_time=None):
|
148
|
+
"""Fetches the version info and updates the given cache dict.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
cache: A dict representing the contents of the cache.
|
152
|
+
http: An httplib2.Http object. This is used for testing.
|
153
|
+
current_time: The current time since the Epoch, in seconds.
|
154
|
+
This is also used for testing.
|
155
|
+
|
156
|
+
Raises:
|
157
|
+
ValueError: If the response code is not 200.
|
158
|
+
"""
|
159
|
+
http = http or httplib2.Http(timeout=TIMEOUT_IN_SEC)
|
160
|
+
response, content = http.request(
|
161
|
+
VERSION_INFO_URL, headers={'Cache-Control': 'no-cache'})
|
162
|
+
LOGGER.debug('Version check response: %s', response)
|
163
|
+
LOGGER.debug('Version check payload: %s', content)
|
164
|
+
if response.status != 200:
|
165
|
+
raise ValueError('Received response code %s while fetching %s.',
|
166
|
+
response.status, VERSION_INFO_URL)
|
167
|
+
|
168
|
+
latest_version = json.loads(content)['version']
|
169
|
+
cache['current_version'] = self._current_version
|
170
|
+
cache['last_checked_version'] = latest_version
|
171
|
+
cache['last_check'] = current_time or time.time()
|
172
|
+
|
173
|
+
def _ReadCache(self):
|
174
|
+
"""Reads the contents of the version cache file.
|
175
|
+
|
176
|
+
Returns:
|
177
|
+
A dict that corresponds to the JSON stored in the cache file.
|
178
|
+
Returns an empty dict if the cache file does not exist or if
|
179
|
+
there is a problem reading/parsing the cache.
|
180
|
+
"""
|
181
|
+
if not os.path.exists(self._cache_path):
|
182
|
+
return {}
|
183
|
+
|
184
|
+
try:
|
185
|
+
with open(self._cache_path) as f:
|
186
|
+
return json.load(f)
|
187
|
+
except BaseException as e:
|
188
|
+
LOGGER.debug('Reading %s failed: %s', self._cache_path, e)
|
189
|
+
|
190
|
+
return {}
|
191
|
+
|
192
|
+
def _WriteToCache(self, cache):
|
193
|
+
"""JSON-serializes the given dict and writes it to the cache."""
|
194
|
+
with open(self._cache_path, 'w') as f:
|
195
|
+
json.dump(cache, f)
|
196
|
+
|
197
|
+
def _NewVersionExists(self):
|
198
|
+
"""Returns True if a new gcutil version exists."""
|
199
|
+
cache = self._ReadCache()
|
200
|
+
if self._IsCacheStale(cache):
|
201
|
+
LOGGER.debug('%s is stale. Consulting %s for latest version info...',
|
202
|
+
self._cache_path, VERSION_INFO_URL)
|
203
|
+
self._UpdateCache(cache)
|
204
|
+
self._WriteToCache(cache)
|
205
|
+
else:
|
206
|
+
LOGGER.debug('Consulting %s for latest version info...', self._cache_path)
|
207
|
+
|
208
|
+
latest_version = cache['last_checked_version']
|
209
|
+
ret = self._CompareVersions(self._current_version, latest_version)
|
210
|
+
return ret
|
211
|
+
|
212
|
+
def CheckForNewVersion(self):
|
213
|
+
"""Performs the actual check for a new version.
|
214
|
+
|
215
|
+
This method may either consult the cache or the web, depending on
|
216
|
+
the cache's age.
|
217
|
+
|
218
|
+
The side-effect of this message is a WARN log that tells the user
|
219
|
+
of an old version.
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
True if version checking was requested and a new version is
|
223
|
+
available.
|
224
|
+
"""
|
225
|
+
if not self._perform_check:
|
226
|
+
logging.debug('Skipping version check...')
|
227
|
+
return
|
228
|
+
|
229
|
+
LOGGER.debug('Performing version check...')
|
230
|
+
|
231
|
+
try:
|
232
|
+
if self._NewVersionExists():
|
233
|
+
LOGGER.warning(
|
234
|
+
'There is a new version of gcutil available. Go to: %s',
|
235
|
+
SETUP_DOC_URL)
|
236
|
+
LOGGER.warning(
|
237
|
+
'Your version of gcutil is %s, the latest version is %s.',
|
238
|
+
version.__version__, latest_version)
|
239
|
+
else:
|
240
|
+
LOGGER.debug('gcutil is up-to-date.')
|
241
|
+
|
242
|
+
# So much can go wrong with this code that it's unreasonable to
|
243
|
+
# add error handling everywhere hence the "catch-all" exception
|
244
|
+
# handling.
|
245
|
+
except BaseException as e:
|
246
|
+
LOGGER.debug('Version checking failed: %s', e)
|