blob: fcfb4255043180157b2559535f6f1e8584d2136d [file] [log] [blame]
package cos
import (
"context"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"sync"
"cloud.google.com/go/compute/metadata"
"cloud.google.com/go/storage"
"cos.googlesource.com/cos/tools.git/src/pkg/gcs"
"github.com/golang/glog"
"github.com/pkg/errors"
)
const (
cosToolsGCS = "cos-tools"
cosToolsGCSAsia = "cos-tools-asia"
cosToolsGCSEU = "cos-tools-eu"
kernelInfo = "kernel_info"
kernelSrcArchive = "kernel-src.tar.gz"
kernelHeaders = "kernel-headers.tgz"
toolchainURL = "toolchain_url"
toolchainArchive = "toolchain.tar.xz"
toolchainEnv = "toolchain_env"
crosKernelRepo = "https://chromium.googlesource.com/chromiumos/third_party/kernel"
)
// Map VM zone prefix to specific cos-tools bucket for geo-redundancy.
var cosToolsPrefixMap = map[string]string{
"us": cosToolsGCS,
"northamerica": cosToolsGCS,
"southamerica": cosToolsGCS,
"europe": cosToolsGCSEU,
"asia": cosToolsGCSAsia,
"australia": cosToolsGCSAsia,
}
// ArtifactsDownloader defines the interface to download COS artifacts.
type ArtifactsDownloader interface {
DownloadKernelSrc(ctx context.Context, destDir string) error
DownloadToolchainEnv(ctx context.Context, destDir string) error
DownloadToolchain(ctx context.Context, destDir string) error
DownloadKernelHeaders(dctx context.Context, estDir string) error
DownloadArtifact(ctx context.Context, destDir, artifact string) error
GetArtifact(ctx context.Context, artifact string) ([]byte, error)
ArtifactExists(ctx context.Context, artifact string) (bool, error)
ListArtifacts(ctx context.Context, prefix string) ([]string, error)
}
// GCSDownloader is the struct downloading COS artifacts from GCS bucket.
type GCSDownloader struct {
gcsClient *storage.Client
envReader *EnvReader
gcsDownloadBucket string
gcsDownloadPrefix string
mutex sync.Mutex
}
// NewGCSDownloader creates a GCSDownloader instance.
func NewGCSDownloader(gcsClient *storage.Client, e *EnvReader, bucket, prefix string) *GCSDownloader {
// If bucket is not set, use cos-tools, cos-tools-asia or cos-tools-eu
// according to the zone the VM is running in for geo-redundancy.
// If cannot fetch zone from metadata or get an unknown zone prefix,
// use cos-tools as the default GCS bucket.
if bucket == "" {
zone, err := metadata.Zone()
if err != nil {
glog.Warningf("failed to get zone from metadata, will use 'gs://cos-tools' as artifact bucket, err: %v", err)
bucket = cosToolsGCS
} else {
zonePrefix := strings.Split(zone, "-")[0]
if geoBucket, found := cosToolsPrefixMap[zonePrefix]; found {
bucket = geoBucket
} else {
bucket = cosToolsGCS
}
}
}
// Use {build number}/{board} as the default GCS download prefix.
if prefix == "" {
prefix = path.Join(e.BuildNumber(), e.Board())
}
return &GCSDownloader{gcsClient: gcsClient, envReader: e, gcsDownloadBucket: bucket, gcsDownloadPrefix: prefix}
}
// DownloadKernelSrc downloads COS kernel sources to destination directory.
func (d *GCSDownloader) DownloadKernelSrc(ctx context.Context, destDir string) error {
return d.DownloadArtifact(ctx, destDir, kernelSrcArchive)
}
// DownloadToolchainEnv downloads toolchain compilation environment variables to destination directory.
func (d *GCSDownloader) DownloadToolchainEnv(ctx context.Context, destDir string) error {
return d.DownloadArtifact(ctx, destDir, toolchainEnv)
}
// DownloadToolchain downloads toolchain package to destination directory.
func (d *GCSDownloader) DownloadToolchain(ctx context.Context, destDir string) error {
return d.DownloadArtifact(ctx, destDir, toolchainArchive)
}
// DownloadKernelHeaders downloads COS kernel headers to destination directory.
func (d *GCSDownloader) DownloadKernelHeaders(ctx context.Context, destDir string) error {
return d.DownloadArtifact(ctx, destDir, kernelHeaders)
}
// GetArtifact gets an artifact from GCS buckets and returns its content.
func (d *GCSDownloader) GetArtifact(ctx context.Context, artifactPath string) ([]byte, error) {
tmpDir, err := ioutil.TempDir("", "tmp")
if err != nil {
return nil, errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(tmpDir)
if err = d.DownloadArtifact(ctx, tmpDir, artifactPath); err != nil {
return nil, errors.Wrapf(err, "failed to download artifact %s", artifactPath)
}
content, err := ioutil.ReadFile(filepath.Join(tmpDir, filepath.Base(artifactPath)))
if err != nil {
return nil, errors.Wrapf(err, "failed to read file %s", filepath.Join(tmpDir, artifactPath))
}
return content, nil
}
// DownloadArtifact downloads an artifact from the GCS prefix configured in GCSDownloader.
func (d *GCSDownloader) DownloadArtifact(ctx context.Context, destDir, artifactPath string) error {
gcsPath := path.Join(d.gcsDownloadPrefix, artifactPath)
filename := filepath.Base(gcsPath)
outputPath := filepath.Join(destDir, filename)
gcsClient, err := d.getGCSClient(ctx)
if err != nil {
return err
}
glog.Infof("Start to download %s artifact from bucket: %s, object: %s to %s.", filename, d.gcsDownloadBucket, gcsPath, outputPath)
if err := gcs.DownloadGCSObject(ctx, gcsClient, d.gcsDownloadBucket, gcsPath, outputPath); err != nil {
glog.Errorf("Failed to download %s artifact from bucket: %s, object: %s to %s with error: %v.", filename, d.gcsDownloadBucket, gcsPath, outputPath, err)
return fmt.Errorf("failed to download %s artifact from bucket: %s, object: %s to %s with error: %w", filename, d.gcsDownloadBucket, gcsPath, outputPath, err)
}
glog.Infof("Sucessfully downloaded %s artifact from bucket: %s, object: %s to %s.", filename, d.gcsDownloadBucket, gcsPath, outputPath)
return nil
}
// ArtifactExists check whether the artifactpath exists.
func (d *GCSDownloader) ArtifactExists(ctx context.Context, artifactPath string) (bool, error) {
gcsPath := path.Join(d.gcsDownloadPrefix, artifactPath)
glog.Infof("Start to check whether artifact: %s exists in bucket: %s", gcsPath, d.gcsDownloadBucket)
gcsClient, err := d.getGCSClient(ctx)
if err != nil {
return false, err
}
exists, error := gcs.GCSObjectExists(ctx, gcsClient, d.gcsDownloadBucket, gcsPath)
if error != nil {
glog.Errorf("Error appears when using gcs to check whether artifact: %s exists in bucket: %s - %v", gcsPath, d.gcsDownloadBucket, error)
return false, fmt.Errorf("error appears when using gcs to check whether artifact: %s exists in bucket: %s - %w", gcsPath, d.gcsDownloadBucket, error)
}
return exists, nil
}
// ListArtifacts: this function lists the artifacts with `gcsDownloadPrefix/prefix` prefix.
func (d *GCSDownloader) ListArtifacts(ctx context.Context, prefix string) ([]string, error) {
var objects []string
var err error
gcsPath := path.Join(d.gcsDownloadPrefix, prefix)
gcsClient, err := d.getGCSClient(ctx)
if err != nil {
return nil, err
}
if objects, err = gcs.ListGCSBucket(ctx, gcsClient, d.gcsDownloadBucket, gcsPath); err != nil {
glog.Errorf("Error appears when listing artifacts in bucket: %s with prefix: %s - %v", d.gcsDownloadBucket, gcsPath, err)
return nil, fmt.Errorf("error appears when listing artifacts in bucket: %s with prefix: %s - %w", d.gcsDownloadBucket, gcsPath, err)
}
return objects, nil
}
// DownloadArtifactFromURL will download the artifact from url and save it to the destinationPath.
func (d *GCSDownloader) DownloadArtifactFromURL(ctx context.Context, url string, destinationPath string) error {
gcsClient, err := d.getGCSClient(ctx)
if err != nil {
return err
}
return gcs.DownloadGCSObjectFromURL(ctx, gcsClient, url, destinationPath)
}
func (d *GCSDownloader) getGCSClient(ctx context.Context) (*storage.Client, error) {
glog.Infof("Start to fetch the GCSClient.")
d.mutex.Lock()
defer d.mutex.Unlock()
// Initialize the GCSClient only if it hasn't been created before.
if d.gcsClient == nil {
glog.Infof("GCSClient hasn't been created, start to create a new GCSClient.")
var err error
d.gcsClient, err = storage.NewClient(ctx)
if err != nil {
return nil, fmt.Errorf("can not create the GCSClient in GCSDownloader due to: %w", err)
}
}
return d.gcsClient, nil
}