Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / tools / telemetry / third_party / gsutilz / gslib / commands / config.py
blob386af159ac5f24784156ad75b4aeb59df5ce81ab
1 # -*- coding: utf-8 -*-
2 # Copyright 2011 Google Inc. All Rights Reserved.
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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."""
17 from __future__ import absolute_import
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
33 import boto
34 from boto.provider import Provider
35 from httplib2 import ServerNotFoundError
36 from oauth2client.client import HAS_CRYPTO
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
53 _SYNOPSIS = """
54 gsutil [-D] config [-a] [-b] [-e] [-f] [-o <file>] [-r] [-s <scope>] [-w]
55 """
57 _DETAILED_HELP_TEXT = ("""
58 <B>SYNOPSIS</B>
59 """ + _SYNOPSIS + """
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.
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).
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.)
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.
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.
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.
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).
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.
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.
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.
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:
131 /etc/projects/my_group_project.boto.cfg:/home/mylogin/.boto
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.)
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]").
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.
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.
162 The currently supported settings, are, by section:
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
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
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
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
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.
231 If no explicit scope option is given, -f (full control) is assumed by default.
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.
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.
245 -e Prompt for service account credentials. This option requires that
246 -a is not set.
248 -f Request token with full-control access (default).
250 -o <file> Write the configuration to <file> instead of ~/.boto.
251 Use '-' for stdout.
253 -r Request token restricted to read-only access.
255 -s <scope> Request additional OAuth2 <scope>.
257 -w Request token restricted to read-write access.
258 """)
261 try:
262 from gcs_oauth2_boto_plugin import oauth2_helper # pylint: disable=g-import-not-at-top
263 except ImportError:
264 pass
266 GOOG_CLOUD_CONSOLE_URI = 'https://cloud.google.com/console#/project'
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'
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).
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
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'
302 CONFIG_BOTO_SECTION_CONTENT = """
303 [Boto]
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
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.
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
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>
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>
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>
350 CONFIG_INPUTLESS_GSUTIL_SECTION_CONTENT = """
351 [GSUtil]
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
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
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
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
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
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)
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_component_size)s
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
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
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
474 # The ability to specify an alternative JSON API version is primarily for cloud
475 # storage service developers.
476 #json_api_version = v1
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
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}
498 CONFIG_OAUTH2_CONFIG_CONTENT = """
499 [OAuth2]
500 # This section specifies options used with OAuth2 authentication.
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
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.
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>
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
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>
548 class ConfigCommand(Command):
549 """Implementation of gsutil config command."""
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,
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={},
574 def _OpenConfigFile(self, file_path):
575 """Creates and opens a configuration file for writing.
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).
580 It is the caller's responsibility to close the file.
582 Args:
583 file_path: Path of the file to be created.
585 Returns:
586 A writable file object for the opened file.
588 Raises:
589 CommandException: if an error occurred when opening the file (including
590 when the file already exists).
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')
603 def _CheckPrivateKeyFilePermissions(self, file_path):
604 """Checks that the file has reasonable permissions for a private key.
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.
610 Args:
611 file_path: The name of the private key file.
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
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.')
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.
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.
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)
665 def _PromptForProxyConfig(self):
666 """Prompts for proxy config data, loads non-empty values into boto.config.
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)
682 def _WriteConfigLineMaybeCommented(self, config_file, name, value, desc):
683 """Writes proxy name/value pair or comment line to config file.
685 Writes proxy name/value pair if value is not None. Otherwise writes
686 comment line.
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).
695 if not value:
696 name = '#%s' % name
697 value = '<%s>' % desc
698 config_file.write('%s = %s\n' % (name, value))
700 def _WriteProxyConfigFileSection(self, config_file):
701 """Writes proxy section of configuration file.
703 Args:
704 config_file: File object to which the resulting config file will be
705 written.
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')
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.
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.
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).
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.')
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.')
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')
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)
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')
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')
907 # Write the config file Boto section.
908 config_file.write('%s\n' % CONFIG_BOTO_SECTION_CONTENT)
909 self._WriteProxyConfigFileSection(config_file)
911 # Write the config file GSUtil section that doesn't depend on user input.
912 config_file.write(CONFIG_INPUTLESS_GSUTIL_SECTION_CONTENT)
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
922 config_file.write('default_api_version = %d\n' % api_version)
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.
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))
966 # Write the config file OAuth2 section.
967 config_file.write(CONFIG_OAUTH2_CONFIG_CONTENT)
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()
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.')
1003 if not scopes:
1004 scopes.append(SCOPE_FULL_CONTROL)
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
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')
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
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')
1083 return 0
1086 def _CleanupHandler(unused_signalnum, unused_handler):
1087 raise AbortException('User interrupted config command')