diff options
| author | Taras Madan <tarasmadan@google.com> | 2025-03-25 12:56:07 +0100 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2025-03-25 19:53:31 +0000 |
| commit | 89d30d7360d4a366f8fdf00d6ac56cced7a45b0b (patch) | |
| tree | d22b5e8682275911832473914c9640d1769805f4 | |
| parent | 875573af37b09758ab48042f2b8a368097204888 (diff) | |
pkg/gcs: define Client interface
Some functions are not the struct members now.
Some functions deleted.
Client mock generated.
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | pkg/asset/backend_gcs.go | 6 | ||||
| -rw-r--r-- | pkg/covermerger/bq_csv_reader.go | 2 | ||||
| -rw-r--r-- | pkg/gcs/gcs.go | 109 | ||||
| -rw-r--r-- | pkg/gcs/mocks/Client.go | 202 | ||||
| -rw-r--r-- | syz-ci/manager.go | 2 | ||||
| -rw-r--r-- | syz-cluster/pkg/blob/gcs.go | 4 | ||||
| -rw-r--r-- | tools/syz-covermerger/syz_covermerger.go | 2 | ||||
| -rw-r--r-- | vm/gce/gce.go | 2 |
9 files changed, 267 insertions, 63 deletions
@@ -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) } |
