8.2.34 | Linux | www-data (uid:33)
👁 View: tail.py
📁 /snap/google-cloud-cli/503/lib/surface/storage/tail.py
# -*- coding: utf-8 -*- # # Copyright 2026 Google LLC. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Implementation of Unix-like tail command for cloud storage providers.""" import os from googlecloudsdk.api_lib.storage import api_factory from googlecloudsdk.api_lib.storage import cloud_api from googlecloudsdk.calliope import arg_parsers from googlecloudsdk.calliope import base from googlecloudsdk.command_lib.storage import encryption_util from googlecloudsdk.command_lib.storage import errors from googlecloudsdk.command_lib.storage import flags from googlecloudsdk.command_lib.storage import storage_url from googlecloudsdk.command_lib.storage.resources import resource_reference from googlecloudsdk.command_lib.storage.tasks.cp import streaming_download_task def _validate_bytes_flag(value: str) -> str: """Validates that the --bytes flag value is a valid integer or +integer. Args: value: The raw string passed to the --bytes flag. Returns: The validated string. Raises: arg_parsers.ArgumentTypeError: If value cannot be parsed as an integer. """ try: int(value) return value except ValueError as exc: raise arg_parsers.ArgumentTypeError( 'Invalid --bytes value: {}. Must be an integer or +integer.'.format( value ) ) from exc def _calculate_start_byte( object_size: int | None, bytes_to_read: str | int ) -> int: """Calculates the absolute start byte position for tailing. Formula: - For +BYTES: min(object_size, max(0, int(BYTES))). - For BYTES: max(0, object_size - abs(int(BYTES))). Does not rely on negative slicing or backend offsets. Args: object_size (int|None): Total size of the source object in bytes. bytes_to_read (str|int): Number of bytes requested from the end of the object, or starting byte offset when prefixed with '+'. Returns: int: Absolute byte offset to begin reading from. """ if not object_size or object_size <= 0: return 0 if isinstance(bytes_to_read, str) and bytes_to_read.startswith('+'): return min(object_size, max(0, int(bytes_to_read[1:]))) return max(0, object_size - abs(int(bytes_to_read))) @base.Hidden @base.ReleaseTracks(base.ReleaseTrack.ALPHA) @base.DefaultUniverseOnly class Tail(base.Command): """Outputs the last parts (the tail) of an object to stdout.""" hints = base.CommandHint(read_only=True) detailed_help = { 'DESCRIPTION': """ The tail command outputs the last B bytes of an object to stdout. It can also be used to monitor real time appends to an appendable object in the RAPID storage. """, 'EXAMPLES': """ The following command outputs the last 1000 bytes of an object: $ {command} gs://my-bucket/object The following command outputs the last 100 bytes of an object: $ {command} gs://my-bucket/object --bytes 100 The following command continuously streams new data as it is appended to the appendable object: $ {command} gs://my-bucket/appendable-object --follow """, } @staticmethod def Args(parser): parser.add_argument('url', help='The URL of the object to tail.') parser.add_argument( '--bytes', default='1000', type=_validate_bytes_flag, help=( 'Output the last BYTES bytes of the object, or use +BYTES to' ' output starting with byte BYTES of the object (capped at object' ' size). Default is 1000.' ), ) parser.add_argument( '--follow', action='store_true', help=( 'Output the last B bytes of the appendable object and continuously' ' streams new bytes appended to the appendable object.' ), ) flags.add_encryption_flags(parser, command_only_reads_data=True) flags.add_additional_headers_flag(parser) def Run(self, args): encryption_util.initialize_key_store(args) url_object = storage_url.storage_url_from_string(args.url) if not isinstance(url_object, storage_url.CloudUrl): raise errors.InvalidUrlError( 'tail only works for valid cloud URLs. {} is an invalid cloud URL.' .format(url_object.url_string) ) client = api_factory.get_api(url_object.scheme) resource = client.get_object_metadata( url_object.bucket_name, url_object.resource_name, generation=url_object.generation, fields_scope=cloud_api.FieldsScope.NO_ACL, ) # Set up direct unbuffered stdout stream. stdout_stream = os.fdopen(1, 'wb', closefd=False) dummy_destination_resource = resource_reference.FileObjectResource( storage_url.FileUrl('-') ) task = streaming_download_task.StreamingDownloadTask( source_resource=resource, destination_resource=dummy_destination_resource, download_stream=stdout_stream, start_byte=_calculate_start_byte(resource.size, args.bytes), end_byte=None, follow=args.follow, ) task.execute()
← Back
💻 Terminal
📤 Upload
📂
Click or Drag & Drop
📁 /snap/google-cloud-cli/503/lib/surface/storage
NameSizePermModifiedActions
📁 __pycache__/-07752026-09-29 12:17🗑 Delete
📁 batch_operations/-07752026-09-29 12:17🗑 Delete
📁 buckets/-07752026-09-29 12:17🗑 Delete
📁 feature_configs/-07752026-09-29 12:17🗑 Delete
📁 folders/-07752026-09-29 12:17🗑 Delete
📁 ftp/-07752026-09-29 12:17🗑 Delete
📁 hmac/-07752026-09-29 12:17🗑 Delete
📁 insights/-07752026-09-29 12:17🗑 Delete
📁 intelligence_configs/-07752026-09-29 12:17🗑 Delete
📁 intelligence_findings/-07752026-09-29 12:17🗑 Delete
📁 managed_folders/-07752026-09-29 12:17🗑 Delete
📁 objects/-07752026-09-29 12:17🗑 Delete
📁 operations/-07752026-09-29 12:17🗑 Delete
__init__.py1.9K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
cat.py5.3K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
copy.py7.2K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
cp.py3.3K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
delete.py5.7K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
diagnose.py15.1K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
du.py6.8K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
hash.py7.2K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
list.py5.5K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
ls.py10.4K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
mv.py4K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
restore.py13.3K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
rm.py10.2K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
rsync.py16.9K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
service_agent.py2.8K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
sign_url.py15K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del
tail.py5.5K06441980-01-01 08:00👁 View ✏️ Edit 📝 Rename 📦 Move 🗑 Del