@@ -25,6 +25,7 @@ import (
2525 "time"
2626
2727 "cloud.google.com/go/storage"
28+ "github.com/apache/beam/sdks/v2/go/pkg/beam/core/util/hooks"
2829 "github.com/apache/beam/sdks/v2/go/pkg/beam/internal/errors"
2930 "github.com/apache/beam/sdks/v2/go/pkg/beam/io/filesystem"
3031 "github.com/apache/beam/sdks/v2/go/pkg/beam/log"
@@ -33,8 +34,30 @@ import (
3334 "google.golang.org/api/iterator"
3435)
3536
37+ const (
38+ projectBillingHook = "beam:go:hook:filesystem:billingproject"
39+ )
40+
41+ var billingProject string = ""
42+
3643func init () {
3744 filesystem .Register ("gs" , New )
45+ hf := func (opts []string ) hooks.Hook {
46+ return hooks.Hook {
47+ Init : func (ctx context.Context ) (context.Context , error ) {
48+ if len (opts ) == 0 {
49+ return ctx , nil
50+ }
51+ if len (opts ) > 1 {
52+ return ctx , fmt .Errorf ("expected 1 option, got %v: %v" , len (opts ), opts )
53+ }
54+
55+ billingProject = opts [0 ]
56+ return ctx , nil
57+ },
58+ }
59+ }
60+ hooks .RegisterHook (projectBillingHook , hf )
3861}
3962
4063type fs struct {
@@ -44,6 +67,7 @@ type fs struct {
4467// New creates a new Google Cloud Storage filesystem using application
4568// default credentials. If it fails, it falls back to unauthenticated
4669// access.
70+ // It will use the environment variable named `BILLING_PROJECT_ID` as requester payer bucket attribute.
4771func New (ctx context.Context ) filesystem.Interface {
4872 client , err := gcsx .NewClient (ctx , storage .ScopeReadWrite )
4973 if err != nil {
@@ -54,7 +78,23 @@ func New(ctx context.Context) filesystem.Interface {
5478 panic (errors .Wrapf (err , "failed to create GCS client" ))
5579 }
5680 }
57- return & fs {client : client }
81+ return & fs {
82+ client : client ,
83+ }
84+ }
85+
86+ func SetRequesterBillingProject (project string ) {
87+ billingProject = project
88+ }
89+
90+ // RequesterBillingProject configure project to be used in google storage operations
91+ // with requester pays actived. More informaiton about requester pays in https://cloud.google.com/storage/docs/requester-pays
92+ func RequesterBillingProject (project string ) error {
93+ if project == "" {
94+ return fmt .Errorf ("project cannot be empty, got %v" , project )
95+ }
96+ // The hook itself is defined in beam/core/runtime/harness/file_system_hooks.go
97+ return hooks .EnableHook (projectBillingHook , project )
5898}
5999
60100func (f * fs ) Close () error {
@@ -73,7 +113,7 @@ func (f *fs) List(ctx context.Context, glob string) ([]string, error) {
73113 // For now, we assume * is the first matching character to make a
74114 // prefix listing and not list the entire bucket.
75115 prefix := fsx .GetPrefix (object )
76- it := f .client .Bucket (bucket ).Objects (ctx , & storage.Query {
116+ it := f .client .Bucket (bucket ).UserProject ( billingProject ). Objects (ctx , & storage.Query {
77117 Prefix : prefix ,
78118 })
79119 for {
@@ -107,7 +147,7 @@ func (f *fs) OpenRead(ctx context.Context, filename string) (io.ReadCloser, erro
107147 return nil , err
108148 }
109149
110- return f .client .Bucket (bucket ).Object (object ).NewReader (ctx )
150+ return f .client .Bucket (bucket ).UserProject ( billingProject ). Object (object ).NewReader (ctx )
111151}
112152
113153// TODO(herohde) 7/12/2017: should we create the bucket in OpenWrite? For now, "no".
@@ -118,7 +158,7 @@ func (f *fs) OpenWrite(ctx context.Context, filename string) (io.WriteCloser, er
118158 return nil , err
119159 }
120160
121- return f .client .Bucket (bucket ).Object (object ).NewWriter (ctx ), nil
161+ return f .client .Bucket (bucket ).UserProject ( billingProject ). Object (object ).NewWriter (ctx ), nil
122162}
123163
124164func (f * fs ) Size (ctx context.Context , filename string ) (int64 , error ) {
@@ -127,7 +167,7 @@ func (f *fs) Size(ctx context.Context, filename string) (int64, error) {
127167 return - 1 , err
128168 }
129169
130- obj := f .client .Bucket (bucket ).Object (object )
170+ obj := f .client .Bucket (bucket ).UserProject ( billingProject ). Object (object )
131171 attrs , err := obj .Attrs (ctx )
132172 if err != nil {
133173 return - 1 , err
@@ -143,7 +183,7 @@ func (f *fs) LastModified(ctx context.Context, filename string) (time.Time, erro
143183 return time.Time {}, err
144184 }
145185
146- obj := f .client .Bucket (bucket ).Object (object )
186+ obj := f .client .Bucket (bucket ).UserProject ( billingProject ). Object (object )
147187 attrs , err := obj .Attrs (ctx )
148188 if err != nil {
149189 return time.Time {}, err
@@ -159,7 +199,7 @@ func (f *fs) Remove(ctx context.Context, filename string) error {
159199 return err
160200 }
161201
162- obj := f .client .Bucket (bucket ).Object (object )
202+ obj := f .client .Bucket (bucket ).UserProject ( billingProject ). Object (object )
163203 return obj .Delete (ctx )
164204}
165205
@@ -169,13 +209,13 @@ func (f *fs) Copy(ctx context.Context, srcpath, dstpath string) error {
169209 if err != nil {
170210 return err
171211 }
172- srcobj := f .client .Bucket (bucket ).Object (src )
212+ srcobj := f .client .Bucket (bucket ).UserProject ( billingProject ). Object (src )
173213
174214 bucket , dst , err := gcsx .ParseObject (dstpath )
175215 if err != nil {
176216 return err
177217 }
178- dstobj := f .client .Bucket (bucket ).Object (dst )
218+ dstobj := f .client .Bucket (bucket ).UserProject ( billingProject ). Object (dst )
179219
180220 cp := dstobj .CopierFrom (srcobj )
181221 _ , err = cp .Run (ctx )
@@ -188,13 +228,13 @@ func (f *fs) Rename(ctx context.Context, srcpath, dstpath string) error {
188228 if err != nil {
189229 return err
190230 }
191- srcobj := f .client .Bucket (bucket ).Object (src )
231+ srcobj := f .client .Bucket (bucket ).UserProject ( billingProject ). Object (src )
192232
193233 bucket , dst , err := gcsx .ParseObject (dstpath )
194234 if err != nil {
195235 return err
196236 }
197- dstobj := f .client .Bucket (bucket ).Object (dst )
237+ dstobj := f .client .Bucket (bucket ).UserProject ( billingProject ). Object (dst )
198238
199239 cp := dstobj .CopierFrom (srcobj )
200240 _ , err = cp .Run (ctx )
0 commit comments