gcloud 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data.tar.gz.sig +2 -3
  2. data/CHANGELOG +4 -0
  3. data/LICENSE +674 -0
  4. data/Manifest +111 -0
  5. data/README.md +4 -3
  6. data/bin/gcutil +53 -0
  7. data/gcloud.gemspec +4 -3
  8. data/packages/gcutil-1.7.1/CHANGELOG +197 -0
  9. data/packages/gcutil-1.7.1/LICENSE +202 -0
  10. data/packages/gcutil-1.7.1/VERSION +1 -0
  11. data/packages/gcutil-1.7.1/gcutil +53 -0
  12. data/packages/gcutil-1.7.1/lib/google_api_python_client/LICENSE +23 -0
  13. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/__init__.py +1 -0
  14. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/discovery.py +743 -0
  15. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/errors.py +123 -0
  16. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/ext/__init__.py +0 -0
  17. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/http.py +1443 -0
  18. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/mimeparse.py +172 -0
  19. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/model.py +385 -0
  20. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/schema.py +303 -0
  21. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/__init__.py +1 -0
  22. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/anyjson.py +32 -0
  23. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/appengine.py +528 -0
  24. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/client.py +1139 -0
  25. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/clientsecrets.py +105 -0
  26. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/crypt.py +244 -0
  27. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/django_orm.py +124 -0
  28. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/file.py +107 -0
  29. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/locked_file.py +343 -0
  30. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/multistore_file.py +379 -0
  31. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/tools.py +174 -0
  32. data/packages/gcutil-1.7.1/lib/google_api_python_client/uritemplate/__init__.py +147 -0
  33. data/packages/gcutil-1.7.1/lib/google_apputils/LICENSE +202 -0
  34. data/packages/gcutil-1.7.1/lib/google_apputils/google/__init__.py +3 -0
  35. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/__init__.py +3 -0
  36. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/app.py +356 -0
  37. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/appcommands.py +783 -0
  38. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/basetest.py +1260 -0
  39. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/datelib.py +421 -0
  40. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/debug.py +60 -0
  41. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/file_util.py +181 -0
  42. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/resources.py +67 -0
  43. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/run_script_module.py +217 -0
  44. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/setup_command.py +159 -0
  45. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/shellutil.py +49 -0
  46. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/stopwatch.py +204 -0
  47. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/__init__.py +0 -0
  48. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auth_helper.py +140 -0
  49. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auth_helper_test.py +149 -0
  50. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auto_auth.py +130 -0
  51. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auto_auth_test.py +75 -0
  52. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/basic_cmds.py +128 -0
  53. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/basic_cmds_test.py +111 -0
  54. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/command_base.py +1808 -0
  55. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/command_base_test.py +1651 -0
  56. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/compute/v1beta13.json +2851 -0
  57. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/compute/v1beta14.json +3361 -0
  58. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/disk_cmds.py +342 -0
  59. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/disk_cmds_test.py +474 -0
  60. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/firewall_cmds.py +344 -0
  61. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/firewall_cmds_test.py +231 -0
  62. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/flags_cache.py +274 -0
  63. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/gcutil +89 -0
  64. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/gcutil_logging.py +69 -0
  65. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/image_cmds.py +262 -0
  66. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/image_cmds_test.py +172 -0
  67. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/instance_cmds.py +1506 -0
  68. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/instance_cmds_test.py +1904 -0
  69. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/kernel_cmds.py +91 -0
  70. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/kernel_cmds_test.py +56 -0
  71. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/machine_type_cmds.py +106 -0
  72. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/machine_type_cmds_test.py +59 -0
  73. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata.py +96 -0
  74. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata_lib.py +357 -0
  75. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata_test.py +84 -0
  76. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/mock_api.py +420 -0
  77. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/mock_metadata.py +58 -0
  78. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/move_cmds.py +824 -0
  79. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/move_cmds_test.py +307 -0
  80. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/network_cmds.py +178 -0
  81. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/network_cmds_test.py +133 -0
  82. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/operation_cmds.py +181 -0
  83. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/operation_cmds_test.py +196 -0
  84. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/path_initializer.py +38 -0
  85. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/project_cmds.py +173 -0
  86. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/project_cmds_test.py +111 -0
  87. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/scopes.py +61 -0
  88. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/scopes_test.py +50 -0
  89. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/snapshot_cmds.py +276 -0
  90. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/snapshot_cmds_test.py +260 -0
  91. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/ssh_keys.py +266 -0
  92. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/ssh_keys_test.py +128 -0
  93. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/table_formatter.py +563 -0
  94. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/thread_pool.py +188 -0
  95. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/thread_pool_test.py +88 -0
  96. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/utils.py +208 -0
  97. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/utils_test.py +193 -0
  98. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version.py +17 -0
  99. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version_checker.py +246 -0
  100. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version_checker_test.py +271 -0
  101. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/zone_cmds.py +151 -0
  102. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/zone_cmds_test.py +60 -0
  103. data/packages/gcutil-1.7.1/lib/httplib2/LICENSE +21 -0
  104. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/__init__.py +1630 -0
  105. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/cacerts.txt +714 -0
  106. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/iri2uri.py +110 -0
  107. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/socks.py +438 -0
  108. data/packages/gcutil-1.7.1/lib/iso8601/LICENSE +20 -0
  109. data/packages/gcutil-1.7.1/lib/iso8601/iso8601/__init__.py +1 -0
  110. data/packages/gcutil-1.7.1/lib/iso8601/iso8601/iso8601.py +102 -0
  111. data/packages/gcutil-1.7.1/lib/iso8601/iso8601/test_iso8601.py +111 -0
  112. data/packages/gcutil-1.7.1/lib/python_gflags/AUTHORS +2 -0
  113. data/packages/gcutil-1.7.1/lib/python_gflags/LICENSE +28 -0
  114. data/packages/gcutil-1.7.1/lib/python_gflags/gflags.py +2862 -0
  115. data/packages/gcutil-1.7.1/lib/python_gflags/gflags2man.py +544 -0
  116. data/packages/gcutil-1.7.1/lib/python_gflags/gflags_validators.py +187 -0
  117. metadata +118 -5
  118. 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)