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 |