Beispiel #1
0
  def ApplyAclChanges(self, name_expansion_result, thread_state=None):
    """Applies the changes in self.changes to the provided URL.

    Args:
      name_expansion_result: NameExpansionResult describing the target object.
      thread_state: If present, gsutil Cloud API instance to apply the changes.
    """
    if thread_state:
      gsutil_api = thread_state
    else:
      gsutil_api = self.gsutil_api

    url = name_expansion_result.expanded_storage_url

    if url.IsBucket():
      bucket = gsutil_api.GetBucket(url.bucket_name, provider=url.scheme,
                                    fields=['acl', 'metageneration'])
      current_acl = bucket.acl
    elif url.IsObject():
      gcs_object = encoding.JsonToMessage(apitools_messages.Object,
                                          name_expansion_result.expanded_result)
      current_acl = gcs_object.acl
    if not current_acl:
      self._RaiseForAccessDenied(url)

    modification_count = 0
    for change in self.changes:
      modification_count += change.Execute(url, current_acl, 'acl', self.logger)
    if modification_count == 0:
      self.logger.info('No changes to %s', url)
      return

    try:
      if url.IsBucket():
        preconditions = Preconditions(meta_gen_match=bucket.metageneration)
        bucket_metadata = apitools_messages.Bucket(acl=current_acl)
        gsutil_api.PatchBucket(url.bucket_name, bucket_metadata,
                               preconditions=preconditions,
                               provider=url.scheme, fields=['id'])
      else:  # Object
        preconditions = Preconditions(gen_match=gcs_object.generation,
                                      meta_gen_match=gcs_object.metageneration)

        object_metadata = apitools_messages.Object(acl=current_acl)
        gsutil_api.PatchObjectMetadata(
            url.bucket_name, url.object_name, object_metadata,
            preconditions=preconditions, provider=url.scheme,
            generation=url.generation, fields=['id'])
    except BadRequestException as e:
      # Don't retry on bad requests, e.g. invalid email address.
      raise CommandException('Received bad request from server: %s' % str(e))
    except AccessDeniedException:
      self._RaiseForAccessDenied(url)

    self.logger.info('Updated ACL on %s', url)
Beispiel #2
0
    def SetMetadataFunc(self, name_expansion_result, thread_state=None):
        """Sets metadata on an object.

    Args:
      name_expansion_result: NameExpansionResult describing target object.
      thread_state: gsutil Cloud API instance to use for the operation.
    """
        gsutil_api = GetCloudApiInstance(self, thread_state=thread_state)

        exp_src_url = name_expansion_result.expanded_storage_url
        self.logger.info("Setting metadata on %s...", exp_src_url)

        fields = ["generation", "metadata", "metageneration"]
        cloud_obj_metadata = gsutil_api.GetObjectMetadata(
            exp_src_url.bucket_name,
            exp_src_url.object_name,
            generation=exp_src_url.generation,
            provider=exp_src_url.scheme,
            fields=fields,
        )

        preconditions = Preconditions(
            gen_match=self.preconditions.gen_match, meta_gen_match=self.preconditions.meta_gen_match
        )
        if preconditions.gen_match is None:
            preconditions.gen_match = cloud_obj_metadata.generation
        if preconditions.meta_gen_match is None:
            preconditions.meta_gen_match = cloud_obj_metadata.metageneration

        # Patch handles the patch semantics for most metadata, but we need to
        # merge the custom metadata field manually.
        patch_obj_metadata = ObjectMetadataFromHeaders(self.metadata_change)

        api = gsutil_api.GetApiSelector(provider=exp_src_url.scheme)
        # For XML we only want to patch through custom metadata that has
        # changed.  For JSON we need to build the complete set.
        if api == ApiSelector.XML:
            pass
        elif api == ApiSelector.JSON:
            CopyObjectMetadata(patch_obj_metadata, cloud_obj_metadata, override=True)
            patch_obj_metadata = cloud_obj_metadata
            # Patch body does not need the object generation and metageneration.
            patch_obj_metadata.generation = None
            patch_obj_metadata.metageneration = None

        gsutil_api.PatchObjectMetadata(
            exp_src_url.bucket_name,
            exp_src_url.object_name,
            patch_obj_metadata,
            generation=exp_src_url.generation,
            preconditions=preconditions,
            provider=exp_src_url.scheme,
        )
Beispiel #3
0
    def SetMetadataFunc(self, name_expansion_result, thread_state=None):
        """Sets metadata on an object.

    Args:
      name_expansion_result: NameExpansionResult describing target object.
      thread_state: gsutil Cloud API instance to use for the operation.
    """
        gsutil_api = GetCloudApiInstance(self, thread_state=thread_state)

        exp_src_url = name_expansion_result.expanded_storage_url
        self.logger.info('Setting metadata on %s...', exp_src_url)

        cloud_obj_metadata = encoding.JsonToMessage(
            apitools_messages.Object, name_expansion_result.expanded_result)

        preconditions = Preconditions(
            gen_match=self.preconditions.gen_match,
            meta_gen_match=self.preconditions.meta_gen_match)
        if preconditions.gen_match is None:
            preconditions.gen_match = cloud_obj_metadata.generation
        if preconditions.meta_gen_match is None:
            preconditions.meta_gen_match = cloud_obj_metadata.metageneration

        # Patch handles the patch semantics for most metadata, but we need to
        # merge the custom metadata field manually.
        patch_obj_metadata = ObjectMetadataFromHeaders(self.metadata_change)

        api = gsutil_api.GetApiSelector(provider=exp_src_url.scheme)
        # For XML we only want to patch through custom metadata that has
        # changed.  For JSON we need to build the complete set.
        if api == ApiSelector.XML:
            pass
        elif api == ApiSelector.JSON:
            CopyObjectMetadata(patch_obj_metadata,
                               cloud_obj_metadata,
                               override=True)
            patch_obj_metadata = cloud_obj_metadata
            # Patch body does not need the object generation and metageneration.
            patch_obj_metadata.generation = None
            patch_obj_metadata.metageneration = None

        gsutil_api.PatchObjectMetadata(exp_src_url.bucket_name,
                                       exp_src_url.object_name,
                                       patch_obj_metadata,
                                       generation=exp_src_url.generation,
                                       preconditions=preconditions,
                                       provider=exp_src_url.scheme,
                                       fields=['id'])
        _PutToQueueWithTimeout(gsutil_api.status_queue,
                               MetadataMessage(message_time=time.time()))
Beispiel #4
0
  def ApplyAclChanges(self, url):
    """Applies the changes in self.changes to the provided URL."""
    bucket = self.gsutil_api.GetBucket(
        url.bucket_name, provider=url.scheme,
        fields=['defaultObjectAcl', 'metageneration'])
    current_acl = bucket.defaultObjectAcl
    if not current_acl:
      self._WarnServiceAccounts()
      self.logger.warning('Failed to set acl for %s. Please ensure you have '
                          'OWNER-role access to this resource.', url)
      return

    modification_count = 0
    for change in self.changes:
      modification_count += change.Execute(
          url, current_acl, 'defacl', self.logger)
    if modification_count == 0:
      self.logger.info('No changes to %s', url)
      return

    try:
      preconditions = Preconditions(meta_gen_match=bucket.metageneration)
      bucket_metadata = apitools_messages.Bucket(defaultObjectAcl=current_acl)
      self.gsutil_api.PatchBucket(url.bucket_name, bucket_metadata,
                                  preconditions=preconditions,
                                  provider=url.scheme, fields=['id'])
    except BadRequestException as e:
      # Don't retry on bad requests, e.g. invalid email address.
      raise CommandException('Received bad request from server: %s' % str(e))

    self.logger.info('Updated default ACL on %s', url)
Beispiel #5
0
  def ApplyAclChanges(self, url):
    """Applies the changes in self.changes to the provided URL."""
    bucket = self.gsutil_api.GetBucket(
        url.bucket_name, provider=url.scheme,
        fields=['defaultObjectAcl', 'metageneration'])

    # Default object ACLs can be blank if the ACL was set to private, or
    # if the user doesn't have permission. We warn about this with defacl get,
    # so just try the modification here and if the user doesn't have
    # permission they'll get an AccessDeniedException.
    current_acl = bucket.defaultObjectAcl

    modification_count = 0
    for change in self.changes:
      modification_count += change.Execute(
          url, current_acl, 'defacl', self.logger)
    if modification_count == 0:
      self.logger.info('No changes to %s', url)
      return

    try:
      preconditions = Preconditions(meta_gen_match=bucket.metageneration)
      bucket_metadata = apitools_messages.Bucket(defaultObjectAcl=current_acl)
      self.gsutil_api.PatchBucket(url.bucket_name, bucket_metadata,
                                  preconditions=preconditions,
                                  provider=url.scheme, fields=['id'])
    except BadRequestException as e:
      # Don't retry on bad requests, e.g. invalid email address.
      raise CommandException('Received bad request from server: %s' % str(e))
    except AccessDeniedException:
      self._WarnServiceAccounts()
      raise CommandException('Failed to set acl for %s. Please ensure you have '
                             'OWNER-role access to this resource.' % url)

    self.logger.info('Updated default ACL on %s', url)
Beispiel #6
0
  def CreateObjectJson(self, contents, bucket_name=None, object_name=None,
                       encryption_key=None, mtime=None, storage_class=None,
                       gs_idempotent_generation=None, kms_key_name=None):
    """Creates a test object (GCS provider only) using the JSON API.

    Args:
      contents: The contents to write to the object.
      bucket_name: Name of bucket to place the object in. If not specified,
          a new temporary bucket is created. Assumes the given bucket name is
          valid.
      object_name: The name to use for the object. If not specified, a temporary
          test object name is constructed.
      encryption_key: AES256 encryption key to use when creating the object,
          if any.
      mtime: The modification time of the file in POSIX time (seconds since
          UTC 1970-01-01). If not specified, this defaults to the current
          system time.
      storage_class: String representing the storage class to use for the
          object.
      gs_idempotent_generation: For use when overwriting an object for which
          you know the previously uploaded generation. Create GCS object
          idempotently by supplying this generation number as a precondition
          and assuming the current object is correct on precondition failure.
          Defaults to 0 (new object); to disable, set to None.
      kms_key_name: Fully-qualified name of the KMS key that should be used to
          encrypt the object. Note that this is currently only valid for 'gs'
          objects.

    Returns:
      An apitools Object for the created object.
    """
    bucket_name = bucket_name or self.CreateBucketJson().name
    object_name = object_name or self.MakeTempName('obj')
    preconditions = Preconditions(gen_match=gs_idempotent_generation)
    custom_metadata = apitools_messages.Object.MetadataValue(
        additionalProperties=[])
    if mtime is not None:
      CreateCustomMetadata({MTIME_ATTR: mtime}, custom_metadata)
    object_metadata = apitools_messages.Object(
        name=object_name,
        metadata=custom_metadata,
        bucket=bucket_name,
        contentType='application/octet-stream',
        storageClass=storage_class,
        kmsKeyName=kms_key_name)
    encryption_keywrapper = CryptoKeyWrapperFromKey(encryption_key)
    try:
      return self.json_api.UploadObject(
          cStringIO.StringIO(contents),
          object_metadata, provider='gs',
          encryption_tuple=encryption_keywrapper,
          preconditions=preconditions)
    except PreconditionException:
      if gs_idempotent_generation is None:
        raise
      with SetBotoConfigForTest([('GSUtil', 'decryption_key1',
                                  encryption_key)]):
        return self.json_api.GetObjectMetadata(bucket_name, object_name)
Beispiel #7
0
        def _ChLabelForBucket(blr):
            url = blr.storage_url
            self.logger.info('Setting label configuration on %s...', blr)

            labels_message = None
            # When performing a read-modify-write cycle, include metageneration to
            # avoid race conditions (supported for GS buckets only).
            metageneration = None
            if (self.gsutil_api.GetApiSelector(
                    url.scheme) == ApiSelector.JSON):
                # The JSON API's PATCH semantics allow us to skip read-modify-write,
                # with the exception of one edge case - attempting to delete a
                # nonexistent label returns an error iff no labels previously existed
                corrected_changes = self.label_changes
                if self.num_deletions:
                    (_, bucket_metadata) = self.GetSingleBucketUrlFromArg(
                        url.url_string,
                        bucket_fields=['labels', 'metageneration'])
                    if not bucket_metadata.labels:
                        metageneration = bucket_metadata.metageneration
                        # Remove each change that would try to delete a nonexistent key.
                        corrected_changes = dict(
                            (k, v)
                            for k, v in six.iteritems(self.label_changes) if v)
                labels_message = LabelTranslation.DictToMessage(
                    corrected_changes)
            else:  # ApiSelector.XML
                # Perform a read-modify-write cycle so that we can specify which
                # existing labels need to be deleted.
                (_, bucket_metadata) = self.GetSingleBucketUrlFromArg(
                    url.url_string, bucket_fields=['labels', 'metageneration'])
                metageneration = bucket_metadata.metageneration

                label_json = {}
                if bucket_metadata.labels:
                    label_json = json.loads(
                        LabelTranslation.JsonFromMessage(
                            bucket_metadata.labels))
                # Modify label_json such that all specified labels are added
                # (overwriting old labels if necessary) and all specified deletions
                # are removed from label_json if already present.
                for key, value in six.iteritems(self.label_changes):
                    if not value and key in label_json:
                        del label_json[key]
                    else:
                        label_json[key] = value
                labels_message = LabelTranslation.DictToMessage(label_json)

            preconditions = Preconditions(meta_gen_match=metageneration)
            bucket_metadata = apitools_messages.Bucket(labels=labels_message)
            self.gsutil_api.PatchBucket(url.bucket_name,
                                        bucket_metadata,
                                        preconditions=preconditions,
                                        provider=url.scheme,
                                        fields=['id'])
Beispiel #8
0
  def ObjectUpdateMetadataFunc(self,
                               patch_obj_metadata,
                               log_template,
                               name_expansion_result,
                               thread_state=None):
    """Updates metadata on an object using PatchObjectMetadata.

    Args:
      patch_obj_metadata: Metadata changes that should be applied to the
                          existing object.
      log_template: The log template that should be printed for each object.
      name_expansion_result: NameExpansionResult describing target object.
      thread_state: gsutil Cloud API instance to use for the operation.
    """
    gsutil_api = GetCloudApiInstance(self, thread_state=thread_state)

    exp_src_url = name_expansion_result.expanded_storage_url
    self.logger.info(log_template, exp_src_url)

    cloud_obj_metadata = encoding.JsonToMessage(
        apitools_messages.Object, name_expansion_result.expanded_result)

    preconditions = Preconditions(
        gen_match=self.preconditions.gen_match,
        meta_gen_match=self.preconditions.meta_gen_match)
    if preconditions.gen_match is None:
      preconditions.gen_match = cloud_obj_metadata.generation
    if preconditions.meta_gen_match is None:
      preconditions.meta_gen_match = cloud_obj_metadata.metageneration

    gsutil_api.PatchObjectMetadata(
        exp_src_url.bucket_name,
        exp_src_url.object_name,
        patch_obj_metadata,
        generation=exp_src_url.generation,
        preconditions=preconditions,
        provider=exp_src_url.scheme,
        fields=['id'])
    PutToQueueWithTimeout(
        gsutil_api.status_queue, MetadataMessage(message_time=time.time()))
Beispiel #9
0
def PreconditionsFromHeaders(headers):
  """Creates bucket or object preconditions acccording to the provided headers.

  Args:
    headers: Dict of headers passed via gsutil -h

  Returns:
    gsutil Cloud API Preconditions object fields populated from headers, or None
    if no precondition headers are present.
  """
  return_preconditions = Preconditions()
  try:
    for header, value in headers.items():
      if GOOG_GENERATION_MATCH_REGEX.match(header):
        return_preconditions.gen_match = long(value)
      if GOOG_METAGENERATION_MATCH_REGEX.match(header):
        return_preconditions.meta_gen_match = long(value)
  except ValueError, _:
    raise ArgumentException('Invalid precondition header specified. '
                            'x-goog-if-generation-match and '
                            'x-goog-if-metageneration match must be specified '
                            'with a positive integer value.')
def PreconditionsFromHeaders(headers):
  """Creates bucket or object preconditions acccording to the provided headers.

  Args:
    headers: Dict of headers passed via gsutil -h

  Returns:
    gsutil Cloud API Preconditions object fields populated from headers, or None
    if no precondition headers are present.
  """
  return_preconditions = Preconditions()
  try:
    for header, value in headers.items():
      if GOOG_GENERATION_MATCH_REGEX.match(header):
        return_preconditions.gen_match = long(value)
      if GOOG_METAGENERATION_MATCH_REGEX.match(header):
        return_preconditions.meta_gen_match = long(value)
  except ValueError, _:
    raise ArgumentException('Invalid precondition header specified. '
                            'x-goog-if-generation-match and '
                            'x-goog-if-metageneration match must be specified '
                            'with a positive integer value.')
Beispiel #11
0
        def _SetLabelForBucket(blr):
            url = blr.storage_url
            self.logger.info('Setting label configuration on %s...', blr)

            if url.scheme == 's3':  # Uses only XML.
                self.gsutil_api.XmlPassThroughSetTagging(label_text,
                                                         url,
                                                         provider=url.scheme)
            else:  # Must be a 'gs://' bucket.
                labels_message = None
                # When performing a read-modify-write cycle, include metageneration to
                # avoid race conditions (supported for GS buckets only).
                metageneration = None
                new_label_json = json.loads(label_text)
                if (self.gsutil_api.GetApiSelector(
                        url.scheme) == ApiSelector.JSON):
                    # Perform a read-modify-write so that we can specify which
                    # existing labels need to be deleted.
                    _, bucket_metadata = self.GetSingleBucketUrlFromArg(
                        url.url_string,
                        bucket_fields=['labels', 'metageneration'])
                    metageneration = bucket_metadata.metageneration
                    label_json = {}
                    if bucket_metadata.labels:
                        label_json = json.loads(
                            LabelTranslation.JsonFromMessage(
                                bucket_metadata.labels))
                    # Set all old keys' values to None; this will delete each key that
                    # is not included in the new set of labels.
                    merged_labels = dict(
                        (key, None) for key, _ in six.iteritems(label_json))
                    merged_labels.update(new_label_json)
                    labels_message = LabelTranslation.DictToMessage(
                        merged_labels)
                else:  # ApiSelector.XML
                    # No need to read-modify-write with the XML API.
                    labels_message = LabelTranslation.DictToMessage(
                        new_label_json)

                preconditions = Preconditions(meta_gen_match=metageneration)
                bucket_metadata = apitools_messages.Bucket(
                    labels=labels_message)
                self.gsutil_api.PatchBucket(url.bucket_name,
                                            bucket_metadata,
                                            preconditions=preconditions,
                                            provider=url.scheme,
                                            fields=['id'])
Beispiel #12
0
    def SetMetadataFunc(self, name_expansion_result, thread_state=None):
        """Sets metadata on an object.

    Args:
      name_expansion_result: NameExpansionResult describing target object.
      thread_state: gsutil Cloud API instance to use for the operation.
    """
        gsutil_api = GetCloudApiInstance(self, thread_state=thread_state)

        exp_src_url = name_expansion_result.expanded_storage_url
        self.logger.info('Setting metadata on %s...', exp_src_url)

        fields = ['generation', 'metadata', 'metageneration']
        cloud_obj_metadata = gsutil_api.GetObjectMetadata(
            exp_src_url.bucket_name,
            exp_src_url.object_name,
            generation=exp_src_url.generation,
            provider=exp_src_url.scheme,
            fields=fields)

        preconditions = Preconditions(
            gen_match=cloud_obj_metadata.generation,
            meta_gen_match=cloud_obj_metadata.metageneration)

        # Patch handles the patch semantics for most metadata, but we need to
        # merge the custom metadata field manually.
        patch_obj_metadata = ObjectMetadataFromHeaders(self.metadata_change)

        api = gsutil_api.GetApiSelector(provider=exp_src_url.scheme)
        # For XML we only want to patch through custom metadata that has
        # changed.  For JSON we need to build the complete set.
        if api == ApiSelector.XML:
            pass
        elif api == ApiSelector.JSON:
            CopyObjectMetadata(patch_obj_metadata,
                               cloud_obj_metadata,
                               override=True)
            patch_obj_metadata = cloud_obj_metadata

        gsutil_api.PatchObjectMetadata(exp_src_url.bucket_name,
                                       exp_src_url.object_name,
                                       patch_obj_metadata,
                                       generation=exp_src_url.generation,
                                       preconditions=preconditions,
                                       provider=exp_src_url.scheme)
Beispiel #13
0
  def _RefetchObjectMetadataAndApplyAclChanges(self, url, gsutil_api):
    """Reattempts object ACL changes after a PreconditionException."""
    gcs_object = gsutil_api.GetObjectMetadata(
        url.bucket_name, url.object_name, provider=url.scheme,
        fields=['acl', 'generation', 'metageneration'])
    current_acl = gcs_object.acl

    if self._ApplyAclChangesAndReturnChangeCount(url, current_acl) == 0:
      self.logger.info('No changes to %s', url)
      return

    object_metadata = apitools_messages.Object(acl=current_acl)
    preconditions = Preconditions(gen_match=gcs_object.generation,
                                  meta_gen_match=gcs_object.metageneration)
    gsutil_api.PatchObjectMetadata(
        url.bucket_name, url.object_name, object_metadata,
        preconditions=preconditions, provider=url.scheme,
        generation=gcs_object.generation, fields=['id'])
Beispiel #14
0
  def BucketUpdateFunc(self, url_args, bucket_metadata_update, fields,
                       log_msg_template):
    preconditions = Preconditions(
        meta_gen_match=self.preconditions.meta_gen_match)

    # Iterate over URLs, expanding wildcards and setting the new bucket metadata
    # on each bucket.
    some_matched = False
    for url_str in url_args:
      bucket_iter = self.GetBucketUrlIterFromArg(url_str, bucket_fields=['id'])
      for blr in bucket_iter:
        url = blr.storage_url
        some_matched = True
        self.logger.info(log_msg_template, blr)
        self.gsutil_api.PatchBucket(url.bucket_name,
                                    bucket_metadata_update,
                                    preconditions=preconditions,
                                    provider=url.scheme,
                                    fields=fields)
    if not some_matched:
      raise CommandException(NO_URLS_MATCHED_TARGET % list(url_args))
Beispiel #15
0
  def ApplyAclChanges(self, name_expansion_result, thread_state=None):
    """Applies the changes in self.changes to the provided URL.

    Args:
      name_expansion_result: NameExpansionResult describing the target object.
      thread_state: If present, gsutil Cloud API instance to apply the changes.
    """
    if thread_state:
      gsutil_api = thread_state
    else:
      gsutil_api = self.gsutil_api

    url = name_expansion_result.expanded_storage_url
    if url.IsBucket():
      bucket = gsutil_api.GetBucket(url.bucket_name, provider=url.scheme,
                                    fields=['acl', 'metageneration'])
      current_acl = bucket.acl
    elif url.IsObject():
      gcs_object = encoding.JsonToMessage(apitools_messages.Object,
                                          name_expansion_result.expanded_result)
      current_acl = gcs_object.acl

    if not current_acl:
      self._RaiseForAccessDenied(url)
    if self._ApplyAclChangesAndReturnChangeCount(url, current_acl) == 0:
      self.logger.info('No changes to %s', url)
      return

    try:
      if url.IsBucket():
        preconditions = Preconditions(meta_gen_match=bucket.metageneration)
        bucket_metadata = apitools_messages.Bucket(acl=current_acl)
        gsutil_api.PatchBucket(url.bucket_name, bucket_metadata,
                               preconditions=preconditions,
                               provider=url.scheme, fields=['id'])
      else:  # Object
        preconditions = Preconditions(gen_match=gcs_object.generation,
                                      meta_gen_match=gcs_object.metageneration)
        object_metadata = apitools_messages.Object(acl=current_acl)
        try:
          gsutil_api.PatchObjectMetadata(
              url.bucket_name, url.object_name, object_metadata,
              preconditions=preconditions, provider=url.scheme,
              generation=url.generation, fields=['id'])
        except PreconditionException as e:
          # Special retry case where we want to do an additional step, the read
          # of the read-modify-write cycle, to fetch the correct object
          # metadata before reattempting ACL changes.
          self._RefetchObjectMetadataAndApplyAclChanges(url, gsutil_api)

      self.logger.info('Updated ACL on %s', url)
    except BadRequestException as e:
      # Don't retry on bad requests, e.g. invalid email address.
      raise CommandException('Received bad request from server: %s' % str(e))
    except AccessDeniedException:
      self._RaiseForAccessDenied(url)
    except PreconditionException as e:
      # For objects, retry attempts should have already been handled.
      if url.IsObject():
        raise CommandException(str(e))
      # For buckets, raise PreconditionException and continue to next retry.
      raise e
  def ApplyAclChanges(self, name_expansion_result, thread_state=None):
    """Applies the changes in self.changes to the provided URL.

    Args:
      name_expansion_result: NameExpansionResult describing the target object.
      thread_state: If present, gsutil Cloud API instance to apply the changes.
    """
    if thread_state:
      gsutil_api = thread_state
    else:
      gsutil_api = self.gsutil_api

    url_string = name_expansion_result.GetExpandedUrlStr()
    url = StorageUrlFromString(url_string)

    if url.IsBucket():
      bucket = gsutil_api.GetBucket(url.bucket_name, provider=url.scheme,
                                    fields=['acl', 'metageneration'])
      current_acl = bucket.acl
    elif url.IsObject():
      gcs_object = gsutil_api.GetObjectMetadata(
          url.bucket_name, url.object_name, provider=url.scheme,
          generation=url.generation,
          fields=['acl', 'generation', 'metageneration'])
      current_acl = gcs_object.acl
    if not current_acl:
      self._WarnServiceAccounts()
      self.logger.warning('Failed to set acl for %s. Please ensure you have '
                          'OWNER-role access to this resource.' % url_string)
      return

    modification_count = 0
    for change in self.changes:
      modification_count += change.Execute(url_string, current_acl,
                                           self.logger)
    if modification_count == 0:
      self.logger.info('No changes to {0}'.format(url_string))
      return

    try:
      if url.IsBucket():
        preconditions = Preconditions(meta_gen_match=bucket.metageneration)
        bucket_metadata = apitools_messages.Bucket(acl=current_acl)
        gsutil_api.PatchBucket(url.bucket_name, bucket_metadata,
                               preconditions=preconditions,
                               provider=url.scheme, fields=['id'])
      else:  # Object
        preconditions = Preconditions(meta_gen_match=gcs_object.metageneration)
        # If we're operating on the live version of the object, only apply
        # if the live version hasn't changed or been overwritten.  If we're
        # referring to a version explicitly, then we don't care what the live
        # version is and we will change the ACL on the requested version.
        if not url.generation:
          preconditions.gen_match = gcs_object.generation

        object_metadata = apitools_messages.Object(acl=current_acl)
        gsutil_api.PatchObjectMetadata(
            url.bucket_name, url.object_name, object_metadata,
            preconditions=preconditions, provider=url.scheme,
            generation=url.generation)
    except BadRequestException as e:
      # Don't retry on bad requests, e.g. invalid email address.
      raise CommandException('Received bad request from server: %s' % str(e))

    self.logger.info('Updated ACL on {0}'.format(url_string))