gcloud 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)