blob: f660b6f0673cf198fa5d1e82624d3a9a9a87a93c [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package bundler
import (
"context"
"io"
"time"
"cloud.google.com/go/storage"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
"google.golang.org/api/iterator"
)
// DataSink is an abstract data sink, providing a mockable interface to
// cloudSink, the GCS-backed implementation below.
type DataSink interface {
ReadFromGCS(ctx context.Context, object string) ([]byte, error)
GetBucketName() string
DoesPathExist(ctx context.Context, prefix string) (bool, error)
}
// CloudSink is a GCS-backed data sink.
type cloudSink struct {
client *storage.Client
bucket *storage.BucketHandle
bucketName string
}
func NewCloudSink(ctx context.Context, bucket string) (*cloudSink, error) {
return newCloudSink(ctx, bucket)
}
func newCloudSink(ctx context.Context, bucket string) (*cloudSink, error) {
client, err := storage.NewClient(ctx)
if err != nil {
return nil, err
}
return &cloudSink{
client: client,
bucket: client.Bucket(bucket),
bucketName: bucket,
}, nil
}
func (s *cloudSink) Close() {
s.client.Close()
}
// readFromGCS reads an object from GCS.
func (s *cloudSink) ReadFromGCS(ctx context.Context, object string) ([]byte, error) {
logger.Debugf(ctx, "reading %s from GCS", object)
ctx, cancel := context.WithTimeout(ctx, time.Second*50)
defer cancel()
rc, err := s.bucket.Object(object).NewReader(ctx)
if err != nil {
return nil, err
}
defer rc.Close()
data, err := io.ReadAll(rc)
if err != nil {
return nil, err
}
return data, nil
}
func (s *cloudSink) GetBucketName() string {
return s.bucketName
}
// doesPathExist checks if a path exists in GCS.
func (s *cloudSink) DoesPathExist(ctx context.Context, prefix string) (bool, error) {
logger.Debugf(ctx, "checking if %s is a valid path in GCS", prefix)
it := s.bucket.Objects(ctx, &storage.Query{
Prefix: prefix,
Delimiter: "/",
})
_, err := it.Next()
// If the first object in the iterator is the end of the iterator, the path
// is invalid and doesn't exist in GCS.
if err == iterator.Done {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}