aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2025-03-25 12:56:07 +0100
committerTaras Madan <tarasmadan@google.com>2025-03-25 19:53:31 +0000
commit89d30d7360d4a366f8fdf00d6ac56cced7a45b0b (patch)
treed22b5e8682275911832473914c9640d1769805f4
parent875573af37b09758ab48042f2b8a368097204888 (diff)
pkg/gcs: define Client interface
Some functions are not the struct members now. Some functions deleted. Client mock generated.
-rw-r--r--Makefile1
-rw-r--r--pkg/asset/backend_gcs.go6
-rw-r--r--pkg/covermerger/bq_csv_reader.go2
-rw-r--r--pkg/gcs/gcs.go109
-rw-r--r--pkg/gcs/mocks/Client.go202
-rw-r--r--syz-ci/manager.go2
-rw-r--r--syz-cluster/pkg/blob/gcs.go4
-rw-r--r--tools/syz-covermerger/syz_covermerger.go2
-rw-r--r--vm/gce/gce.go2
9 files changed, 267 insertions, 63 deletions
diff --git a/Makefile b/Makefile
index 3591994c6..bdcaba6c1 100644
--- a/Makefile
+++ b/Makefile
@@ -240,6 +240,7 @@ generate_go: format_cpp
$(GO) generate ./vm/proxyapp
$(GO) generate ./pkg/coveragedb
$(GO) generate ./pkg/covermerger
+ $(GO) generate ./pkg/gcs
generate_rpc:
flatc -o pkg/flatrpc --warnings-as-errors --gen-object-api --filename-suffix "" --go --gen-onefile --go-namespace flatrpc pkg/flatrpc/flatrpc.fbs
diff --git a/pkg/asset/backend_gcs.go b/pkg/asset/backend_gcs.go
index 7791fe777..15fd1f492 100644
--- a/pkg/asset/backend_gcs.go
+++ b/pkg/asset/backend_gcs.go
@@ -16,7 +16,7 @@ import (
)
type cloudStorageBackend struct {
- client *gcs.Client
+ client gcs.Client
bucket string
tracer debugtracer.DebugTracer
}
@@ -69,7 +69,7 @@ func (csb *cloudStorageBackend) upload(req *uploadRequest) (*uploadResponse, err
if exists {
return nil, &FileExistsError{req.savePath}
}
- w, err := csb.client.FileWriterExt(path, req.contentType, req.contentEncoding)
+ w, err := csb.client.FileWriter(path, req.contentType, req.contentEncoding)
csb.tracer.Log("gcs upload: obtained a writer for %s, error %s", path, err)
if err != nil {
return nil, err
@@ -84,7 +84,7 @@ func (csb *cloudStorageBackend) upload(req *uploadRequest) (*uploadResponse, err
}
func (csb *cloudStorageBackend) downloadURL(path string, publicURL bool) (string, error) {
- return csb.client.GetDownloadURL(fmt.Sprintf("%s/%s", csb.bucket, path), publicURL), nil
+ return gcs.GetDownloadURL(fmt.Sprintf("%s/%s", csb.bucket, path), publicURL), nil
}
var allowedDomainsRe = regexp.MustCompile(`^storage\.googleapis\.com|storage\.cloud\.google\.com$`)
diff --git a/pkg/covermerger/bq_csv_reader.go b/pkg/covermerger/bq_csv_reader.go
index 1ea084dc5..f78ac4f18 100644
--- a/pkg/covermerger/bq_csv_reader.go
+++ b/pkg/covermerger/bq_csv_reader.go
@@ -90,7 +90,7 @@ func (r *bqCSVReader) InitNsRecords(ctx context.Context, ns, filePath, commit st
}
func (r *bqCSVReader) initGCSFileReaders(ctx context.Context, bucket, path string) error {
- var gcsClient *gcs.Client
+ var gcsClient gcs.Client
var err error
if gcsClient, err = gcs.NewClient(ctx); err != nil {
return fmt.Errorf("err creating gcs client: %w", err)
diff --git a/pkg/gcs/gcs.go b/pkg/gcs/gcs.go
index 186b9a8eb..9d2e68339 100644
--- a/pkg/gcs/gcs.go
+++ b/pkg/gcs/gcs.go
@@ -7,6 +7,9 @@
// See the following links for details and API reference:
// https://cloud.google.com/go/getting-started/using-cloud-storage
// https://godoc.org/cloud.google.com/go/storage
+
+//go:generate ../../tools/mockery.sh --name Client -r
+
package gcs
import (
@@ -14,7 +17,6 @@ import (
"errors"
"fmt"
"io"
- "os"
"strings"
"time"
@@ -22,14 +24,34 @@ import (
"google.golang.org/api/iterator"
)
-func UploadFile(ctx context.Context, srcFile io.Reader, destURL string, publish bool) error {
+type Client interface {
+ Close() error
+ Read(path string) (*File, error)
+ FileWriter(path string, contentType string, contentEncoding string) (io.WriteCloser, error)
+ DeleteFile(path string) error
+ FileExists(path string) (bool, error)
+ ListObjects(path string) ([]*Object, error)
+
+ publish(path string) error
+}
+
+type UploadOptions struct {
+ Publish bool
+ ContentEncoding string
+ GCSClientMock Client
+}
+
+func UploadFile(ctx context.Context, srcFile io.Reader, destURL string, opts UploadOptions) error {
destURL = strings.TrimPrefix(destURL, "gs://")
- client, err := NewClient(ctx)
- if err != nil {
- return fmt.Errorf("func NewClient: %w", err)
+ var err error
+ gcsClient := opts.GCSClientMock
+ if gcsClient == nil {
+ if gcsClient, err = NewClient(ctx); err != nil {
+ return fmt.Errorf("func NewClient: %w", err)
+ }
}
- defer client.Close()
- gcsWriter, err := client.FileWriter(destURL)
+ defer gcsClient.Close()
+ gcsWriter, err := gcsClient.FileWriter(destURL, "", opts.ContentEncoding)
if err != nil {
return fmt.Errorf("client.FileWriter: %w", err)
}
@@ -40,13 +62,13 @@ func UploadFile(ctx context.Context, srcFile io.Reader, destURL string, publish
if err := gcsWriter.Close(); err != nil {
return fmt.Errorf("gcsWriter.Close: %w", err)
}
- if publish {
- return client.Publish(destURL)
+ if opts.Publish {
+ return gcsClient.publish(destURL)
}
return nil
}
-type Client struct {
+type client struct {
client *storage.Client
ctx context.Context
}
@@ -62,30 +84,30 @@ func (file *File) Reader() (io.ReadCloser, error) {
return file.handle.NewReader(file.ctx)
}
-func NewClient(ctx context.Context) (*Client, error) {
+func NewClient(ctx context.Context) (Client, error) {
storageClient, err := storage.NewClient(ctx)
if err != nil {
return nil, err
}
- client := &Client{
+ c := &client{
client: storageClient,
ctx: ctx,
}
- return client, nil
+ return c, nil
}
-func (client *Client) Close() {
- client.client.Close()
+func (c *client) Close() error {
+ return c.client.Close()
}
-func (client *Client) Read(gcsFile string) (*File, error) {
+func (c *client) Read(gcsFile string) (*File, error) {
bucket, filename, err := split(gcsFile)
if err != nil {
return nil, err
}
- bkt := client.client.Bucket(bucket)
+ bkt := c.client.Bucket(bucket)
f := bkt.Object(filename)
- attrs, err := f.Attrs(client.ctx)
+ attrs, err := f.Attrs(c.ctx)
if err != nil {
return nil, fmt.Errorf("failed to read %v attributes: %w", gcsFile, err)
}
@@ -98,37 +120,20 @@ func (client *Client) Read(gcsFile string) (*File, error) {
})
file := &File{
Updated: attrs.Updated,
- ctx: client.ctx,
+ ctx: c.ctx,
handle: handle,
}
return file, nil
}
-func (client *Client) UploadFile(localFile, gcsFile, contentType, contentEncoding string) error {
- local, err := os.Open(localFile)
- if err != nil {
- return err
- }
- defer local.Close()
-
- w, err := client.FileWriterExt(gcsFile, contentType, contentEncoding)
- if err != nil {
- return err
- }
- defer w.Close()
-
- _, err = io.Copy(w, local)
- return err
-}
-
-func (client *Client) FileWriterExt(gcsFile, contentType, contentEncoding string) (io.WriteCloser, error) {
+func (c *client) FileWriter(gcsFile, contentType, contentEncoding string) (io.WriteCloser, error) {
bucket, filename, err := split(gcsFile)
if err != nil {
return nil, err
}
- bkt := client.client.Bucket(bucket)
+ bkt := c.client.Bucket(bucket)
f := bkt.Object(filename)
- w := f.NewWriter(client.ctx)
+ w := f.NewWriter(c.ctx)
if contentType != "" {
w.ContentType = contentType
}
@@ -138,40 +143,36 @@ func (client *Client) FileWriterExt(gcsFile, contentType, contentEncoding string
return w, nil
}
-func (client *Client) FileWriter(gcsFile string) (io.WriteCloser, error) {
- return client.FileWriterExt(gcsFile, "", "")
-}
-
-// Publish lets any user read gcsFile.
-func (client *Client) Publish(gcsFile string) error {
+// publish lets any user read gcsFile.
+func (c *client) publish(gcsFile string) error {
bucket, filename, err := split(gcsFile)
if err != nil {
return err
}
- obj := client.client.Bucket(bucket).Object(filename)
- return obj.ACL().Set(client.ctx, storage.AllUsers, storage.RoleReader)
+ obj := c.client.Bucket(bucket).Object(filename)
+ return obj.ACL().Set(c.ctx, storage.AllUsers, storage.RoleReader)
}
var ErrFileNotFound = errors.New("the requested files does not exist")
-func (client *Client) DeleteFile(gcsFile string) error {
+func (c *client) DeleteFile(gcsFile string) error {
bucket, filename, err := split(gcsFile)
if err != nil {
return err
}
- err = client.client.Bucket(bucket).Object(filename).Delete(client.ctx)
+ err = c.client.Bucket(bucket).Object(filename).Delete(c.ctx)
if err == storage.ErrObjectNotExist {
return ErrFileNotFound
}
return err
}
-func (client *Client) FileExists(gcsFile string) (bool, error) {
+func (c *client) FileExists(gcsFile string) (bool, error) {
bucket, filename, err := split(gcsFile)
if err != nil {
return false, err
}
- _, err = client.client.Bucket(bucket).Object(filename).Attrs(client.ctx)
+ _, err = c.client.Bucket(bucket).Object(filename).Attrs(c.ctx)
if err == storage.ErrObjectNotExist {
return false, nil
} else if err != nil {
@@ -186,7 +187,7 @@ const (
AuthenticatedPrefix = "https://storage.cloud.google.com/"
)
-func (client *Client) GetDownloadURL(gcsFile string, publicURL bool) string {
+func GetDownloadURL(gcsFile string, publicURL bool) string {
gcsFile = strings.TrimPrefix(gcsFile, "/")
if publicURL {
return PublicPrefix + gcsFile
@@ -200,13 +201,13 @@ type Object struct {
}
// ListObjects expects "bucket/path" or "bucket" as input.
-func (client *Client) ListObjects(bucketObjectPath string) ([]*Object, error) {
+func (c *client) ListObjects(bucketObjectPath string) ([]*Object, error) {
bucket, objectPath, err := split(bucketObjectPath)
if err != nil { // no path specified
bucket = bucketObjectPath
}
query := &storage.Query{Prefix: objectPath}
- it := client.client.Bucket(bucket).Objects(client.ctx, query)
+ it := c.client.Bucket(bucket).Objects(c.ctx, query)
ret := []*Object{}
for {
objAttrs, err := it.Next()
diff --git a/pkg/gcs/mocks/Client.go b/pkg/gcs/mocks/Client.go
new file mode 100644
index 000000000..80e72c73e
--- /dev/null
+++ b/pkg/gcs/mocks/Client.go
@@ -0,0 +1,202 @@
+// Code generated by mockery v2.52.1. DO NOT EDIT.
+
+package mocks
+
+import (
+ io "io"
+
+ gcs "github.com/google/syzkaller/pkg/gcs"
+
+ mock "github.com/stretchr/testify/mock"
+)
+
+// Client is an autogenerated mock type for the Client type
+type Client struct {
+ mock.Mock
+}
+
+// Close provides a mock function with no fields
+func (_m *Client) Close() error {
+ ret := _m.Called()
+
+ if len(ret) == 0 {
+ panic("no return value specified for Close")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func() error); ok {
+ r0 = rf()
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// DeleteFile provides a mock function with given fields: path
+func (_m *Client) DeleteFile(path string) error {
+ ret := _m.Called(path)
+
+ if len(ret) == 0 {
+ panic("no return value specified for DeleteFile")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string) error); ok {
+ r0 = rf(path)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// FileExists provides a mock function with given fields: path
+func (_m *Client) FileExists(path string) (bool, error) {
+ ret := _m.Called(path)
+
+ if len(ret) == 0 {
+ panic("no return value specified for FileExists")
+ }
+
+ var r0 bool
+ var r1 error
+ if rf, ok := ret.Get(0).(func(string) (bool, error)); ok {
+ return rf(path)
+ }
+ if rf, ok := ret.Get(0).(func(string) bool); ok {
+ r0 = rf(path)
+ } else {
+ r0 = ret.Get(0).(bool)
+ }
+
+ if rf, ok := ret.Get(1).(func(string) error); ok {
+ r1 = rf(path)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// FileWriter provides a mock function with given fields: path, contentType, contentEncoding
+func (_m *Client) FileWriter(path string, contentType string, contentEncoding string) (io.WriteCloser, error) {
+ ret := _m.Called(path, contentType, contentEncoding)
+
+ if len(ret) == 0 {
+ panic("no return value specified for FileWriter")
+ }
+
+ var r0 io.WriteCloser
+ var r1 error
+ if rf, ok := ret.Get(0).(func(string, string, string) (io.WriteCloser, error)); ok {
+ return rf(path, contentType, contentEncoding)
+ }
+ if rf, ok := ret.Get(0).(func(string, string, string) io.WriteCloser); ok {
+ r0 = rf(path, contentType, contentEncoding)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(io.WriteCloser)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
+ r1 = rf(path, contentType, contentEncoding)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// ListObjects provides a mock function with given fields: path
+func (_m *Client) ListObjects(path string) ([]*gcs.Object, error) {
+ ret := _m.Called(path)
+
+ if len(ret) == 0 {
+ panic("no return value specified for ListObjects")
+ }
+
+ var r0 []*gcs.Object
+ var r1 error
+ if rf, ok := ret.Get(0).(func(string) ([]*gcs.Object, error)); ok {
+ return rf(path)
+ }
+ if rf, ok := ret.Get(0).(func(string) []*gcs.Object); ok {
+ r0 = rf(path)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*gcs.Object)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(string) error); ok {
+ r1 = rf(path)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// Read provides a mock function with given fields: path
+func (_m *Client) Read(path string) (*gcs.File, error) {
+ ret := _m.Called(path)
+
+ if len(ret) == 0 {
+ panic("no return value specified for Read")
+ }
+
+ var r0 *gcs.File
+ var r1 error
+ if rf, ok := ret.Get(0).(func(string) (*gcs.File, error)); ok {
+ return rf(path)
+ }
+ if rf, ok := ret.Get(0).(func(string) *gcs.File); ok {
+ r0 = rf(path)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*gcs.File)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(string) error); ok {
+ r1 = rf(path)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// publish provides a mock function with given fields: path
+func (_m *Client) publish(path string) error {
+ ret := _m.Called(path)
+
+ if len(ret) == 0 {
+ panic("no return value specified for publish")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string) error); ok {
+ r0 = rf(path)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewClient(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *Client {
+ mock := &Client{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/syz-ci/manager.go b/syz-ci/manager.go
index 776ed2e84..ff646b5ff 100644
--- a/syz-ci/manager.go
+++ b/syz-ci/manager.go
@@ -1032,7 +1032,7 @@ func uploadFile(dstPath, name string, file io.Reader, publish bool) error {
strings.HasPrefix(URLStr, "https://") {
return uploadFileHTTPPut(URLStr, file)
}
- return gcs.UploadFile(context.Background(), file, URLStr, publish)
+ return gcs.UploadFile(context.Background(), file, URLStr, gcs.UploadOptions{Publish: publish})
}
func uploadFileHTTPPut(URL string, file io.Reader) error {
diff --git a/syz-cluster/pkg/blob/gcs.go b/syz-cluster/pkg/blob/gcs.go
index b8df1b174..bd132214d 100644
--- a/syz-cluster/pkg/blob/gcs.go
+++ b/syz-cluster/pkg/blob/gcs.go
@@ -15,7 +15,7 @@ import (
type gcsDriver struct {
bucket string
- client *gcs.Client
+ client gcs.Client
}
func NewGCSClient(ctx context.Context, bucket string) (Storage, error) {
@@ -75,7 +75,7 @@ func (gcs *gcsDriver) objectURI(object string) string {
}
func (gcs *gcsDriver) writeObject(object string, source io.Reader) error {
- w, err := gcs.client.FileWriterExt(fmt.Sprintf("%s/%s", gcs.bucket, object), "", "")
+ w, err := gcs.client.FileWriter(fmt.Sprintf("%s/%s", gcs.bucket, object), "", "")
if err != nil {
return err
}
diff --git a/tools/syz-covermerger/syz_covermerger.go b/tools/syz-covermerger/syz_covermerger.go
index f125309ba..9f055c7d2 100644
--- a/tools/syz-covermerger/syz_covermerger.go
+++ b/tools/syz-covermerger/syz_covermerger.go
@@ -103,7 +103,7 @@ func do() error {
return fmt.Errorf("gcs.NewClient: %w", err)
}
defer gcsClient.Close()
- wc, err = gcsClient.FileWriter(strings.TrimPrefix(url, "gs://"))
+ wc, err = gcsClient.FileWriter(strings.TrimPrefix(url, "gs://"), "", "")
if err != nil {
return fmt.Errorf("gcsClient.FileWriter: %w", err)
}
diff --git a/vm/gce/gce.go b/vm/gce/gce.go
index f9f5ded00..77e9ba113 100644
--- a/vm/gce/gce.go
+++ b/vm/gce/gce.go
@@ -504,7 +504,7 @@ func uploadImageToGCS(localImage, gcsImage string) error {
return fmt.Errorf("failed to stat image file: %w", err)
}
- gcsWriter, err := GCS.FileWriter(gcsImage)
+ gcsWriter, err := GCS.FileWriter(gcsImage, "", "")
if err != nil {
return fmt.Errorf("failed to upload image: %w", err)
}