@@ -29,12 +29,15 @@ import (
2929
3030 "cloud.google.com/go/iam"
3131 "cloud.google.com/go/storage"
32+ control "cloud.google.com/go/storage/control/apiv2"
33+ "cloud.google.com/go/storage/control/apiv2/controlpb"
3234 "cloud.google.com/go/storage/experimental"
3335 foUtils "github.com/googlecloudplatform/gcs-fuse-csi-driver/pkg/util/goclientobjectcommands"
3436 "golang.org/x/oauth2"
3537 "google.golang.org/api/iterator"
3638 "google.golang.org/api/option"
3739 "google.golang.org/grpc/codes"
40+ "google.golang.org/grpc/status"
3841 "k8s.io/apimachinery/pkg/util/wait"
3942 "k8s.io/klog/v2"
4043)
@@ -70,6 +73,8 @@ type ServiceManager interface {
7073
7174type gcsService struct {
7275 storageClient * storage.Client
76+ // The control client is for the GetStorageLayout bucket access check.
77+ controlClient * control.StorageControlClient
7378}
7479
7580type gcsServiceManager struct {}
@@ -97,7 +102,16 @@ func (manager *gcsServiceManager) SetupService(ctx context.Context, ts oauth2.To
97102 return nil , err
98103 }
99104
100- return & gcsService {storageClient : storageClient }, nil
105+ controlClient , err := control .NewStorageControlClient (ctx , option .WithTokenSource (ts ))
106+ if err != nil {
107+ storageClient .Close ()
108+ return nil , err
109+ }
110+
111+ return & gcsService {
112+ storageClient : storageClient ,
113+ controlClient : controlClient ,
114+ }, nil
101115}
102116
103117func (manager * gcsServiceManager ) SetupServiceWithDefaultCredential (ctx context.Context , enableZB bool ) (Service , error ) {
@@ -112,7 +126,18 @@ func (manager *gcsServiceManager) SetupServiceWithDefaultCredential(ctx context.
112126 return nil , err
113127 }
114128
115- return & gcsService {storageClient : storageClient }, nil
129+ var controlClient * control.StorageControlClient
130+ // Without explicit auth options, it automatically uses Application Default Credentials.
131+ controlClient , err = control .NewStorageControlClient (ctx )
132+ if err != nil {
133+ storageClient .Close ()
134+ return nil , err
135+ }
136+
137+ return & gcsService {
138+ storageClient : storageClient ,
139+ controlClient : controlClient ,
140+ }, nil
116141}
117142
118143func (service * gcsService ) CreateBucket (ctx context.Context , obj * ServiceBucket ) (* ServiceBucket , error ) {
@@ -201,14 +226,24 @@ func (service *gcsService) GetBucket(ctx context.Context, obj *ServiceBucket) (*
201226 return nil , fmt .Errorf ("failed to get bucket %q: got empty attrs" , obj .Name )
202227}
203228
229+ // CheckBucketExists checks the bucket access the bucket exists by calling the
230+ // API's GetStorageLayout method which uses storage.objects.list permission.
231+ // If the customer hasn't set up permission to the bucket, this will return a
232+ // 403 error. A 404 error will be returned if the bucket doesn't exist, but
233+ // correct permissions are configured.
204234func (service * gcsService ) CheckBucketExists (ctx context.Context , obj * ServiceBucket ) (bool , error ) {
205- bkt := service .storageClient .Bucket (obj .Name )
206- _ , err := bkt .Objects (ctx , & storage.Query {Prefix : "" }).Next ()
235+ // This call matches the access check GCSFuse does:
236+ // https://github.com/GoogleCloudPlatform/gcsfuse/blob/919dbde3e074ff8f6a2cc7f7b612863542a1fb90/internal/storage/storage_handle.go#L319
237+ req := & controlpb.GetStorageLayoutRequest {
238+ Name : fmt .Sprintf ("projects/_/buckets/%s/storageLayout" , obj .Name ),
239+ Prefix : "" ,
240+ RequestId : "" ,
241+ }
207242
208- if err == nil || errors .Is (err , iterator .Done ) {
243+ _ , err := service .controlClient .GetStorageLayout (ctx , req )
244+ if err == nil {
209245 return true , nil
210246 }
211-
212247 return false , err
213248}
214249
@@ -244,6 +279,7 @@ func (service *gcsService) RemoveIAMPolicy(ctx context.Context, obj *ServiceBuck
244279
245280func (service * gcsService ) Close () {
246281 service .storageClient .Close ()
282+ service .controlClient .Close ()
247283}
248284
249285func cloudBucketToServiceBucket (attrs * storage.BucketAttrs ) (* ServiceBucket , error ) {
@@ -289,21 +325,31 @@ func isCanceledErr(err error) bool {
289325}
290326
291327// ParseErrCode parses error and returns a gRPC code.
328+ // This function is necessary because the gcsService uses two different Google Cloud
329+ // clients: a gRPC-based StorageControlClient and a JSON-based vanilla GCS client.
330+ // These clients return different error types.
331+ //
332+ // The function first checks if the error is a gRPC status error.
333+ // If so, it returns the gRPC code. This is the case for errors from
334+ // the StorageControlClient (e.g., in CheckBucketExists).
335+ // If not, it falls back to checking for legacy error types used by the
336+ // vanilla GCS client (e.g., in GetBucket).
292337func ParseErrCode (err error ) codes.Code {
293- code := codes .Internal
294- if IsNotExistErr (err ) {
295- code = codes .NotFound
338+ if s , ok := status .FromError (err ); ok {
339+ return s .Code ()
296340 }
297341
342+ if IsNotExistErr (err ) {
343+ return codes .NotFound
344+ }
298345 if isPermissionDeniedErr (err ) {
299- code = codes .PermissionDenied
346+ return codes .PermissionDenied
300347 }
301-
302348 if isCanceledErr (err ) {
303- code = codes .Aborted
349+ return codes .Aborted
304350 }
305351
306- return code
352+ return codes . Internal
307353}
308354
309355// UploadGCSObject uploads a local file to a specified GCS bucket and object.
@@ -387,22 +433,32 @@ func isBucketAZonalBucket(ctx context.Context, client *storage.Client, bucketNam
387433}
388434
389435func (manager * gcsServiceManager ) SetupStorageServiceForSidecar (ctx context.Context , ts oauth2.TokenSource ) (Service , error ) {
390- var err error
391- var storageClient * storage.Client
436+ var storageOpts []option.ClientOption
437+ var controlOpts []option.ClientOption
438+
392439 // For workload identity enabled resources we need to create the storage service with default credentials so as to not consume more STS quota.
393440 // The token source thus is only shared for host network enabled workload. If token source is nil then create storage client with default credentials else use the tokenSource.
394441 // This is needed as the storage API checks calls TokenSource.Token() function (defined above) which leads to increased STS quota since we are directly hitting the STS API.
395442 if ts != nil {
396443 client := oauth2 .NewClient (ctx , ts )
397- storageClient , err = storage .NewClient (ctx , option .WithHTTPClient (client ))
398- } else {
399- storageClient , err = storage .NewClient (ctx )
444+ storageOpts = append (storageOpts , option .WithHTTPClient (client ))
445+ controlOpts = append (controlOpts , option .WithTokenSource (ts ))
400446 }
401- // Storage client is expected to be created by either path, return the error if storage client creation fails
402- if err != nil || storageClient == nil {
403- klog . Errorf ( "Errored while creating with tokensource %v, got storage client %v" , err , storageClient )
404- return nil , err
447+
448+ storageClient , err := storage . NewClient ( ctx , storageOpts ... )
449+ if err != nil {
450+ return nil , fmt . Errorf ( "failed to create storage client: %w" , err )
405451 }
406- klog .Infof ("Storage service client created successfully, %v" , storageClient )
407- return & gcsService {storageClient : storageClient }, nil
452+
453+ controlClient , err := control .NewStorageControlClient (ctx , controlOpts ... )
454+ if err != nil {
455+ storageClient .Close ()
456+ return nil , fmt .Errorf ("failed to create control client: %w" , err )
457+ }
458+
459+ klog .Infof ("Storage client and control client created successfully for sidecar." )
460+ return & gcsService {
461+ storageClient : storageClient ,
462+ controlClient : controlClient ,
463+ }, nil
408464}
0 commit comments