OLD | NEW |
| (Empty) |
1 # -*- coding: utf-8 -*- | |
2 # Copyright 2011 Google Inc. All Rights Reserved. | |
3 # | |
4 # Licensed under the Apache License, Version 2.0 (the "License"); | |
5 # you may not use this file except in compliance with the License. | |
6 # You may obtain a copy of the License at | |
7 # | |
8 # http://www.apache.org/licenses/LICENSE-2.0 | |
9 # | |
10 # Unless required by applicable law or agreed to in writing, software | |
11 # distributed under the License is distributed on an "AS IS" BASIS, | |
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 # See the License for the specific language governing permissions and | |
14 # limitations under the License. | |
15 """Implementation of config command for creating a gsutil configuration file.""" | |
16 | |
17 from __future__ import absolute_import | |
18 | |
19 import datetime | |
20 from httplib import ResponseNotReady | |
21 import json | |
22 import multiprocessing | |
23 import os | |
24 import platform | |
25 import signal | |
26 import socket | |
27 import stat | |
28 import sys | |
29 import textwrap | |
30 import time | |
31 import webbrowser | |
32 | |
33 import boto | |
34 from boto.provider import Provider | |
35 from httplib2 import ServerNotFoundError | |
36 from oauth2client.client import HAS_CRYPTO | |
37 | |
38 import gslib | |
39 from gslib.command import Command | |
40 from gslib.commands.compose import MAX_COMPONENT_COUNT | |
41 from gslib.cred_types import CredTypes | |
42 from gslib.exception import AbortException | |
43 from gslib.exception import CommandException | |
44 from gslib.hashing_helper import CHECK_HASH_ALWAYS | |
45 from gslib.hashing_helper import CHECK_HASH_IF_FAST_ELSE_FAIL | |
46 from gslib.hashing_helper import CHECK_HASH_IF_FAST_ELSE_SKIP | |
47 from gslib.hashing_helper import CHECK_HASH_NEVER | |
48 from gslib.sig_handling import RegisterSignalHandler | |
49 from gslib.util import EIGHT_MIB | |
50 from gslib.util import IS_WINDOWS | |
51 | |
52 | |
53 _SYNOPSIS = """ | |
54 gsutil [-D] config [-a] [-b] [-e] [-f] [-o <file>] [-r] [-s <scope>] [-w] | |
55 """ | |
56 | |
57 _DETAILED_HELP_TEXT = (""" | |
58 <B>SYNOPSIS</B> | |
59 """ + _SYNOPSIS + """ | |
60 | |
61 | |
62 <B>DESCRIPTION</B> | |
63 The gsutil config command obtains access credentials for Google Cloud | |
64 Storage and writes a boto/gsutil configuration file containing the obtained | |
65 credentials along with a number of other configuration-controllable values. | |
66 | |
67 Unless specified otherwise (see OPTIONS), the configuration file is written | |
68 to ~/.boto (i.e., the file .boto under the user's home directory). If the | |
69 default file already exists, an attempt is made to rename the existing file | |
70 to ~/.boto.bak; if that attempt fails the command will exit. A different | |
71 destination file can be specified with the -o option (see OPTIONS). | |
72 | |
73 Because the boto configuration file contains your credentials you should | |
74 keep its file permissions set so no one but you has read access. (The file | |
75 is created read-only when you run gsutil config.) | |
76 | |
77 | |
78 <B>CREDENTIALS</B> | |
79 By default gsutil config obtains OAuth2 credentials, and writes them | |
80 to the [Credentials] section of the configuration file. The -r, -w, | |
81 -f options (see OPTIONS below) cause gsutil config to request a token | |
82 with restricted scope; the resulting token will be restricted to read-only | |
83 operations, read-write operations, or all operations (including acl get/set, | |
84 defacl get/set, and logging get/'set on'/'set off' operations). In | |
85 addition, -s <scope> can be used to request additional (non-Google-Storage) | |
86 scopes. | |
87 | |
88 If you want to use credentials based on access key and secret (the older | |
89 authentication method before OAuth2 was supported) instead of OAuth2, | |
90 see help about the -a option in the OPTIONS section. | |
91 | |
92 If you wish to use gsutil with other providers (or to copy data back and | |
93 forth between multiple providers) you can edit their credentials into the | |
94 [Credentials] section after creating the initial configuration file. | |
95 | |
96 | |
97 <B>CONFIGURING SERVICE ACCOUNT CREDENTIALS</B> | |
98 You can configure credentials for service accounts using the gsutil config -e | |
99 option. Service accounts are useful for authenticating on behalf of a service | |
100 or application (as opposed to a user). | |
101 | |
102 When you run gsutil config -e, you will be prompted for your service account | |
103 email address and the path to your private key file. To get these data, visit | |
104 the `Google Developers Console <https://cloud.google.com/console#/project>`_, | |
105 click on the project you are using, then click "APIs & auth", then click | |
106 "Credentials", then click "Create new Client ID"; on the pop-up dialog box | |
107 select "Service account" and click "Create Client ID". This will download | |
108 a private key file, which you should move to somewhere | |
109 accessible from the machine where you run gsutil. Make sure to set its | |
110 protection so only the users you want to be able to authenticate have | |
111 access. | |
112 | |
113 Note that your service account will NOT be considered an Owner for the | |
114 purposes of API access (see "gsutil help creds" for more information about | |
115 this). See https://developers.google.com/accounts/docs/OAuth2ServiceAccount | |
116 for further information on service account authentication. | |
117 | |
118 | |
119 <B>CONFIGURATION FILE SELECTION PROCEDURE</B> | |
120 By default, gsutil will look for the configuration file in /etc/boto.cfg and | |
121 ~/.boto. You can override this choice by setting the BOTO_CONFIG environment | |
122 variable. This is also useful if you have several different identities or | |
123 cloud storage environments: By setting up the credentials and any additional | |
124 configuration in separate files for each, you can switch environments by | |
125 changing environment variables. | |
126 | |
127 You can also set up a path of configuration files, by setting the BOTO_PATH | |
128 environment variable to contain a ":" delimited path. For example setting | |
129 the BOTO_PATH environment variable to: | |
130 | |
131 /etc/projects/my_group_project.boto.cfg:/home/mylogin/.boto | |
132 | |
133 will cause gsutil to load each configuration file found in the path in | |
134 order. This is useful if you want to set up some shared configuration | |
135 state among many users: The shared state can go in the central shared file | |
136 ( /etc/projects/my_group_project.boto.cfg) and each user's individual | |
137 credentials can be placed in the configuration file in each of their home | |
138 directories. (For security reasons users should never share credentials | |
139 via a shared configuration file.) | |
140 | |
141 | |
142 <B>CONFIGURATION FILE STRUCTURE</B> | |
143 The configuration file contains a number of sections: [Credentials], | |
144 [Boto], [GSUtil], and [OAuth2]. If you edit the file make sure to edit the | |
145 appropriate section (discussed below), and to be careful not to mis-edit | |
146 any of the setting names (like "gs_access_key_id") and not to remove the | |
147 section delimiters (like "[Credentials]"). | |
148 | |
149 | |
150 <B>ADDITIONAL CONFIGURATION-CONTROLLABLE FEATURES</B> | |
151 With the exception of setting up gsutil to work through a proxy (see | |
152 below), most users won't need to edit values in the boto configuration file; | |
153 values found in there tend to be of more specialized use than command line | |
154 option-controllable features. | |
155 | |
156 The following are the currently defined configuration settings, broken | |
157 down by section. Their use is documented in comments preceding each, in | |
158 the configuration file. If you see a setting you want to change that's not | |
159 listed in your current file, see the section below on Updating to the Latest | |
160 Configuration File. | |
161 | |
162 The currently supported settings, are, by section: | |
163 | |
164 [Credentials] | |
165 aws_access_key_id | |
166 aws_secret_access_key | |
167 gs_access_key_id | |
168 gs_host | |
169 gs_json_host | |
170 gs_json_port | |
171 gs_oauth2_refresh_token | |
172 gs_port | |
173 gs_secret_access_key | |
174 s3_host | |
175 s3_port | |
176 | |
177 [Boto] | |
178 proxy | |
179 proxy_port | |
180 proxy_user | |
181 proxy_pass | |
182 proxy_rdns | |
183 http_socket_timeout | |
184 https_validate_certificates | |
185 debug | |
186 max_retry_delay | |
187 num_retries | |
188 | |
189 [GSUtil] | |
190 check_hashes | |
191 content_language | |
192 default_api_version | |
193 default_project_id | |
194 json_api_version | |
195 parallel_composite_upload_component_size | |
196 parallel_composite_upload_threshold | |
197 parallel_process_count | |
198 parallel_thread_count | |
199 prefer_api | |
200 resumable_threshold | |
201 resumable_tracker_dir (deprecated in 4.6, use state_dir) | |
202 rsync_buffer_lines | |
203 software_update_check_period | |
204 state_dir | |
205 tab_completion_time_logs | |
206 tab_completion_timeout | |
207 use_magicfile | |
208 | |
209 [OAuth2] | |
210 client_id | |
211 client_secret | |
212 oauth2_refresh_retries | |
213 provider_authorization_uri | |
214 provider_label | |
215 provider_token_uri | |
216 token_cache | |
217 | |
218 | |
219 <B>UPDATING TO THE LATEST CONFIGURATION FILE</B> | |
220 We add new configuration controllable features to the boto configuration file | |
221 over time, but most gsutil users create a configuration file once and then | |
222 keep it for a long time, so new features aren't apparent when you update | |
223 to a newer version of gsutil. If you want to get the latest configuration | |
224 file (which includes all the latest settings and documentation about each) | |
225 you can rename your current file (e.g., to '.boto_old'), run gsutil config, | |
226 and then edit any configuration settings you wanted from your old file | |
227 into the newly created file. Note, however, that if you're using OAuth2 | |
228 credentials and you go back through the OAuth2 configuration dialog it will | |
229 invalidate your previous OAuth2 credentials. | |
230 | |
231 If no explicit scope option is given, -f (full control) is assumed by default. | |
232 | |
233 | |
234 <B>OPTIONS</B> | |
235 -a Prompt for Google Cloud Storage access key and secret (the older | |
236 authentication method before OAuth2 was supported) instead of | |
237 obtaining an OAuth2 token. | |
238 | |
239 -b Causes gsutil config to launch a browser to obtain OAuth2 approval | |
240 and the project ID instead of showing the URL for each and asking | |
241 the user to open the browser. This will probably not work as | |
242 expected if you are running gsutil from an ssh window, or using | |
243 gsutil on Windows. | |
244 | |
245 -e Prompt for service account credentials. This option requires that | |
246 -a is not set. | |
247 | |
248 -f Request token with full-control access (default). | |
249 | |
250 -o <file> Write the configuration to <file> instead of ~/.boto. | |
251 Use '-' for stdout. | |
252 | |
253 -r Request token restricted to read-only access. | |
254 | |
255 -s <scope> Request additional OAuth2 <scope>. | |
256 | |
257 -w Request token restricted to read-write access. | |
258 """) | |
259 | |
260 | |
261 try: | |
262 from gcs_oauth2_boto_plugin import oauth2_helper # pylint: disable=g-import-n
ot-at-top | |
263 except ImportError: | |
264 pass | |
265 | |
266 GOOG_CLOUD_CONSOLE_URI = 'https://cloud.google.com/console#/project' | |
267 | |
268 SCOPE_FULL_CONTROL = 'https://www.googleapis.com/auth/devstorage.full_control' | |
269 SCOPE_READ_WRITE = 'https://www.googleapis.com/auth/devstorage.read_write' | |
270 SCOPE_READ_ONLY = 'https://www.googleapis.com/auth/devstorage.read_only' | |
271 | |
272 CONFIG_PRELUDE_CONTENT = """ | |
273 # This file contains credentials and other configuration information needed | |
274 # by the boto library, used by gsutil. You can edit this file (e.g., to add | |
275 # credentials) but be careful not to mis-edit any of the variable names (like | |
276 # "gs_access_key_id") or remove important markers (like the "[Credentials]" and | |
277 # "[Boto]" section delimiters). | |
278 # | |
279 """ | |
280 | |
281 # Default number of OS processes and Python threads for parallel operations. | |
282 # On Linux systems we automatically scale the number of processes to match | |
283 # the underlying CPU/core count. Given we'll be running multiple concurrent | |
284 # processes on a typical multi-core Linux computer, to avoid being too | |
285 # aggressive with resources, the default number of threads is reduced from | |
286 # the previous value of 24 to 10. | |
287 # On Windows and Mac systems parallel multi-processing and multi-threading | |
288 # in Python presents various challenges so we retain compatibility with | |
289 # the established parallel mode operation, i.e. one process and 24 threads. | |
290 if platform.system() == 'Linux': | |
291 DEFAULT_PARALLEL_PROCESS_COUNT = multiprocessing.cpu_count() | |
292 DEFAULT_PARALLEL_THREAD_COUNT = 10 | |
293 else: | |
294 DEFAULT_PARALLEL_PROCESS_COUNT = 1 | |
295 DEFAULT_PARALLEL_THREAD_COUNT = 24 | |
296 | |
297 # TODO: Once compiled crcmod is being distributed by major Linux distributions | |
298 # revert DEFAULT_PARALLEL_COMPOSITE_UPLOAD_THRESHOLD value to '150M'. | |
299 DEFAULT_PARALLEL_COMPOSITE_UPLOAD_THRESHOLD = '0' | |
300 DEFAULT_PARALLEL_COMPOSITE_UPLOAD_COMPONENT_SIZE = '50M' | |
301 | |
302 CONFIG_BOTO_SECTION_CONTENT = """ | |
303 [Boto] | |
304 | |
305 # http_socket_timeout specifies the timeout (in seconds) used to tell httplib | |
306 # how long to wait for socket timeouts. The default is 70 seconds. Note that | |
307 # this timeout only applies to httplib, not to httplib2 (which is used for | |
308 # OAuth2 refresh/access token exchanges). | |
309 #http_socket_timeout = 70 | |
310 | |
311 # The following two options control the use of a secure transport for requests | |
312 # to S3 and Google Cloud Storage. It is highly recommended to set both options | |
313 # to True in production environments, especially when using OAuth2 bearer token | |
314 # authentication with Google Cloud Storage. | |
315 | |
316 # Set 'https_validate_certificates' to False to disable server certificate | |
317 # checking. The default for this option in the boto library is currently | |
318 # 'False' (to avoid breaking apps that depend on invalid certificates); it is | |
319 # therefore strongly recommended to always set this option explicitly to True | |
320 # in configuration files, to protect against "man-in-the-middle" attacks. | |
321 https_validate_certificates = True | |
322 | |
323 # 'debug' controls the level of debug messages printed: 0 for none, 1 | |
324 # for basic boto debug, 2 for all boto debug plus HTTP requests/responses. | |
325 # Note: 'gsutil -d' sets debug to 2 for that one command run. | |
326 #debug = <0, 1, or 2> | |
327 | |
328 # 'num_retries' controls the number of retry attempts made when errors occur | |
329 # during data transfers. The default is 6. | |
330 # Note 1: You can cause gsutil to retry failures effectively infinitely by | |
331 # setting this value to a large number (like 10000). Doing that could be useful | |
332 # in cases where your network connection occasionally fails and is down for an | |
333 # extended period of time, because when it comes back up gsutil will continue | |
334 # retrying. However, in general we recommend not setting the value above 10, | |
335 # because otherwise gsutil could appear to "hang" due to excessive retries | |
336 # (since unless you run gsutil -D you won't see any logged evidence that gsutil | |
337 # is retrying). | |
338 # Note 2: Don't set this value to 0, as it will cause boto to fail when reusing | |
339 # HTTP connections. | |
340 #num_retries = <integer value> | |
341 | |
342 # 'max_retry_delay' controls the max delay (in seconds) between retries. The | |
343 # default value is 60, so the backoff sequence will be 1 seconds, 2 seconds, 4, | |
344 # 8, 16, 32, and then 60 for all subsequent retries for a given HTTP request. | |
345 # Note: At present this value only impacts the XML API and the JSON API uses a | |
346 # fixed value of 60. | |
347 #max_retry_delay = <integer value> | |
348 """ | |
349 | |
350 CONFIG_INPUTLESS_GSUTIL_SECTION_CONTENT = """ | |
351 [GSUtil] | |
352 | |
353 # 'resumable_threshold' specifies the smallest file size [bytes] for which | |
354 # resumable Google Cloud Storage uploads are attempted. The default is 8388608 | |
355 # (8 MiB). | |
356 #resumable_threshold = %(resumable_threshold)d | |
357 | |
358 # 'rsync_buffer_lines' specifies the number of lines of bucket or directory | |
359 # listings saved in each temp file during sorting. (The complete set is | |
360 # split across temp files and separately sorted/merged, to avoid needing to | |
361 # fit everything in memory at once.) If you are trying to synchronize very | |
362 # large directories/buckets (e.g., containing millions or more objects), | |
363 # having too small a value here can cause gsutil to run out of open file | |
364 # handles. If that happens, you can try to increase the number of open file | |
365 # handles your system allows (e.g., see 'man ulimit' on Linux; see also | |
366 # http://docs.python.org/2/library/resource.html). If you can't do that (or | |
367 # if you're already at the upper limit), increasing rsync_buffer_lines will | |
368 # cause gsutil to use fewer file handles, but at the cost of more memory. With | |
369 # rsync_buffer_lines set to 32000 and assuming a typical URL is 100 bytes | |
370 # long, gsutil will require approximately 10 MiB of memory while building | |
371 # the synchronization state, and will require approximately 60 open file | |
372 # descriptors to build the synchronization state over all 1M source and 1M | |
373 # destination URLs. Memory and file descriptors are only consumed while | |
374 # building the state; once the state is built, it resides in two temp files that | |
375 # are read and processed incrementally during the actual copy/delete | |
376 # operations. | |
377 #rsync_buffer_lines = 32000 | |
378 | |
379 # 'state_dir' specifies the base location where files that | |
380 # need a static location are stored, such as pointers to credentials, | |
381 # resumable transfer tracker files, and the last software update check. | |
382 # By default these files are stored in ~/.gsutil | |
383 #state_dir = <file_path> | |
384 # gsutil periodically checks whether a new version of the gsutil software is | |
385 # available. 'software_update_check_period' specifies the number of days | |
386 # between such checks. The default is 30. Setting the value to 0 disables | |
387 # periodic software update checks. | |
388 #software_update_check_period = 30 | |
389 | |
390 # 'tab_completion_timeout' controls the timeout (in seconds) for tab | |
391 # completions that involve remote requests (such as bucket or object names). | |
392 # If tab completion does not succeed within this timeout, no tab completion | |
393 # suggestions will be returned. | |
394 # A value of 0 will disable completions that involve remote requests. | |
395 #tab_completion_timeout = 5 | |
396 | |
397 # 'parallel_process_count' and 'parallel_thread_count' specify the number | |
398 # of OS processes and Python threads, respectively, to use when executing | |
399 # operations in parallel. The default settings should work well as configured, | |
400 # however, to enhance performance for transfers involving large numbers of | |
401 # files, you may experiment with hand tuning these values to optimize | |
402 # performance for your particular system configuration. | |
403 # MacOS and Windows users should see | |
404 # https://github.com/GoogleCloudPlatform/gsutil/issues/77 before attempting | |
405 # to experiment with these values. | |
406 #parallel_process_count = %(parallel_process_count)d | |
407 #parallel_thread_count = %(parallel_thread_count)d | |
408 | |
409 # 'parallel_composite_upload_threshold' specifies the maximum size of a file to | |
410 # upload in a single stream. Files larger than this threshold will be | |
411 # partitioned into component parts and uploaded in parallel and then composed | |
412 # into a single object. | |
413 # The number of components will be the smaller of | |
414 # ceil(file_size / parallel_composite_upload_component_size) and | |
415 # MAX_COMPONENT_COUNT. The current value of MAX_COMPONENT_COUNT is | |
416 # %(max_component_count)d. | |
417 # If 'parallel_composite_upload_threshold' is set to 0, then automatic parallel | |
418 # uploads will never occur. | |
419 # Setting an extremely low threshold is unadvisable. The vast majority of | |
420 # environments will see degraded performance for thresholds below 80M, and it | |
421 # is almost never advantageous to have a threshold below 20M. | |
422 # 'parallel_composite_upload_component_size' specifies the ideal size of a | |
423 # component in bytes, which will act as an upper bound to the size of the | |
424 # components if ceil(file_size / parallel_composite_upload_component_size) is | |
425 # less than MAX_COMPONENT_COUNT. | |
426 # Values can be provided either in bytes or as human-readable values | |
427 # (e.g., "150M" to represent 150 mebibytes) | |
428 # | |
429 # Note: At present parallel composite uploads are disabled by default, because | |
430 # using composite objects requires a compiled crcmod (see "gsutil help crcmod"), | |
431 # and for operating systems that don't already have this package installed this | |
432 # makes gsutil harder to use. Google is actively working with a number of the | |
433 # Linux distributions to get crcmod included with the stock distribution. Once | |
434 # that is done we will re-enable parallel composite uploads by default in | |
435 # gsutil. | |
436 #parallel_composite_upload_threshold = %(parallel_composite_upload_threshold)s | |
437 #parallel_composite_upload_component_size = %(parallel_composite_upload_componen
t_size)s | |
438 | |
439 # 'use_magicfile' specifies if the 'file --mime-type <filename>' command should | |
440 # be used to guess content types instead of the default filename extension-based | |
441 # mechanism. Available on UNIX and MacOS (and possibly on Windows, if you're | |
442 # running Cygwin or some other package that provides implementations of | |
443 # UNIX-like commands). When available and enabled use_magicfile should be more | |
444 # robust because it analyzes file contents in addition to extensions. | |
445 #use_magicfile = False | |
446 | |
447 # 'content_language' specifies the ISO 639-1 language code of the content, to be | |
448 # passed in the Content-Language header. By default no Content-Language is sent. | |
449 # See the ISO 639-1 column of | |
450 # http://www.loc.gov/standards/iso639-2/php/code_list.php for a list of | |
451 # language codes. | |
452 content_language = en | |
453 | |
454 # 'check_hashes' specifies how strictly to require integrity checking for | |
455 # downloaded data. Legal values are: | |
456 # '%(hash_fast_else_fail)s' - (default) Only integrity check if the digest | |
457 # will run efficiently (using compiled code), else fail the download. | |
458 # '%(hash_fast_else_skip)s' - Only integrity check if the server supplies a | |
459 # hash and the local digest computation will run quickly, else skip the | |
460 # check. | |
461 # '%(hash_always)s' - Always check download integrity regardless of possible | |
462 # performance costs. | |
463 # '%(hash_never)s' - Don't perform download integrity checks. This setting is | |
464 # not recommended except for special cases such as measuring download | |
465 # performance excluding time for integrity checking. | |
466 # This option exists to assist users who wish to download a GCS composite object | |
467 # and are unable to install crcmod with the C-extension. CRC32c is the only | |
468 # available integrity check for composite objects, and without the C-extension, | |
469 # download performance can be significantly degraded by the digest computation. | |
470 # This option is ignored for daisy-chain copies, which don't compute hashes but | |
471 # instead (inexpensively) compare the cloud source and destination hashes. | |
472 #check_hashes = if_fast_else_fail | |
473 | |
474 # The ability to specify an alternative JSON API version is primarily for cloud | |
475 # storage service developers. | |
476 #json_api_version = v1 | |
477 | |
478 # Specifies the API to use when interacting with cloud storage providers. If | |
479 # the gsutil command supports this API for the provider, it will be used | |
480 # instead of the default. | |
481 # Commands typically default to XML for S3 and JSON for GCS. | |
482 #prefer_api = json | |
483 #prefer_api = xml | |
484 | |
485 """ % {'hash_fast_else_fail': CHECK_HASH_IF_FAST_ELSE_FAIL, | |
486 'hash_fast_else_skip': CHECK_HASH_IF_FAST_ELSE_SKIP, | |
487 'hash_always': CHECK_HASH_ALWAYS, | |
488 'hash_never': CHECK_HASH_NEVER, | |
489 'resumable_threshold': EIGHT_MIB, | |
490 'parallel_process_count': DEFAULT_PARALLEL_PROCESS_COUNT, | |
491 'parallel_thread_count': DEFAULT_PARALLEL_THREAD_COUNT, | |
492 'parallel_composite_upload_threshold': ( | |
493 DEFAULT_PARALLEL_COMPOSITE_UPLOAD_THRESHOLD), | |
494 'parallel_composite_upload_component_size': ( | |
495 DEFAULT_PARALLEL_COMPOSITE_UPLOAD_COMPONENT_SIZE), | |
496 'max_component_count': MAX_COMPONENT_COUNT} | |
497 | |
498 CONFIG_OAUTH2_CONFIG_CONTENT = """ | |
499 [OAuth2] | |
500 # This section specifies options used with OAuth2 authentication. | |
501 | |
502 # 'token_cache' specifies how the OAuth2 client should cache access tokens. | |
503 # Valid values are: | |
504 # 'in_memory': an in-memory cache is used. This is only useful if the boto | |
505 # client instance (and with it the OAuth2 plugin instance) persists | |
506 # across multiple requests. | |
507 # 'file_system' : access tokens will be cached in the file system, in files | |
508 # whose names include a key derived from the refresh token the access token | |
509 # based on. | |
510 # The default is 'file_system'. | |
511 #token_cache = file_system | |
512 #token_cache = in_memory | |
513 | |
514 # 'token_cache_path_pattern' specifies a path pattern for token cache files. | |
515 # This option is only relevant if token_cache = file_system. | |
516 # The value of this option should be a path, with place-holders '%(key)s' (which | |
517 # will be replaced with a key derived from the refresh token the cached access | |
518 # token was based on), and (optionally), %(uid)s (which will be replaced with | |
519 # the UID of the current user, if available via os.getuid()). | |
520 # Note that the config parser itself interpolates '%' placeholders, and hence | |
521 # the above placeholders need to be escaped as '%%(key)s'. | |
522 # The default value of this option is | |
523 # token_cache_path_pattern = <tmpdir>/oauth2client-tokencache.%%(uid)s.%%(key)s | |
524 # where <tmpdir> is the system-dependent default temp directory. | |
525 | |
526 # The following options specify the OAuth2 client identity and secret that is | |
527 # used when requesting and using OAuth2 tokens. If not specified, a default | |
528 # OAuth2 client for the gsutil tool is used; for uses of the boto library (with | |
529 # OAuth2 authentication plugin) in other client software, it is recommended to | |
530 # use a tool/client-specific OAuth2 client. For more information on OAuth2, see | |
531 # http://code.google.com/apis/accounts/docs/OAuth2.html | |
532 #client_id = <OAuth2 client id> | |
533 #client_secret = <OAuth2 client secret> | |
534 | |
535 # The following options specify the label and endpoint URIs for the OAUth2 | |
536 # authorization provider being used. Primarily useful for tool developers. | |
537 #provider_label = Google | |
538 #provider_authorization_uri = https://accounts.google.com/o/oauth2/auth | |
539 #provider_token_uri = https://accounts.google.com/o/oauth2/token | |
540 | |
541 # 'oauth2_refresh_retries' controls the number of retry attempts made when | |
542 # rate limiting errors occur for OAuth2 requests to retrieve an access token. | |
543 # The default value is 6. | |
544 #oauth2_refresh_retries = <integer value> | |
545 """ | |
546 | |
547 | |
548 class ConfigCommand(Command): | |
549 """Implementation of gsutil config command.""" | |
550 | |
551 # Command specification. See base class for documentation. | |
552 command_spec = Command.CreateCommandSpec( | |
553 'config', | |
554 command_name_aliases=['cfg', 'conf', 'configure'], | |
555 usage_synopsis=_SYNOPSIS, | |
556 min_args=0, | |
557 max_args=0, | |
558 supported_sub_args='habefwrs:o:', | |
559 file_url_ok=False, | |
560 provider_url_ok=False, | |
561 urls_start_arg=0, | |
562 ) | |
563 # Help specification. See help_provider.py for documentation. | |
564 help_spec = Command.HelpSpec( | |
565 help_name='config', | |
566 help_name_aliases=['cfg', 'conf', 'configure', 'aws', 's3'], | |
567 help_type='command_help', | |
568 help_one_line_summary=( | |
569 'Obtain credentials and create configuration file'), | |
570 help_text=_DETAILED_HELP_TEXT, | |
571 subcommand_help_text={}, | |
572 ) | |
573 | |
574 def _OpenConfigFile(self, file_path): | |
575 """Creates and opens a configuration file for writing. | |
576 | |
577 The file is created with mode 0600, and attempts to open existing files will | |
578 fail (the latter is important to prevent symlink attacks). | |
579 | |
580 It is the caller's responsibility to close the file. | |
581 | |
582 Args: | |
583 file_path: Path of the file to be created. | |
584 | |
585 Returns: | |
586 A writable file object for the opened file. | |
587 | |
588 Raises: | |
589 CommandException: if an error occurred when opening the file (including | |
590 when the file already exists). | |
591 """ | |
592 flags = os.O_RDWR | os.O_CREAT | os.O_EXCL | |
593 # Accommodate Windows; copied from python2.6/tempfile.py. | |
594 if hasattr(os, 'O_NOINHERIT'): | |
595 flags |= os.O_NOINHERIT | |
596 try: | |
597 fd = os.open(file_path, flags, 0600) | |
598 except (OSError, IOError), e: | |
599 raise CommandException('Failed to open %s for writing: %s' % | |
600 (file_path, e)) | |
601 return os.fdopen(fd, 'w') | |
602 | |
603 def _CheckPrivateKeyFilePermissions(self, file_path): | |
604 """Checks that the file has reasonable permissions for a private key. | |
605 | |
606 In particular, check that the filename provided by the user is not | |
607 world- or group-readable. If either of these are true, we issue a warning | |
608 and offer to fix the permissions. | |
609 | |
610 Args: | |
611 file_path: The name of the private key file. | |
612 """ | |
613 if IS_WINDOWS: | |
614 # For Windows, this check doesn't work (it actually just checks whether | |
615 # the file is read-only). Since Windows files have a complicated ACL | |
616 # system, this check doesn't make much sense on Windows anyway, so we | |
617 # just don't do it. | |
618 return | |
619 | |
620 st = os.stat(file_path) | |
621 if bool((stat.S_IRGRP | stat.S_IROTH) & st.st_mode): | |
622 self.logger.warn( | |
623 '\nYour private key file is readable by people other than yourself.\n' | |
624 'This is a security risk, since anyone with this information can use ' | |
625 'your service account.\n') | |
626 fix_it = raw_input('Would you like gsutil to change the file ' | |
627 'permissions for you? (y/N) ') | |
628 if fix_it in ('y', 'Y'): | |
629 try: | |
630 os.chmod(file_path, 0400) | |
631 self.logger.info( | |
632 '\nThe permissions on your file have been successfully ' | |
633 'modified.' | |
634 '\nThe only access allowed is readability by the user ' | |
635 '(permissions 0400 in chmod).') | |
636 except Exception, _: # pylint: disable=broad-except | |
637 self.logger.warn( | |
638 '\nWe were unable to modify the permissions on your file.\n' | |
639 'If you would like to fix this yourself, consider running:\n' | |
640 '"sudo chmod 400 </path/to/key>" for improved security.') | |
641 else: | |
642 self.logger.info( | |
643 '\nYou have chosen to allow this file to be readable by others.\n' | |
644 'If you would like to fix this yourself, consider running:\n' | |
645 '"sudo chmod 400 </path/to/key>" for improved security.') | |
646 | |
647 def _PromptForProxyConfigVarAndMaybeSaveToBotoConfig(self, varname, prompt, | |
648 convert_to_bool=False): | |
649 """Prompts for one proxy config line, saves to boto.config if not empty. | |
650 | |
651 Args: | |
652 varname: The config variable name. | |
653 prompt: The prompt to output to the user. | |
654 convert_to_bool: Whether to convert "y/n" to True/False. | |
655 """ | |
656 value = raw_input(prompt) | |
657 if value: | |
658 if convert_to_bool: | |
659 if value == 'y' or value == 'Y': | |
660 value = 'True' | |
661 else: | |
662 value = 'False' | |
663 boto.config.set('Boto', varname, value) | |
664 | |
665 def _PromptForProxyConfig(self): | |
666 """Prompts for proxy config data, loads non-empty values into boto.config. | |
667 """ | |
668 self._PromptForProxyConfigVarAndMaybeSaveToBotoConfig( | |
669 'proxy', 'What is your proxy host? ') | |
670 self._PromptForProxyConfigVarAndMaybeSaveToBotoConfig( | |
671 'proxy_port', 'What is your proxy port? ') | |
672 self._PromptForProxyConfigVarAndMaybeSaveToBotoConfig( | |
673 'proxy_user', 'What is your proxy user (leave blank if not used)? ') | |
674 self._PromptForProxyConfigVarAndMaybeSaveToBotoConfig( | |
675 'proxy_pass', 'What is your proxy pass (leave blank if not used)? ') | |
676 self._PromptForProxyConfigVarAndMaybeSaveToBotoConfig( | |
677 'proxy_rdns', | |
678 'Should DNS lookups be resolved by your proxy? (Y if your site ' | |
679 'disallows client DNS lookups)? ', | |
680 convert_to_bool=True) | |
681 | |
682 def _WriteConfigLineMaybeCommented(self, config_file, name, value, desc): | |
683 """Writes proxy name/value pair or comment line to config file. | |
684 | |
685 Writes proxy name/value pair if value is not None. Otherwise writes | |
686 comment line. | |
687 | |
688 Args: | |
689 config_file: File object to which the resulting config file will be | |
690 written. | |
691 name: The config variable name. | |
692 value: The value, or None. | |
693 desc: Human readable description (for comment). | |
694 """ | |
695 if not value: | |
696 name = '#%s' % name | |
697 value = '<%s>' % desc | |
698 config_file.write('%s = %s\n' % (name, value)) | |
699 | |
700 def _WriteProxyConfigFileSection(self, config_file): | |
701 """Writes proxy section of configuration file. | |
702 | |
703 Args: | |
704 config_file: File object to which the resulting config file will be | |
705 written. | |
706 """ | |
707 config = boto.config | |
708 config_file.write( | |
709 '# To use a proxy, edit and uncomment the proxy and proxy_port lines.\n' | |
710 '# If you need a user/password with this proxy, edit and uncomment\n' | |
711 '# those lines as well. If your organization also disallows DNS\n' | |
712 '# lookups by client machines set proxy_rdns = True\n' | |
713 '# If proxy_host and proxy_port are not specified in this file and\n' | |
714 '# one of the OS environment variables http_proxy, https_proxy, or\n' | |
715 '# HTTPS_PROXY is defined, gsutil will use the proxy server specified\n' | |
716 '# in these environment variables, in order of precedence according\n' | |
717 '# to how they are listed above.\n') | |
718 self._WriteConfigLineMaybeCommented( | |
719 config_file, 'proxy', config.get_value('Boto', 'proxy', None), | |
720 'proxy host') | |
721 self._WriteConfigLineMaybeCommented( | |
722 config_file, 'proxy_port', config.get_value('Boto', 'proxy_port', None), | |
723 'proxy port') | |
724 self._WriteConfigLineMaybeCommented( | |
725 config_file, 'proxy_user', config.get_value('Boto', 'proxy_user', None), | |
726 'proxy user') | |
727 self._WriteConfigLineMaybeCommented( | |
728 config_file, 'proxy_pass', config.get_value('Boto', 'proxy_pass', None), | |
729 'proxy password') | |
730 self._WriteConfigLineMaybeCommented( | |
731 config_file, 'proxy_rdns', | |
732 config.get_value('Boto', 'proxy_rdns', False), | |
733 'let proxy server perform DNS lookups') | |
734 | |
735 # pylint: disable=dangerous-default-value,too-many-statements | |
736 def _WriteBotoConfigFile(self, config_file, launch_browser=True, | |
737 oauth2_scopes=[SCOPE_FULL_CONTROL], | |
738 cred_type=CredTypes.OAUTH2_USER_ACCOUNT): | |
739 """Creates a boto config file interactively. | |
740 | |
741 Needed credentials are obtained interactively, either by asking the user for | |
742 access key and secret, or by walking the user through the OAuth2 approval | |
743 flow. | |
744 | |
745 Args: | |
746 config_file: File object to which the resulting config file will be | |
747 written. | |
748 launch_browser: In the OAuth2 approval flow, attempt to open a browser | |
749 window and navigate to the approval URL. | |
750 oauth2_scopes: A list of OAuth2 scopes to request authorization for, when | |
751 using OAuth2. | |
752 cred_type: There are three options: | |
753 - for HMAC, ask the user for access key and secret | |
754 - for OAUTH2_USER_ACCOUNT, walk the user through OAuth2 approval flow | |
755 and produce a config with an oauth2_refresh_token credential. | |
756 - for OAUTH2_SERVICE_ACCOUNT, prompt the user for OAuth2 for service | |
757 account email address and private key file (and if the file is a .p12 | |
758 file, the password for that file). | |
759 """ | |
760 # Collect credentials | |
761 provider_map = {'aws': 'aws', 'google': 'gs'} | |
762 uri_map = {'aws': 's3', 'google': 'gs'} | |
763 key_ids = {} | |
764 sec_keys = {} | |
765 service_account_key_is_json = False | |
766 if cred_type == CredTypes.OAUTH2_SERVICE_ACCOUNT: | |
767 gs_service_key_file = raw_input('What is the full path to your private ' | |
768 'key file? ') | |
769 # JSON files have the email address built-in and don't require a password. | |
770 try: | |
771 with open(gs_service_key_file, 'rb') as key_file_fp: | |
772 json.loads(key_file_fp.read()) | |
773 service_account_key_is_json = True | |
774 except ValueError: | |
775 if not HAS_CRYPTO: | |
776 raise CommandException( | |
777 'Service account authentication via a .p12 file requires ' | |
778 'either\nPyOpenSSL or PyCrypto 2.6 or later. Please install ' | |
779 'either of these\nto proceed, use a JSON-format key file, or ' | |
780 'configure a different type of credentials.') | |
781 | |
782 if not service_account_key_is_json: | |
783 gs_service_client_id = raw_input('What is your service account email ' | |
784 'address? ') | |
785 gs_service_key_file_password = raw_input( | |
786 '\n'.join(textwrap.wrap( | |
787 'What is the password for your service key file [if you ' | |
788 'haven\'t set one explicitly, leave this line blank]?')) + ' ') | |
789 self._CheckPrivateKeyFilePermissions(gs_service_key_file) | |
790 elif cred_type == CredTypes.OAUTH2_USER_ACCOUNT: | |
791 oauth2_client = oauth2_helper.OAuth2ClientFromBotoConfig(boto.config, | |
792 cred_type) | |
793 try: | |
794 oauth2_refresh_token = oauth2_helper.OAuth2ApprovalFlow( | |
795 oauth2_client, oauth2_scopes, launch_browser) | |
796 except (ResponseNotReady, ServerNotFoundError, socket.error): | |
797 # TODO: Determine condition to check for in the ResponseNotReady | |
798 # exception so we only run proxy config flow if failure was caused by | |
799 # request being blocked because it wasn't sent through proxy. (This | |
800 # error could also happen if gsutil or the oauth2 client had a bug that | |
801 # attempted to incorrectly reuse an HTTP connection, for example.) | |
802 sys.stdout.write('\n'.join(textwrap.wrap( | |
803 "Unable to connect to accounts.google.com during OAuth2 flow. This " | |
804 "can happen if your site uses a proxy. If you are using gsutil " | |
805 "through a proxy, please enter the proxy's information; otherwise " | |
806 "leave the following fields blank.")) + '\n') | |
807 self._PromptForProxyConfig() | |
808 oauth2_client = oauth2_helper.OAuth2ClientFromBotoConfig(boto.config, | |
809 cred_type) | |
810 oauth2_refresh_token = oauth2_helper.OAuth2ApprovalFlow( | |
811 oauth2_client, oauth2_scopes, launch_browser) | |
812 elif cred_type == CredTypes.HMAC: | |
813 got_creds = False | |
814 for provider in provider_map: | |
815 if provider == 'google': | |
816 key_ids[provider] = raw_input('What is your %s access key ID? ' % | |
817 provider) | |
818 sec_keys[provider] = raw_input('What is your %s secret access key? ' % | |
819 provider) | |
820 got_creds = True | |
821 if not key_ids[provider] or not sec_keys[provider]: | |
822 raise CommandException( | |
823 'Incomplete credentials provided. Please try again.') | |
824 if not got_creds: | |
825 raise CommandException('No credentials provided. Please try again.') | |
826 | |
827 # Write the config file prelude. | |
828 config_file.write(CONFIG_PRELUDE_CONTENT.lstrip()) | |
829 config_file.write( | |
830 '# This file was created by gsutil version %s at %s.\n' | |
831 % (gslib.VERSION, | |
832 datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) | |
833 config_file.write( | |
834 '#\n# You can create additional configuration files by ' | |
835 'running\n# gsutil config [options] [-o <config-file>]\n\n\n') | |
836 | |
837 # Write the config file Credentials section. | |
838 config_file.write('[Credentials]\n\n') | |
839 if cred_type == CredTypes.OAUTH2_SERVICE_ACCOUNT: | |
840 config_file.write('# Google OAuth2 service account credentials ' | |
841 '(for "gs://" URIs):\n') | |
842 config_file.write('gs_service_key_file = %s\n' % gs_service_key_file) | |
843 if not service_account_key_is_json: | |
844 config_file.write('gs_service_client_id = %s\n' | |
845 % gs_service_client_id) | |
846 | |
847 if not gs_service_key_file_password: | |
848 config_file.write( | |
849 '# If you would like to set your password, you can do so using\n' | |
850 '# the following commands (replaced with your information):\n' | |
851 '# "openssl pkcs12 -in cert1.p12 -out temp_cert.pem"\n' | |
852 '# "openssl pkcs12 -export -in temp_cert.pem -out cert2.p12"\n' | |
853 '# "rm -f temp_cert.pem"\n' | |
854 '# Your initial password is "notasecret" - for more information,' | |
855 '\n# please see http://www.openssl.org/docs/apps/pkcs12.html.\n') | |
856 config_file.write('#gs_service_key_file_password =\n\n') | |
857 else: | |
858 config_file.write('gs_service_key_file_password = %s\n\n' | |
859 % gs_service_key_file_password) | |
860 elif cred_type == CredTypes.OAUTH2_USER_ACCOUNT: | |
861 config_file.write( | |
862 '# Google OAuth2 credentials (for "gs://" URIs):\n' | |
863 '# The following OAuth2 account is authorized for scope(s):\n') | |
864 for scope in oauth2_scopes: | |
865 config_file.write('# %s\n' % scope) | |
866 config_file.write( | |
867 'gs_oauth2_refresh_token = %s\n\n' % oauth2_refresh_token) | |
868 else: | |
869 config_file.write( | |
870 '# To add Google OAuth2 credentials ("gs://" URIs), ' | |
871 'edit and uncomment the\n# following line:\n' | |
872 '#gs_oauth2_refresh_token = <your OAuth2 refresh token>\n\n') | |
873 | |
874 for provider in provider_map: | |
875 key_prefix = provider_map[provider] | |
876 uri_scheme = uri_map[provider] | |
877 if provider in key_ids and provider in sec_keys: | |
878 config_file.write('# %s credentials ("%s://" URIs):\n' % | |
879 (provider, uri_scheme)) | |
880 config_file.write('%s_access_key_id = %s\n' % | |
881 (key_prefix, key_ids[provider])) | |
882 config_file.write('%s_secret_access_key = %s\n' % | |
883 (key_prefix, sec_keys[provider])) | |
884 else: | |
885 config_file.write( | |
886 '# To add %s credentials ("%s://" URIs), edit and ' | |
887 'uncomment the\n# following two lines:\n' | |
888 '#%s_access_key_id = <your %s access key ID>\n' | |
889 '#%s_secret_access_key = <your %s secret access key>\n' % | |
890 (provider, uri_scheme, key_prefix, provider, key_prefix, | |
891 provider)) | |
892 host_key = Provider.HostKeyMap[provider] | |
893 config_file.write( | |
894 '# The ability to specify an alternate storage host and port\n' | |
895 '# is primarily for cloud storage service developers.\n' | |
896 '# Setting a non-default gs_host only works if prefer_api=xml.\n' | |
897 '#%s_host = <alternate storage host address>\n' | |
898 '#%s_port = <alternate storage host port>\n' | |
899 % (host_key, host_key)) | |
900 if host_key == 'gs': | |
901 config_file.write( | |
902 '#%s_json_host = <alternate JSON API storage host address>\n' | |
903 '#%s_json_port = <alternate JSON API storage host port>\n\n' | |
904 % (host_key, host_key)) | |
905 config_file.write('\n') | |
906 | |
907 # Write the config file Boto section. | |
908 config_file.write('%s\n' % CONFIG_BOTO_SECTION_CONTENT) | |
909 self._WriteProxyConfigFileSection(config_file) | |
910 | |
911 # Write the config file GSUtil section that doesn't depend on user input. | |
912 config_file.write(CONFIG_INPUTLESS_GSUTIL_SECTION_CONTENT) | |
913 | |
914 # Write the default API version. | |
915 config_file.write(""" | |
916 # 'default_api_version' specifies the default Google Cloud Storage XML API | |
917 # version to use. If not set below gsutil defaults to API version 1. | |
918 """) | |
919 api_version = 2 | |
920 if cred_type == CredTypes.HMAC: api_version = 1 | |
921 | |
922 config_file.write('default_api_version = %d\n' % api_version) | |
923 | |
924 # Write the config file GSUtil section that includes the default | |
925 # project ID input from the user. | |
926 if launch_browser: | |
927 sys.stdout.write( | |
928 'Attempting to launch a browser to open the Google Cloud Console at ' | |
929 'URL: %s\n\n' | |
930 '[Note: due to a Python bug, you may see a spurious error message ' | |
931 '"object is not\ncallable [...] in [...] Popen.__del__" which can ' | |
932 'be ignored.]\n\n' % GOOG_CLOUD_CONSOLE_URI) | |
933 sys.stdout.write( | |
934 'In your browser you should see the Cloud Console. Find the project ' | |
935 'you will\nuse, and then copy the Project ID string from the second ' | |
936 'column. Older projects do\nnot have Project ID strings. For such ' | |
937 'projects, click the project and then copy the\nProject Number ' | |
938 'listed under that project.\n\n') | |
939 if not webbrowser.open(GOOG_CLOUD_CONSOLE_URI, new=1, autoraise=True): | |
940 sys.stdout.write( | |
941 'Launching browser appears to have failed; please navigate a ' | |
942 'browser to the following URL:\n%s\n' % GOOG_CLOUD_CONSOLE_URI) | |
943 # Short delay; webbrowser.open on linux insists on printing out a message | |
944 # which we don't want to run into the prompt for the auth code. | |
945 time.sleep(2) | |
946 else: | |
947 sys.stdout.write( | |
948 '\nPlease navigate your browser to %s,\nthen find the project you ' | |
949 'will use, and copy the Project ID string from the\nsecond column. ' | |
950 'Older projects do not have Project ID strings. For such projects,\n' | |
951 'click the project and then copy the Project Number listed under ' | |
952 'that project.\n\n' % GOOG_CLOUD_CONSOLE_URI) | |
953 default_project_id = raw_input('What is your project-id? ').strip() | |
954 project_id_section_prelude = """ | |
955 # 'default_project_id' specifies the default Google Cloud Storage project ID to | |
956 # use with the 'mb' and 'ls' commands. This default can be overridden by | |
957 # specifying the -p option to the 'mb' and 'ls' commands. | |
958 """ | |
959 if not default_project_id: | |
960 raise CommandException( | |
961 'No default project ID entered. The default project ID is needed by ' | |
962 'the\nls and mb commands; please try again.') | |
963 config_file.write('%sdefault_project_id = %s\n\n\n' % | |
964 (project_id_section_prelude, default_project_id)) | |
965 | |
966 # Write the config file OAuth2 section. | |
967 config_file.write(CONFIG_OAUTH2_CONFIG_CONTENT) | |
968 | |
969 def RunCommand(self): | |
970 """Command entry point for the config command.""" | |
971 scopes = [] | |
972 cred_type = CredTypes.OAUTH2_USER_ACCOUNT | |
973 launch_browser = False | |
974 output_file_name = None | |
975 has_a = False | |
976 has_e = False | |
977 for opt, opt_arg in self.sub_opts: | |
978 if opt == '-a': | |
979 cred_type = CredTypes.HMAC | |
980 has_a = True | |
981 elif opt == '-b': | |
982 launch_browser = True | |
983 elif opt == '-e': | |
984 cred_type = CredTypes.OAUTH2_SERVICE_ACCOUNT | |
985 has_e = True | |
986 elif opt == '-f': | |
987 scopes.append(SCOPE_FULL_CONTROL) | |
988 elif opt == '-o': | |
989 output_file_name = opt_arg | |
990 elif opt == '-r': | |
991 scopes.append(SCOPE_READ_ONLY) | |
992 elif opt == '-s': | |
993 scopes.append(opt_arg) | |
994 elif opt == '-w': | |
995 scopes.append(SCOPE_READ_WRITE) | |
996 else: | |
997 self.RaiseInvalidArgumentException() | |
998 | |
999 if has_e and has_a: | |
1000 raise CommandException('Both -a and -e cannot be specified. Please see ' | |
1001 '"gsutil help config" for more information.') | |
1002 | |
1003 if not scopes: | |
1004 scopes.append(SCOPE_FULL_CONTROL) | |
1005 | |
1006 default_config_path_bak = None | |
1007 if not output_file_name: | |
1008 # Check to see if a default config file name is requested via | |
1009 # environment variable. If so, use it, otherwise use the hard-coded | |
1010 # default file. Then use the default config file name, if it doesn't | |
1011 # exist or can be moved out of the way without clobbering an existing | |
1012 # backup file. | |
1013 boto_config_from_env = os.environ.get('BOTO_CONFIG', None) | |
1014 if boto_config_from_env: | |
1015 default_config_path = boto_config_from_env | |
1016 else: | |
1017 default_config_path = os.path.expanduser(os.path.join('~', '.boto')) | |
1018 if not os.path.exists(default_config_path): | |
1019 output_file_name = default_config_path | |
1020 else: | |
1021 default_config_path_bak = default_config_path + '.bak' | |
1022 if os.path.exists(default_config_path_bak): | |
1023 raise CommandException( | |
1024 'Cannot back up existing config ' | |
1025 'file "%s": backup file exists ("%s").' | |
1026 % (default_config_path, default_config_path_bak)) | |
1027 else: | |
1028 try: | |
1029 sys.stderr.write( | |
1030 'Backing up existing config file "%s" to "%s"...\n' | |
1031 % (default_config_path, default_config_path_bak)) | |
1032 os.rename(default_config_path, default_config_path_bak) | |
1033 except Exception, e: | |
1034 raise CommandException( | |
1035 'Failed to back up existing config ' | |
1036 'file ("%s" -> "%s"): %s.' | |
1037 % (default_config_path, default_config_path_bak, e)) | |
1038 output_file_name = default_config_path | |
1039 | |
1040 if output_file_name == '-': | |
1041 output_file = sys.stdout | |
1042 else: | |
1043 output_file = self._OpenConfigFile(output_file_name) | |
1044 sys.stderr.write('\n'.join(textwrap.wrap( | |
1045 'This command will create a boto config file at %s containing your ' | |
1046 'credentials, based on your responses to the following questions.' | |
1047 % output_file_name)) + '\n') | |
1048 | |
1049 # Catch ^C so we can restore the backup. | |
1050 RegisterSignalHandler(signal.SIGINT, _CleanupHandler) | |
1051 try: | |
1052 self._WriteBotoConfigFile(output_file, launch_browser=launch_browser, | |
1053 oauth2_scopes=scopes, cred_type=cred_type) | |
1054 except Exception as e: | |
1055 user_aborted = isinstance(e, AbortException) | |
1056 if user_aborted: | |
1057 sys.stderr.write('\nCaught ^C; cleaning up\n') | |
1058 # If an error occurred during config file creation, remove the invalid | |
1059 # config file and restore the backup file. | |
1060 if output_file_name != '-': | |
1061 output_file.close() | |
1062 os.unlink(output_file_name) | |
1063 try: | |
1064 if default_config_path_bak: | |
1065 sys.stderr.write('Restoring previous backed up file (%s)\n' % | |
1066 default_config_path_bak) | |
1067 os.rename(default_config_path_bak, output_file_name) | |
1068 except Exception as e: | |
1069 # Raise the original exception so that we can see what actually went | |
1070 # wrong, rather than just finding out that we died before assigning | |
1071 # a value to default_config_path_bak. | |
1072 raise e | |
1073 raise | |
1074 | |
1075 if output_file_name != '-': | |
1076 output_file.close() | |
1077 if not boto.config.has_option('Boto', 'proxy'): | |
1078 sys.stderr.write('\n' + '\n'.join(textwrap.wrap( | |
1079 'Boto config file "%s" created.\nIf you need to use a proxy to ' | |
1080 'access the Internet please see the instructions in that file.' | |
1081 % output_file_name)) + '\n') | |
1082 | |
1083 return 0 | |
1084 | |
1085 | |
1086 def _CleanupHandler(unused_signalnum, unused_handler): | |
1087 raise AbortException('User interrupted config command') | |
OLD | NEW |