text: # -*- coding: utf-8 -*-
  # Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
  # Use of this source code is governed by a BSD-style license that can be
  # found in the LICENSE file.
  
  """Module with utilities for archiving functionality."""
  
  from __future__ import print_function
  
  import os
  import sys
  
  from chromite.cbuildbot import commands
  from chromite.lib import config_lib
  
  from chromite.lib import cros_logging as logging
  from chromite.lib import gs
  from chromite.lib import osutils
  
  
  assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
  
  
  def GetBaseUploadURI(config, archive_base=None, bot_id=None):
    """Get the base URL where artifacts from this builder are uploaded.
  
    Each build run stores its artifacts in a subdirectory of the base URI.
    We also have LATEST files under the base URI which help point to the
    latest build available for a given builder.
  
    Args:
      config: The build config to examine.
      archive_base: Optional. The root URL under which objects from all
        builders are uploaded. If not specified, we use the default archive
        bucket.
      bot_id: The bot ID to archive files under.
  
    Returns:
      Google Storage URI (i.e. 'gs://...') under which all archived files
        should be uploaded.  In other words, a path like a directory, even
        through GS has no real directories.
    """
    if not bot_id:
      bot_id = config.name
  
    if archive_base:
      gs_base = archive_base
    elif config.gs_path == config_lib.GS_PATH_DEFAULT:
      gs_base = config_lib.GetSiteParams().ARCHIVE_URL
    else:
      gs_base = config.gs_path
  
    return os.path.join(gs_base, bot_id)
  
  
  def GetUploadACL(config):
    """Get the ACL we should use to upload artifacts for a given config."""
    if config.internal:
      # Use the bucket default ACL.
      return None
  
    return 'public-read'
  
  
  class Archive(object):
    """Class to represent the archive for one builder run.
  
    An Archive object is a read-only object with attributes and methods useful
    for archive purposes.  Most of the attributes are supported as properties
    because they depend on the ChromeOS version and if they are calculated too
    soon (i.e. before the sync stage) they will raise an exception.
  
    Attributes:
      archive_path: The full local path where output from this builder is stored.
      download_url: The URL where we can download directory artifacts.
      download_url_file: The URL where we can download file artifacts.
      upload_url: The Google Storage location where we should upload artifacts.
      version: The ChromeOS version for this archive.
    """
    # TODO(davidriley): The use of a special download url for directories and
    # files is a workaround for b/27653354. If that is ultimately fixed, revisit
    # this workaround.
  
    _BUILDBOT_ARCHIVE = 'buildbot_archive'
    _TRYBOT_ARCHIVE = 'trybot_archive'
  
    def __init__(self, bot_id, version_getter, options, config):
      """Initialize.
  
      Args:
        bot_id: The bot id associated with this archive.
        version_getter: Functor that should return the ChromeOS version for
          this run when called, if the version is known.  Typically, this
          is BuilderRun.GetVersion.
        options: The command options object for this run.
        config: The build config for this run.
      """
      self._options = options
      self._config = config
      self._version_getter = version_getter
      self._version = None
  
      self.bot_id = bot_id
  
    @property
    def version(self):
      if self._version is None:
        self._version = self._version_getter()
  
      return self._version
  
    @property
    def archive_path(self):
      return os.path.join(self.GetLocalArchiveRoot(), self.bot_id, self.version)
  
    @property
    def upload_url(self):
      base_upload_url = GetBaseUploadURI(
          self._config,
          archive_base=self._options.archive_base,
          bot_id=self.bot_id)
      return '%s/%s' % (base_upload_url, self.version)
  
    @property
    def upload_acl(self):
      """Get the ACL we should use to upload artifacts for a given config."""
      return GetUploadACL(self._config)
  
    @property
    def download_url(self):
      if self._options.buildbot or self._options.remote_trybot:
        # Translate the gs:// URI to the URL for downloading the same files.
        # TODO(akeshet): The use of a special download url is a workaround for
        # b/27653354. If that is ultimately fixed, revisit this workaround.
        # This download link works for directories.
        return self.upload_url.replace(
            'gs://', gs.PRIVATE_BASE_HTTPS_DOWNLOAD_URL)
      else:
        return self.archive_path
  
    @property
    def download_url_file(self):
      if self._options.buildbot or self._options.remote_trybot:
        # Translate the gs:// URI to the URL for downloading the same files.
        # TODO(akeshet): The use of a special download url is a workaround for
        # b/27653354. If that is ultimately fixed, revisit this workaround.
        # This download link works for files.
        return self.upload_url.replace('gs://', gs.PRIVATE_BASE_HTTPS_URL)
      else:
        return self.archive_path
  
    def GetLocalArchiveRoot(self, trybot=None):
      """Return the location on disk where archive images are kept."""
      buildroot = os.path.abspath(self._options.buildroot)
  
      if trybot is None:
        trybot = not self._options.buildbot or self._options.debug
  
      archive_base = self._TRYBOT_ARCHIVE if trybot else self._BUILDBOT_ARCHIVE
      return os.path.join(buildroot, archive_base)
  
    def SetupArchivePath(self):
      """Create a fresh directory for archiving a build."""
      logging.info('Preparing local archive directory at "%s".',
                   self.archive_path)
      if self._options.buildbot:
        # Buildbot: Clear out any leftover build artifacts, if present, for
        # this particular run.  The Clean stage is responsible for trimming
        # back the number of archive paths to the last X runs.
        osutils.RmDir(self.archive_path, ignore_missing=True)
      else:
        # Clear the list of uploaded file if it exists.  In practice, the Clean
        # stage deletes everything in the archive root, so this may not be
        # doing anything at all.
        osutils.SafeUnlink(os.path.join(self.archive_path,
                                        commands.UPLOADED_LIST_FILENAME))
  
      osutils.SafeMakedirs(self.archive_path)
  
    def UpdateLatestMarkers(self, manifest_branch, debug, upload_urls=None):
      """Update the LATEST markers in GS archive area.
  
      Args:
        manifest_branch: The name of the branch in the manifest for this run.
        debug: Boolean debug value for this run.
        upload_urls: Google storage urls to upload the Latest Markers to.
      """
      if not upload_urls:
        upload_urls = [self.upload_url]
      # self.version will be one of these forms, shown through examples:
      # R35-1234.5.6 or R35-1234.5.6-b123.  In either case, we want "1234.5.6".
      version_marker = self.version.split('-')[1]
  
      filenames = ('LATEST-%s' % manifest_branch,
                   'LATEST-%s' % version_marker)
      base_archive_path = os.path.dirname(self.archive_path)
      base_upload_urls = [os.path.dirname(url) for url in upload_urls]
      for base_upload_url in base_upload_urls:
        for filename in filenames:
          latest_path = os.path.join(base_archive_path, filename)
          osutils.WriteFile(latest_path, self.version, mode='w')
          commands.UploadArchivedFile(
              base_archive_path, [base_upload_url], filename,
              debug, acl=self.upload_acl)
  