1 # -*- coding: utf-8 -*- 2 # Copyright 2013 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 Unix-like stat command for cloud storage providers.""" 16 17 from __future__ import absolute_import 18 19 import logging 20 import sys 21 22 from gslib.bucket_listing_ref import BucketListingObject 23 from gslib.cloud_api import AccessDeniedException 24 from gslib.cloud_api import NotFoundException 25 from gslib.command import Command 26 from gslib.command_argument import CommandArgument 27 from gslib.cs_api_map import ApiSelector 28 from gslib.exception import CommandException 29 from gslib.exception import InvalidUrlError 30 from gslib.storage_url import ContainsWildcard 31 from gslib.storage_url import StorageUrlFromString 32 from gslib.util import NO_MAX 33 from gslib.util import PrintFullInfoAboutObject 34 35 36 _SYNOPSIS = """ 37 gsutil stat url... 38 """ 39 40 _DETAILED_HELP_TEXT = (""" 41 <B>SYNOPSIS</B> 42 """ + _SYNOPSIS + """ 43 44 45 <B>DESCRIPTION</B> 46 The stat command will output details about the specified object URLs. 47 It is similar to running: 48 49 gsutil ls -L gs://some-bucket/some-object 50 51 but is more efficient because it avoids performing bucket listings and gets 52 the minimum necessary amount of object metadata. Moreover, because it avoids 53 performing bucket listings (which are eventually consistent) the gsutil stat 54 command provides a strongly consistent way to check for the existence (and 55 read the metadata) of an object. 56 57 The gsutil stat command will, however, perform bucket listings if you specify 58 URLs using wildcards. 59 60 If run with the gsutil -q option nothing will be printed, e.g.: 61 62 gsutil -q stat gs://some-bucket/some-object 63 64 This can be useful for writing scripts, because the exit status will be 0 for 65 an existing object and 1 for a non-existent object. 66 67 Note: Unlike the gsutil ls command, the stat command does not support 68 operations on sub-directories. For example, if you run the command: 69 70 gsutil -q stat gs://some-bucket/some-subdir/ 71 72 gsutil will look for information about an object called "some-subdir/" (with a 73 trailing slash) inside the bucket "some-bucket", as opposed to operating on 74 objects nested under gs://some-bucket/some-subdir/. Unless you actually have 75 an object with that name, the operation will fail. However, you can use the 76 stat command on objects within subdirectories. For example, this command will 77 work as expected: 78 79 gsutil -q stat gs://some-bucket/some-subdir/file.txt 80 """) 81 82 83 # TODO: Add ability to stat buckets. 84 class StatCommand(Command): 85 """Implementation of gsutil stat command.""" 86 87 # Command specification. See base class for documentation. 88 command_spec = Command.CreateCommandSpec( 89 'stat', 90 command_name_aliases=[], 91 usage_synopsis=_SYNOPSIS, 92 min_args=1, 93 max_args=NO_MAX, 94 supported_sub_args='', 95 file_url_ok=False, 96 provider_url_ok=False, 97 urls_start_arg=0, 98 gs_api_support=[ApiSelector.XML, ApiSelector.JSON], 99 gs_default_api=ApiSelector.JSON, 100 argparse_arguments=[ 101 CommandArgument.MakeZeroOrMoreCloudURLsArgument() 102 ] 103 ) 104 # Help specification. See help_provider.py for documentation. 105 help_spec = Command.HelpSpec( 106 help_name='stat', 107 help_name_aliases=[], 108 help_type='command_help', 109 help_one_line_summary='Display object status', 110 help_text=_DETAILED_HELP_TEXT, 111 subcommand_help_text={}, 112 ) 113 114 def RunCommand(self): 115 """Command entry point for stat command.""" 116 # List of fields we'll print for stat objects. 117 stat_fields = ['updated', 'cacheControl', 'contentDisposition', 118 'contentEncoding', 'contentLanguage', 'size', 'contentType', 119 'componentCount', 'metadata', 'crc32c', 'md5Hash', 'etag', 120 'generation', 'metageneration'] 121 found_nonmatching_arg = False 122 for url_str in self.args: 123 arg_matches = 0 124 url = StorageUrlFromString(url_str) 125 if not url.IsObject(): 126 raise CommandException('The stat command only works with object URLs') 127 try: 128 if ContainsWildcard(url_str): 129 blr_iter = self.WildcardIterator(url_str).IterObjects( 130 bucket_listing_fields=stat_fields) 131 else: 132 single_obj = self.gsutil_api.GetObjectMetadata( 133 url.bucket_name, url.object_name, generation=url.generation, 134 provider=url.scheme, fields=stat_fields) 135 blr_iter = [BucketListingObject(url, root_object=single_obj)] 136 for blr in blr_iter: 137 if blr.IsObject(): 138 arg_matches += 1 139 if logging.getLogger().isEnabledFor(logging.INFO): 140 PrintFullInfoAboutObject(blr, incl_acl=False) 141 except AccessDeniedException: 142 sys.stderr.write('You aren\'t authorized to read %s - skipping' % 143 url_str) 144 except InvalidUrlError: 145 raise 146 except NotFoundException: 147 pass 148 if not arg_matches: 149 sys.stderr.write('No URLs matched %s' % url_str) 150 found_nonmatching_arg = True 151 if found_nonmatching_arg: 152 return 1 153 return 0 154