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.
- data.tar.gz.sig +2 -3
- data/CHANGELOG +4 -0
- data/LICENSE +674 -0
- data/Manifest +111 -0
- data/README.md +4 -3
- data/bin/gcutil +53 -0
- data/gcloud.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,260 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2012 Google Inc. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
"""Unit tests for the persistent disk snapshot commands."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
import path_initializer
|
|
22
|
+
path_initializer.InitializeSysPath()
|
|
23
|
+
|
|
24
|
+
import copy
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
import gflags as flags
|
|
28
|
+
import unittest
|
|
29
|
+
|
|
30
|
+
from gcutil import command_base
|
|
31
|
+
from gcutil import mock_api
|
|
32
|
+
from gcutil import snapshot_cmds
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
FLAGS = flags.FLAGS
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SnapshotCmdsTest(unittest.TestCase):
|
|
39
|
+
|
|
40
|
+
def _DoTestAddSnapshotGeneratesCorrectRequest(self, service_version):
|
|
41
|
+
flag_values = copy.deepcopy(FLAGS)
|
|
42
|
+
|
|
43
|
+
command = snapshot_cmds.AddSnapshot('addsnapshot', flag_values)
|
|
44
|
+
|
|
45
|
+
expected_project = 'test_project'
|
|
46
|
+
expected_snapshot = 'test_snapshot'
|
|
47
|
+
expected_description = 'test snapshot'
|
|
48
|
+
submitted_source_disk = 'disk1'
|
|
49
|
+
submitted_zone = 'myzone'
|
|
50
|
+
flag_values.service_version = service_version
|
|
51
|
+
flag_values.source_disk = submitted_source_disk
|
|
52
|
+
flag_values.project = expected_project
|
|
53
|
+
flag_values.description = expected_description
|
|
54
|
+
|
|
55
|
+
command.SetFlags(flag_values)
|
|
56
|
+
command.SetApi(mock_api.MockApi())
|
|
57
|
+
|
|
58
|
+
if command._IsUsingAtLeastApiVersion('v1beta14'):
|
|
59
|
+
flag_values.zone = submitted_zone
|
|
60
|
+
|
|
61
|
+
expected_source_disk = command.NormalizePerZoneResourceName(
|
|
62
|
+
expected_project,
|
|
63
|
+
submitted_zone,
|
|
64
|
+
'disks',
|
|
65
|
+
submitted_source_disk)
|
|
66
|
+
|
|
67
|
+
result = command.Handle(expected_snapshot)
|
|
68
|
+
|
|
69
|
+
self.assertEqual(result['project'], expected_project)
|
|
70
|
+
self.assertEqual(result['body']['name'], expected_snapshot)
|
|
71
|
+
self.assertEqual(result['body']['description'], expected_description)
|
|
72
|
+
self.assertEqual(result['body']['sourceDisk'], expected_source_disk)
|
|
73
|
+
|
|
74
|
+
def testAddSnapshotGeneratesCorrectRequest(self):
|
|
75
|
+
for version in command_base.SUPPORTED_VERSIONS:
|
|
76
|
+
self._DoTestAddSnapshotGeneratesCorrectRequest(version)
|
|
77
|
+
|
|
78
|
+
def _DoTestAddSnapshotWithoutZoneGeneratesCorrectRequest(self,
|
|
79
|
+
service_version):
|
|
80
|
+
flag_values = copy.deepcopy(FLAGS)
|
|
81
|
+
|
|
82
|
+
command = snapshot_cmds.AddSnapshot('addsnapshot', flag_values)
|
|
83
|
+
|
|
84
|
+
expected_project = 'test_project'
|
|
85
|
+
expected_snapshot = 'test_snapshot'
|
|
86
|
+
expected_description = 'test snapshot'
|
|
87
|
+
submitted_source_disk = 'disk1'
|
|
88
|
+
disk_zone = 'us-east-a'
|
|
89
|
+
api_base = 'https://www.googleapis.com/compute/%s' % service_version
|
|
90
|
+
disk_self_link = '%s/projects/%s/zones/%s/disks/%s' % (
|
|
91
|
+
api_base, expected_project, disk_zone, submitted_source_disk)
|
|
92
|
+
|
|
93
|
+
flag_values.service_version = service_version
|
|
94
|
+
flag_values.source_disk = submitted_source_disk
|
|
95
|
+
flag_values.project = expected_project
|
|
96
|
+
flag_values.description = expected_description
|
|
97
|
+
|
|
98
|
+
zones = {'items': [{'name': disk_zone}]}
|
|
99
|
+
disks = {'items': [{'name': 'disk1',
|
|
100
|
+
'selfLink': disk_self_link}]}
|
|
101
|
+
|
|
102
|
+
class MockZonesApi(object):
|
|
103
|
+
def list(self, **unused_kwargs):
|
|
104
|
+
return mock_api.MockRequest(zones)
|
|
105
|
+
|
|
106
|
+
class MockDisksApi(object):
|
|
107
|
+
def list(self, **unused_kwargs):
|
|
108
|
+
return mock_api.MockRequest(disks)
|
|
109
|
+
|
|
110
|
+
api = mock_api.MockApi()
|
|
111
|
+
api.zones = MockZonesApi
|
|
112
|
+
api.disks = MockDisksApi
|
|
113
|
+
|
|
114
|
+
command.SetFlags(flag_values)
|
|
115
|
+
command.SetApi(api)
|
|
116
|
+
|
|
117
|
+
expected_source_disk = command.NormalizePerZoneResourceName(
|
|
118
|
+
expected_project,
|
|
119
|
+
disk_zone,
|
|
120
|
+
'disks',
|
|
121
|
+
submitted_source_disk)
|
|
122
|
+
|
|
123
|
+
result = command.Handle(expected_snapshot)
|
|
124
|
+
|
|
125
|
+
self.assertEqual(result['project'], expected_project)
|
|
126
|
+
self.assertEqual(result['body']['name'], expected_snapshot)
|
|
127
|
+
self.assertEqual(result['body']['description'], expected_description)
|
|
128
|
+
self.assertEqual(result['body']['sourceDisk'], expected_source_disk)
|
|
129
|
+
|
|
130
|
+
def testAddSnapshotWithoutZoneGeneratesCorrectRequest(self):
|
|
131
|
+
for version in command_base.SUPPORTED_VERSIONS:
|
|
132
|
+
self._DoTestAddSnapshotWithoutZoneGeneratesCorrectRequest(version)
|
|
133
|
+
|
|
134
|
+
def testAddSnapshotRequiresSourceDisk(self):
|
|
135
|
+
flag_values = copy.deepcopy(FLAGS)
|
|
136
|
+
|
|
137
|
+
command = snapshot_cmds.AddSnapshot('addsnapshot', flag_values)
|
|
138
|
+
|
|
139
|
+
expected_project = 'test_project'
|
|
140
|
+
expected_snapshot = 'test_snapshot'
|
|
141
|
+
expected_description = 'test snapshot'
|
|
142
|
+
submitted_version = command_base.CURRENT_VERSION
|
|
143
|
+
submitted_source_disk = 'disk1'
|
|
144
|
+
|
|
145
|
+
flag_values.service_version = submitted_version
|
|
146
|
+
flag_values.project = expected_project
|
|
147
|
+
flag_values.description = expected_description
|
|
148
|
+
|
|
149
|
+
command.SetFlags(flag_values)
|
|
150
|
+
|
|
151
|
+
def GetDiskPath(disk_name):
|
|
152
|
+
disk_path = 'projects/test_project/disks/%s' % (disk_name)
|
|
153
|
+
if command._IsUsingAtLeastApiVersion('v1beta14'):
|
|
154
|
+
disk_path = 'projects/test_project/zones/zone-a/disks/%s' % (disk_name)
|
|
155
|
+
return disk_path
|
|
156
|
+
|
|
157
|
+
disks = {
|
|
158
|
+
'items': [
|
|
159
|
+
{'name': GetDiskPath('disk1')},
|
|
160
|
+
{'name': GetDiskPath('disk2')},
|
|
161
|
+
{'name': GetDiskPath('disk3')}]}
|
|
162
|
+
|
|
163
|
+
class MockDisksApi(object):
|
|
164
|
+
def list(self, **unused_kwargs):
|
|
165
|
+
return mock_api.MockRequest(disks)
|
|
166
|
+
|
|
167
|
+
api = mock_api.MockApi()
|
|
168
|
+
api.disks = MockDisksApi
|
|
169
|
+
command.SetApi(api)
|
|
170
|
+
|
|
171
|
+
expected_disk = command.NormalizePerZoneResourceName(
|
|
172
|
+
expected_project,
|
|
173
|
+
'zone-a',
|
|
174
|
+
'disks',
|
|
175
|
+
submitted_source_disk)
|
|
176
|
+
|
|
177
|
+
mock_output = mock_api.MockOutput()
|
|
178
|
+
mock_input = mock_api.MockInput('1\n\r')
|
|
179
|
+
oldin = sys.stdin
|
|
180
|
+
sys.stdin = mock_input
|
|
181
|
+
oldout = sys.stdout
|
|
182
|
+
sys.stdout = mock_output
|
|
183
|
+
|
|
184
|
+
result = command.Handle(expected_snapshot)
|
|
185
|
+
self.assertEqual(result['body']['sourceDisk'], expected_disk)
|
|
186
|
+
sys.stdin = oldin
|
|
187
|
+
sys.stdout = oldout
|
|
188
|
+
|
|
189
|
+
def _DoTestGetSnapshotGeneratesCorrectRequest(self, service_version):
|
|
190
|
+
flag_values = copy.deepcopy(FLAGS)
|
|
191
|
+
|
|
192
|
+
command = snapshot_cmds.GetSnapshot('getsnapshot', flag_values)
|
|
193
|
+
|
|
194
|
+
expected_project = 'test_project'
|
|
195
|
+
expected_snapshot = 'test_snapshot'
|
|
196
|
+
flag_values.project = expected_project
|
|
197
|
+
flag_values.service_version = service_version
|
|
198
|
+
|
|
199
|
+
command.SetFlags(flag_values)
|
|
200
|
+
command.SetApi(mock_api.MockApi())
|
|
201
|
+
|
|
202
|
+
result = command.Handle(expected_snapshot)
|
|
203
|
+
|
|
204
|
+
self.assertEqual(result['project'], expected_project)
|
|
205
|
+
self.assertEqual(result['snapshot'], expected_snapshot)
|
|
206
|
+
|
|
207
|
+
def testGetSnapshotGeneratesCorrectRequest(self):
|
|
208
|
+
for version in command_base.SUPPORTED_VERSIONS:
|
|
209
|
+
self._DoTestGetSnapshotGeneratesCorrectRequest(version)
|
|
210
|
+
|
|
211
|
+
def _DoTestDeleteSnapshotGeneratesCorrectRequest(self, service_version):
|
|
212
|
+
flag_values = copy.deepcopy(FLAGS)
|
|
213
|
+
|
|
214
|
+
command = snapshot_cmds.DeleteSnapshot('deletesnapshot', flag_values)
|
|
215
|
+
|
|
216
|
+
expected_project = 'test_project'
|
|
217
|
+
expected_snapshot = 'test_snapshot'
|
|
218
|
+
flag_values.project = expected_project
|
|
219
|
+
|
|
220
|
+
command.SetFlags(flag_values)
|
|
221
|
+
command.SetApi(mock_api.MockApi())
|
|
222
|
+
command._credential = mock_api.MockCredential()
|
|
223
|
+
flag_values.service_version = service_version
|
|
224
|
+
|
|
225
|
+
results, exceptions = command.Handle(expected_snapshot)
|
|
226
|
+
self.assertEquals(exceptions, [])
|
|
227
|
+
self.assertEquals(len(results['items']), 1)
|
|
228
|
+
result = results['items'][0]
|
|
229
|
+
|
|
230
|
+
self.assertEqual(result['project'], expected_project)
|
|
231
|
+
self.assertEqual(result['snapshot'], expected_snapshot)
|
|
232
|
+
|
|
233
|
+
def testDeleteSnapshotGeneratesCorrectRequest(self):
|
|
234
|
+
for version in command_base.SUPPORTED_VERSIONS:
|
|
235
|
+
self._DoTestDeleteSnapshotGeneratesCorrectRequest(version)
|
|
236
|
+
|
|
237
|
+
def testDeleteMultipleSnapshots(self):
|
|
238
|
+
flag_values = copy.deepcopy(FLAGS)
|
|
239
|
+
command = snapshot_cmds.DeleteSnapshot('deletesnapshot', flag_values)
|
|
240
|
+
|
|
241
|
+
expected_project = 'test_project'
|
|
242
|
+
expected_snapshots = ['test-snapshot-%02d' % x for x in xrange(100)]
|
|
243
|
+
flag_values.project = expected_project
|
|
244
|
+
|
|
245
|
+
command.SetFlags(flag_values)
|
|
246
|
+
command.SetApi(mock_api.MockApi())
|
|
247
|
+
command._credential = mock_api.MockCredential()
|
|
248
|
+
|
|
249
|
+
results, exceptions = command.Handle(*expected_snapshots)
|
|
250
|
+
self.assertEqual(exceptions, [])
|
|
251
|
+
results = results['items']
|
|
252
|
+
self.assertEqual(len(results), len(expected_snapshots))
|
|
253
|
+
|
|
254
|
+
for expected_snapshot, result in zip(expected_snapshots, results):
|
|
255
|
+
self.assertEqual(result['project'], expected_project)
|
|
256
|
+
self.assertEqual(result['snapshot'], expected_snapshot)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
if __name__ == '__main__':
|
|
260
|
+
unittest.main()
|
|
@@ -0,0 +1,266 @@
|
|
|
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
|
+
"""Utility class for creating/storing SSH keys."""
|
|
16
|
+
|
|
17
|
+
from __future__ import with_statement
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
import base64
|
|
22
|
+
import os
|
|
23
|
+
import struct
|
|
24
|
+
import subprocess
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
import gflags as flags
|
|
28
|
+
|
|
29
|
+
from gcutil import command_base
|
|
30
|
+
from gcutil import gcutil_logging
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
PRIVATE_KEY_FILE = 'google_compute_engine'
|
|
34
|
+
PUBLIC_KEY_FILE = PRIVATE_KEY_FILE + '.pub'
|
|
35
|
+
|
|
36
|
+
flags.DEFINE_string(
|
|
37
|
+
'public_key_file',
|
|
38
|
+
os.path.expanduser('~/.ssh/' + PUBLIC_KEY_FILE),
|
|
39
|
+
'The location of the default (generated) public ssh key for use '
|
|
40
|
+
'with Google Cloud Compute instances.')
|
|
41
|
+
|
|
42
|
+
flags.DEFINE_string(
|
|
43
|
+
'private_key_file',
|
|
44
|
+
os.path.expanduser('~/.ssh/' + PRIVATE_KEY_FILE),
|
|
45
|
+
'The location of the default (generated) private ssh key for use '
|
|
46
|
+
'with Google Cloud Compute instances.')
|
|
47
|
+
|
|
48
|
+
flags.DEFINE_string(
|
|
49
|
+
'ssh_user',
|
|
50
|
+
os.getenv('USER'),
|
|
51
|
+
'The default ssh user for the instance.')
|
|
52
|
+
|
|
53
|
+
FLAGS = flags.FLAGS
|
|
54
|
+
LOGGER = gcutil_logging.LOGGER
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Error(Exception):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class UserSetupError(Error):
|
|
62
|
+
"""Raised the users environment isn't set up correctly."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, msg):
|
|
65
|
+
Error.__init__(self)
|
|
66
|
+
self.msg = msg
|
|
67
|
+
|
|
68
|
+
def __str__(self):
|
|
69
|
+
return self.msg
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class SshKeys(object):
|
|
73
|
+
"""Collection of methods that work with Google Compute Engine SSH Keys."""
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def GetAuthorizedUserKeys(use_compute_key=True,
|
|
77
|
+
authorized_ssh_keys=None):
|
|
78
|
+
"""Get a typical list of ssh user/key dictionaries.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
use_compute_key: authorize using ~/.ssh/compute.pub
|
|
82
|
+
authorized_ssh_keys: key string user1:keyfile1,user2:keyfile2...
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
A list of {'user': ..., 'key': ...} dictionaries.
|
|
86
|
+
"""
|
|
87
|
+
user_keys = []
|
|
88
|
+
|
|
89
|
+
if use_compute_key:
|
|
90
|
+
user_keys.append(SshKeys.GetPublicKey())
|
|
91
|
+
|
|
92
|
+
if authorized_ssh_keys:
|
|
93
|
+
for user_key_file_pair in authorized_ssh_keys:
|
|
94
|
+
user, key_file = user_key_file_pair.split(':')
|
|
95
|
+
user_keys.append({'user': user,
|
|
96
|
+
'key': SshKeys.GetKeyFromFile(key_file)})
|
|
97
|
+
return user_keys
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def GetAuthorizedUserKeysFromMetadata(metadata):
|
|
101
|
+
"""Get the set of authorized user keys from the given metadata.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
metadata: list of {'key': ..., 'value': ...} dictionaries.
|
|
105
|
+
Returns:
|
|
106
|
+
A list of {'user': ..., 'key':...} dictionaries.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def GetAuthorizedUserKeyFromLine(line):
|
|
110
|
+
line_parts = line.split(':')
|
|
111
|
+
return {'user': line_parts[0], 'key': line_parts[1]}
|
|
112
|
+
|
|
113
|
+
for metadata_entry in metadata:
|
|
114
|
+
key = metadata_entry['key']
|
|
115
|
+
value = metadata_entry['value']
|
|
116
|
+
if key == 'sshKeys':
|
|
117
|
+
lines = value.split('\n')
|
|
118
|
+
return [GetAuthorizedUserKeyFromLine(line)
|
|
119
|
+
for line in lines if ':' in line]
|
|
120
|
+
return []
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def SetAuthorizedUserKeysInMetadata(metadata, authorized_user_keys):
|
|
124
|
+
"""Add the authorized public ssh keys to the given metadata.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
metadata: A list of {'key': ..., 'value': ...} dictionaries.
|
|
128
|
+
authorized_user_keys: A list of {'user': ..., 'key':...} dictionaries.
|
|
129
|
+
Returns:
|
|
130
|
+
The metadata updated to include exactly one 'sshKeys' entry that
|
|
131
|
+
matches the given authorized user keys.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
all_user_keys_string = '\n'.join(
|
|
135
|
+
['%(user)s:%(key)s' % user_keys for user_keys in authorized_user_keys])
|
|
136
|
+
for metadata_entry in metadata:
|
|
137
|
+
if metadata_entry['key'] == 'sshKeys':
|
|
138
|
+
metadata_entry['value'] = all_user_keys_string
|
|
139
|
+
return
|
|
140
|
+
metadata.append({'key': 'sshKeys', 'value': all_user_keys_string})
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def GetPublicKey():
|
|
144
|
+
"""Returns the standard Compute key for the current user.
|
|
145
|
+
|
|
146
|
+
If the key doesn't exist, it will be created and will
|
|
147
|
+
interactively prompt the user.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
A dictionary of an user/key pair for the user's ssh key.
|
|
151
|
+
"""
|
|
152
|
+
SshKeys.EnsureSshKeyCreated()
|
|
153
|
+
if (not FLAGS['ssh_user'].present) and FLAGS.ssh_user == 'root':
|
|
154
|
+
LOGGER.warn('Logging into root is not supported on default images. '
|
|
155
|
+
'Please specify a different user account with --ssh_user. '
|
|
156
|
+
'Use this flag for addinstance and all ssh based commands.')
|
|
157
|
+
return {'user': FLAGS.ssh_user,
|
|
158
|
+
'key': SshKeys.GetKeyFromFile(FLAGS.public_key_file)}
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def EnsureSshKeyCreated():
|
|
162
|
+
"""Ensures that the ssh key actually exists.
|
|
163
|
+
|
|
164
|
+
This will create a public/private key pair if no existing
|
|
165
|
+
key pair is found.
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
UserSetupError: Error when generating the ssh key
|
|
169
|
+
"""
|
|
170
|
+
if (os.path.exists(FLAGS.public_key_file) and
|
|
171
|
+
os.path.exists(FLAGS.private_key_file)):
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
LOGGER.warn('You don\'t have an ssh key for Google Compute Engine. '
|
|
175
|
+
'Creating one now...')
|
|
176
|
+
command_line = [
|
|
177
|
+
'ssh-keygen',
|
|
178
|
+
'-t', 'rsa',
|
|
179
|
+
'-q',
|
|
180
|
+
'-f', FLAGS.private_key_file,
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
LOGGER.debug(' '.join(command_line))
|
|
184
|
+
try:
|
|
185
|
+
process = subprocess.Popen(command_line)
|
|
186
|
+
process.communicate()
|
|
187
|
+
if process.wait() != 0:
|
|
188
|
+
raise UserSetupError('Error generating compute ssh key.')
|
|
189
|
+
except OSError as e:
|
|
190
|
+
raise UserSetupError('There was a problem running ssh-keygen: %s' % e)
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def GetKeyFromFile(key_file):
|
|
194
|
+
"""Read an ssh key from key_file, and return it.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
key_file: the file containing the ssh key
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
A the ssh key stored in the file.
|
|
201
|
+
"""
|
|
202
|
+
key_file = os.path.expanduser(key_file)
|
|
203
|
+
with open(key_file) as f:
|
|
204
|
+
return SshKeys._ValidateSshKey(f.read().strip(), key_file)
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def _ValidateSshKey(key, key_file):
|
|
208
|
+
"""Validates the public ssh key format (OpenSSH).
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
key: string containing the public ssh key.
|
|
212
|
+
key_file: filename whence the key cometh.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
the ssh key value (value of key parameter) if validation has passed.
|
|
216
|
+
|
|
217
|
+
Raises:
|
|
218
|
+
UserSetupError: if the key validation fails.
|
|
219
|
+
"""
|
|
220
|
+
if not key:
|
|
221
|
+
raise UserSetupError(
|
|
222
|
+
'Public key file (%s) doesn\'t contain a key.' % key_file)
|
|
223
|
+
if '\n' in key:
|
|
224
|
+
raise UserSetupError(
|
|
225
|
+
'Public key file (%s) has invalid format. '
|
|
226
|
+
'It must only contain single line.\n%s' % (key_file, key))
|
|
227
|
+
# Validate the OpenSSH key format
|
|
228
|
+
parts = key.split()
|
|
229
|
+
if len(parts) != 3:
|
|
230
|
+
raise UserSetupError(
|
|
231
|
+
'Public key file (%s) doesn\'t contain an OpenSSH public key. '
|
|
232
|
+
'The key must consist of exactly three space separated parts.\n%s' %
|
|
233
|
+
(key_file, key))
|
|
234
|
+
|
|
235
|
+
key_type, key_value, _ = parts
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
key_value = base64.b64decode(key_value)
|
|
239
|
+
except TypeError:
|
|
240
|
+
raise UserSetupError(
|
|
241
|
+
'Public key file (%s) doesn\'t contain an OpenSSH public key. '
|
|
242
|
+
'The key is not a valid base64 encoded value.\n%s' %
|
|
243
|
+
(key_file, key))
|
|
244
|
+
|
|
245
|
+
if len(key_value) < 4:
|
|
246
|
+
raise UserSetupError(
|
|
247
|
+
'Public key file (%s) doesn\'t contain an OpenSSH public key. '
|
|
248
|
+
'The key has invalid length.\n%s' %
|
|
249
|
+
(key_file, key))
|
|
250
|
+
|
|
251
|
+
# First 4 bytes is the length of key type.
|
|
252
|
+
decoded_length = struct.unpack_from('>I', key_value)[0]
|
|
253
|
+
if len(key_value) < 4 + decoded_length:
|
|
254
|
+
raise UserSetupError(
|
|
255
|
+
'Public key file (%s) doesn\'t contain an OpenSSH public key. '
|
|
256
|
+
'The key doesn\'t have a valid type.\n%s' %
|
|
257
|
+
(key_file, key))
|
|
258
|
+
|
|
259
|
+
decoded_type = key_value[4:4 + decoded_length]
|
|
260
|
+
if key_type != decoded_type:
|
|
261
|
+
raise UserSetupError(
|
|
262
|
+
'Public key file (%s) doesn\'t contain an OpenSSH public key. '
|
|
263
|
+
'The decoded key type doesn\'t match.\n%s' %
|
|
264
|
+
(key_file, key))
|
|
265
|
+
|
|
266
|
+
return key
|