1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package storage
16
17 import (
18 "context"
19 "fmt"
20 "testing"
21 "time"
22
23 "cloud.google.com/go/internal/testutil"
24 "cloud.google.com/go/storage/internal/apiv2/storagepb"
25 "github.com/google/go-cmp/cmp"
26 gax "github.com/googleapis/gax-go/v2"
27 "golang.org/x/oauth2/google"
28 "google.golang.org/api/googleapi"
29 "google.golang.org/api/option"
30 raw "google.golang.org/api/storage/v1"
31 "google.golang.org/protobuf/proto"
32 "google.golang.org/protobuf/types/known/durationpb"
33 )
34
35 func TestBucketAttrsToRawBucket(t *testing.T) {
36 t.Parallel()
37 attrs := &BucketAttrs{
38 Name: "name",
39 ACL: []ACLRule{{Entity: "bob@example.com", Role: RoleOwner, Domain: "d", Email: "e"}},
40 DefaultObjectACL: []ACLRule{{Entity: AllUsers, Role: RoleReader, EntityID: "eid",
41 ProjectTeam: &ProjectTeam{ProjectNumber: "17", Team: "t"}}},
42 Etag: "Zkyw9ACJZUvcYmlFaKGChzhmtnE/dt1zHSfweiWpwzdGsqXwuJZqiD0",
43 Location: "loc",
44 StorageClass: "class",
45 RetentionPolicy: &RetentionPolicy{
46 RetentionPeriod: 3 * time.Second,
47 },
48 BucketPolicyOnly: BucketPolicyOnly{Enabled: true},
49 UniformBucketLevelAccess: UniformBucketLevelAccess{Enabled: true},
50 PublicAccessPrevention: PublicAccessPreventionEnforced,
51 VersioningEnabled: false,
52 RPO: RPOAsyncTurbo,
53
54 MetaGeneration: 39,
55 Created: time.Now(),
56 Labels: map[string]string{"label": "value"},
57 CORS: []CORS{
58 {
59 MaxAge: time.Hour,
60 Methods: []string{"GET", "POST"},
61 Origins: []string{"*"},
62 ResponseHeaders: []string{"FOO"},
63 },
64 },
65 Encryption: &BucketEncryption{DefaultKMSKeyName: "key"},
66 Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
67 Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
68 Autoclass: &Autoclass{Enabled: true, TerminalStorageClass: "NEARLINE"},
69 SoftDeletePolicy: &SoftDeletePolicy{RetentionDuration: time.Hour},
70 Lifecycle: Lifecycle{
71 Rules: []LifecycleRule{{
72 Action: LifecycleAction{
73 Type: SetStorageClassAction,
74 StorageClass: "NEARLINE",
75 },
76 Condition: LifecycleCondition{
77 AgeInDays: 10,
78 Liveness: Live,
79 CreatedBefore: time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC),
80 MatchesStorageClasses: []string{"STANDARD"},
81 NumNewerVersions: 3,
82 },
83 }, {
84 Action: LifecycleAction{
85 Type: SetStorageClassAction,
86 StorageClass: "ARCHIVE",
87 },
88 Condition: LifecycleCondition{
89 CustomTimeBefore: time.Date(2020, 1, 2, 3, 0, 0, 0, time.UTC),
90 DaysSinceCustomTime: 100,
91 Liveness: Live,
92 MatchesStorageClasses: []string{"STANDARD"},
93 },
94 }, {
95 Action: LifecycleAction{
96 Type: DeleteAction,
97 },
98 Condition: LifecycleCondition{
99 DaysSinceNoncurrentTime: 30,
100 Liveness: Live,
101 NoncurrentTimeBefore: time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC),
102 MatchesStorageClasses: []string{"NEARLINE"},
103 NumNewerVersions: 10,
104 },
105 }, {
106 Action: LifecycleAction{
107 Type: DeleteAction,
108 },
109 Condition: LifecycleCondition{
110 AgeInDays: 10,
111 MatchesPrefix: []string{"testPrefix"},
112 MatchesSuffix: []string{"testSuffix"},
113 NumNewerVersions: 3,
114 },
115 }, {
116 Action: LifecycleAction{
117 Type: DeleteAction,
118 },
119 Condition: LifecycleCondition{
120 Liveness: Archived,
121 },
122 }, {
123 Action: LifecycleAction{
124 Type: AbortIncompleteMPUAction,
125 },
126 Condition: LifecycleCondition{
127 AgeInDays: 20,
128 },
129 }, {
130 Action: LifecycleAction{
131 Type: DeleteAction,
132 },
133 Condition: LifecycleCondition{
134 AllObjects: true,
135 },
136 }},
137 },
138 }
139 got := attrs.toRawBucket()
140 want := &raw.Bucket{
141 Name: "name",
142 Acl: []*raw.BucketAccessControl{
143 {Entity: "bob@example.com", Role: "OWNER"},
144 },
145 DefaultObjectAcl: []*raw.ObjectAccessControl{
146 {Entity: "allUsers", Role: "READER"},
147 },
148 Location: "loc",
149 StorageClass: "class",
150 RetentionPolicy: &raw.BucketRetentionPolicy{
151 RetentionPeriod: 3,
152 },
153 IamConfiguration: &raw.BucketIamConfiguration{
154 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
155 Enabled: true,
156 },
157 PublicAccessPrevention: "enforced",
158 },
159 Versioning: nil,
160 Rpo: rpoAsyncTurbo,
161 Labels: map[string]string{"label": "value"},
162 Cors: []*raw.BucketCors{
163 {
164 MaxAgeSeconds: 3600,
165 Method: []string{"GET", "POST"},
166 Origin: []string{"*"},
167 ResponseHeader: []string{"FOO"},
168 },
169 },
170 Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key"},
171 Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
172 Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
173 Autoclass: &raw.BucketAutoclass{Enabled: true, TerminalStorageClass: "NEARLINE"},
174 SoftDeletePolicy: &raw.BucketSoftDeletePolicy{RetentionDurationSeconds: 60 * 60},
175 Lifecycle: &raw.BucketLifecycle{
176 Rule: []*raw.BucketLifecycleRule{{
177 Action: &raw.BucketLifecycleRuleAction{
178 Type: SetStorageClassAction,
179 StorageClass: "NEARLINE",
180 },
181 Condition: &raw.BucketLifecycleRuleCondition{
182 Age: googleapi.Int64(10),
183 IsLive: googleapi.Bool(true),
184 CreatedBefore: "2017-01-02",
185 MatchesStorageClass: []string{"STANDARD"},
186 NumNewerVersions: 3,
187 },
188 },
189 {
190 Action: &raw.BucketLifecycleRuleAction{
191 StorageClass: "ARCHIVE",
192 Type: SetStorageClassAction,
193 },
194 Condition: &raw.BucketLifecycleRuleCondition{
195 IsLive: googleapi.Bool(true),
196 CustomTimeBefore: "2020-01-02",
197 DaysSinceCustomTime: 100,
198 MatchesStorageClass: []string{"STANDARD"},
199 },
200 },
201 {
202 Action: &raw.BucketLifecycleRuleAction{
203 Type: DeleteAction,
204 },
205 Condition: &raw.BucketLifecycleRuleCondition{
206 DaysSinceNoncurrentTime: 30,
207 IsLive: googleapi.Bool(true),
208 NoncurrentTimeBefore: "2017-01-02",
209 MatchesStorageClass: []string{"NEARLINE"},
210 NumNewerVersions: 10,
211 },
212 },
213 {
214 Action: &raw.BucketLifecycleRuleAction{
215 Type: DeleteAction,
216 },
217 Condition: &raw.BucketLifecycleRuleCondition{
218 Age: googleapi.Int64(10),
219 MatchesPrefix: []string{"testPrefix"},
220 MatchesSuffix: []string{"testSuffix"},
221 NumNewerVersions: 3,
222 },
223 },
224 {
225 Action: &raw.BucketLifecycleRuleAction{
226 Type: DeleteAction,
227 },
228 Condition: &raw.BucketLifecycleRuleCondition{
229 IsLive: googleapi.Bool(false),
230 },
231 },
232 {
233 Action: &raw.BucketLifecycleRuleAction{
234 Type: AbortIncompleteMPUAction,
235 },
236 Condition: &raw.BucketLifecycleRuleCondition{
237 Age: googleapi.Int64(20),
238 },
239 },
240 {
241 Action: &raw.BucketLifecycleRuleAction{
242 Type: DeleteAction,
243 },
244 Condition: &raw.BucketLifecycleRuleCondition{
245 Age: googleapi.Int64(0),
246 ForceSendFields: []string{"Age"},
247 },
248 },
249 },
250 },
251 }
252 if msg := testutil.Diff(got, want); msg != "" {
253 t.Error(msg)
254 }
255
256 attrs.VersioningEnabled = true
257 attrs.RequesterPays = true
258 got = attrs.toRawBucket()
259 want.Versioning = &raw.BucketVersioning{Enabled: true}
260 want.Billing = &raw.BucketBilling{RequesterPays: true}
261 if msg := testutil.Diff(got, want); msg != "" {
262 t.Error(msg)
263 }
264
265
266
267
268 attrs.BucketPolicyOnly = BucketPolicyOnly{}
269 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: true}
270 got = attrs.toRawBucket()
271 want.IamConfiguration = &raw.BucketIamConfiguration{
272 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
273 Enabled: true,
274 },
275 PublicAccessPrevention: "enforced",
276 }
277 if msg := testutil.Diff(got, want); msg != "" {
278 t.Errorf(msg)
279 }
280
281
282
283 attrs.BucketPolicyOnly = BucketPolicyOnly{Enabled: true}
284 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{}
285 got = attrs.toRawBucket()
286 want.IamConfiguration = &raw.BucketIamConfiguration{
287 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
288 Enabled: true,
289 },
290 PublicAccessPrevention: "enforced",
291 }
292 if msg := testutil.Diff(got, want); msg != "" {
293 t.Errorf(msg)
294 }
295
296
297
298
299 attrs.BucketPolicyOnly = BucketPolicyOnly{Enabled: true}
300 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: true}
301 got = attrs.toRawBucket()
302 want.IamConfiguration = &raw.BucketIamConfiguration{
303 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
304 Enabled: true,
305 },
306 PublicAccessPrevention: "enforced",
307 }
308 if msg := testutil.Diff(got, want); msg != "" {
309 t.Errorf(msg)
310 }
311
312
313
314 attrs.BucketPolicyOnly = BucketPolicyOnly{}
315 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{}
316 got = attrs.toRawBucket()
317 want.IamConfiguration = &raw.BucketIamConfiguration{
318 PublicAccessPrevention: "enforced",
319 }
320 if msg := testutil.Diff(got, want); msg != "" {
321 t.Errorf(msg)
322 }
323
324
325
326 attrs.PublicAccessPrevention = PublicAccessPreventionUnspecified
327 got = attrs.toRawBucket()
328 want.IamConfiguration = &raw.BucketIamConfiguration{
329 PublicAccessPrevention: "inherited",
330 }
331 if msg := testutil.Diff(got, want); msg != "" {
332 t.Errorf(msg)
333 }
334
335
336
337 attrs.PublicAccessPrevention = PublicAccessPreventionInherited
338 got = attrs.toRawBucket()
339 want.IamConfiguration = &raw.BucketIamConfiguration{
340 PublicAccessPrevention: "inherited",
341 }
342 if msg := testutil.Diff(got, want); msg != "" {
343 t.Errorf(msg)
344 }
345
346
347 attrs.RPO = RPODefault
348 got = attrs.toRawBucket()
349 want.Rpo = rpoDefault
350 if msg := testutil.Diff(got, want); msg != "" {
351 t.Errorf(msg)
352 }
353
354
355 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: true}
356 got = attrs.toRawBucket()
357 want.IamConfiguration = &raw.BucketIamConfiguration{
358 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
359 Enabled: true,
360 },
361 PublicAccessPrevention: "inherited",
362 }
363 if msg := testutil.Diff(got, want); msg != "" {
364 t.Errorf(msg)
365 }
366
367
368
369 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: false}
370 attrs.PublicAccessPrevention = PublicAccessPreventionUnknown
371 got = attrs.toRawBucket()
372 want.IamConfiguration = nil
373 if msg := testutil.Diff(got, want); msg != "" {
374 t.Errorf(msg)
375 }
376 }
377
378 func TestBucketAttrsToUpdateToRawBucket(t *testing.T) {
379 t.Parallel()
380 au := &BucketAttrsToUpdate{
381 VersioningEnabled: false,
382 RequesterPays: false,
383 BucketPolicyOnly: &BucketPolicyOnly{Enabled: false},
384 UniformBucketLevelAccess: &UniformBucketLevelAccess{Enabled: false},
385 DefaultEventBasedHold: false,
386 RetentionPolicy: &RetentionPolicy{RetentionPeriod: time.Hour},
387 Encryption: &BucketEncryption{DefaultKMSKeyName: "key2"},
388 Lifecycle: &Lifecycle{
389 Rules: []LifecycleRule{
390 {
391 Action: LifecycleAction{Type: "Delete"},
392 Condition: LifecycleCondition{AgeInDays: 30},
393 },
394 {
395 Action: LifecycleAction{Type: AbortIncompleteMPUAction},
396 Condition: LifecycleCondition{AgeInDays: 13},
397 },
398 },
399 },
400 Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
401 Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
402 StorageClass: "NEARLINE",
403 Autoclass: &Autoclass{Enabled: true, TerminalStorageClass: "ARCHIVE"},
404 SoftDeletePolicy: &SoftDeletePolicy{RetentionDuration: time.Hour},
405 }
406 au.SetLabel("a", "foo")
407 au.DeleteLabel("b")
408 au.SetLabel("c", "")
409 got := au.toRawBucket()
410 want := &raw.Bucket{
411 Versioning: &raw.BucketVersioning{
412 Enabled: false,
413 ForceSendFields: []string{"Enabled"},
414 },
415 Labels: map[string]string{
416 "a": "foo",
417 "c": "",
418 },
419 Billing: &raw.BucketBilling{
420 RequesterPays: false,
421 ForceSendFields: []string{"RequesterPays"},
422 },
423 DefaultEventBasedHold: false,
424 RetentionPolicy: &raw.BucketRetentionPolicy{RetentionPeriod: 3600},
425 IamConfiguration: &raw.BucketIamConfiguration{
426 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
427 Enabled: false,
428 ForceSendFields: []string{"Enabled"},
429 },
430 },
431 Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key2"},
432 NullFields: []string{"Labels.b"},
433 Lifecycle: &raw.BucketLifecycle{
434 Rule: []*raw.BucketLifecycleRule{
435 {
436 Action: &raw.BucketLifecycleRuleAction{Type: "Delete"},
437 Condition: &raw.BucketLifecycleRuleCondition{Age: googleapi.Int64(30)},
438 },
439 {
440 Action: &raw.BucketLifecycleRuleAction{Type: AbortIncompleteMPUAction},
441 Condition: &raw.BucketLifecycleRuleCondition{Age: googleapi.Int64(13)},
442 },
443 },
444 },
445 Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
446 Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
447 StorageClass: "NEARLINE",
448 Autoclass: &raw.BucketAutoclass{Enabled: true, TerminalStorageClass: "ARCHIVE", ForceSendFields: []string{"Enabled"}},
449 SoftDeletePolicy: &raw.BucketSoftDeletePolicy{RetentionDurationSeconds: 3600},
450 ForceSendFields: []string{"DefaultEventBasedHold", "Lifecycle", "Autoclass"},
451 }
452 if msg := testutil.Diff(got, want); msg != "" {
453 t.Error(msg)
454 }
455
456 var au2 BucketAttrsToUpdate
457 au2.DeleteLabel("b")
458 got = au2.toRawBucket()
459 want = &raw.Bucket{
460 Labels: map[string]string{},
461 ForceSendFields: []string{"Labels"},
462 NullFields: []string{"Labels.b"},
463 }
464 if msg := testutil.Diff(got, want); msg != "" {
465 t.Error(msg)
466 }
467
468
469 au3 := &BucketAttrsToUpdate{
470 RetentionPolicy: &RetentionPolicy{},
471 Encryption: &BucketEncryption{},
472 Logging: &BucketLogging{},
473 Website: &BucketWebsite{},
474 SoftDeletePolicy: &SoftDeletePolicy{},
475 }
476 got = au3.toRawBucket()
477 want = &raw.Bucket{
478 NullFields: []string{"RetentionPolicy", "Encryption", "Logging", "Website", "SoftDeletePolicy"},
479 }
480 if msg := testutil.Diff(got, want); msg != "" {
481 t.Error(msg)
482 }
483
484
485
486
487 au4 := &BucketAttrsToUpdate{
488 UniformBucketLevelAccess: &UniformBucketLevelAccess{Enabled: true},
489 }
490 got = au4.toRawBucket()
491 want = &raw.Bucket{
492 IamConfiguration: &raw.BucketIamConfiguration{
493 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
494 Enabled: true,
495 ForceSendFields: []string{"Enabled"},
496 },
497 },
498 }
499 if msg := testutil.Diff(got, want); msg != "" {
500 t.Errorf(msg)
501 }
502
503
504
505 au5 := &BucketAttrsToUpdate{
506 BucketPolicyOnly: &BucketPolicyOnly{Enabled: true},
507 }
508 got = au5.toRawBucket()
509 want = &raw.Bucket{
510 IamConfiguration: &raw.BucketIamConfiguration{
511 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
512 Enabled: true,
513 ForceSendFields: []string{"Enabled"},
514 },
515 },
516 }
517 if msg := testutil.Diff(got, want); msg != "" {
518 t.Errorf(msg)
519 }
520
521
522
523
524 au6 := &BucketAttrsToUpdate{
525 BucketPolicyOnly: &BucketPolicyOnly{Enabled: true},
526 UniformBucketLevelAccess: &UniformBucketLevelAccess{Enabled: true},
527 }
528 got = au6.toRawBucket()
529 want = &raw.Bucket{
530 IamConfiguration: &raw.BucketIamConfiguration{
531 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
532 Enabled: true,
533 ForceSendFields: []string{"Enabled"},
534 },
535 },
536 }
537 if msg := testutil.Diff(got, want); msg != "" {
538 t.Errorf(msg)
539 }
540
541
542
543 au7 := &BucketAttrsToUpdate{
544 BucketPolicyOnly: &BucketPolicyOnly{Enabled: false},
545 UniformBucketLevelAccess: &UniformBucketLevelAccess{Enabled: false},
546 }
547 got = au7.toRawBucket()
548 want = &raw.Bucket{
549 IamConfiguration: &raw.BucketIamConfiguration{
550 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
551 Enabled: false,
552 ForceSendFields: []string{"Enabled"},
553 },
554 },
555 }
556 if msg := testutil.Diff(got, want); msg != "" {
557 t.Errorf(msg)
558 }
559
560
561
562 au8 := &BucketAttrsToUpdate{
563 BucketPolicyOnly: &BucketPolicyOnly{Enabled: true},
564 UniformBucketLevelAccess: &UniformBucketLevelAccess{Enabled: false},
565 }
566 got = au8.toRawBucket()
567 want = &raw.Bucket{
568 IamConfiguration: &raw.BucketIamConfiguration{
569 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
570 Enabled: false,
571 ForceSendFields: []string{"Enabled"},
572 },
573 },
574 }
575 if msg := testutil.Diff(got, want); msg != "" {
576 t.Errorf(msg)
577 }
578
579
580 au9 := &BucketAttrsToUpdate{
581 Lifecycle: &Lifecycle{},
582 }
583 got = au9.toRawBucket()
584 want = &raw.Bucket{
585 Lifecycle: &raw.BucketLifecycle{
586 ForceSendFields: []string{"Rule"},
587 },
588 ForceSendFields: []string{"Lifecycle"},
589 }
590 if msg := testutil.Diff(got, want); msg != "" {
591 t.Errorf(msg)
592 }
593 }
594
595 func TestNewBucket(t *testing.T) {
596 labels := map[string]string{"a": "b"}
597 matchClasses := []string{"STANDARD"}
598 aTime := time.Date(2017, 1, 2, 0, 0, 0, 0, time.UTC)
599 rb := &raw.Bucket{
600 Name: "name",
601 Location: "loc",
602 DefaultEventBasedHold: true,
603 Metageneration: 3,
604 StorageClass: "sc",
605 TimeCreated: "2017-10-23T04:05:06Z",
606 Versioning: &raw.BucketVersioning{Enabled: true},
607 Labels: labels,
608 Billing: &raw.BucketBilling{RequesterPays: true},
609 Etag: "Zkyw9ACJZUvcYmlFaKGChzhmtnE/dt1zHSfweiWpwzdGsqXwuJZqiD0",
610 Lifecycle: &raw.BucketLifecycle{
611 Rule: []*raw.BucketLifecycleRule{{
612 Action: &raw.BucketLifecycleRuleAction{
613 Type: "SetStorageClass",
614 StorageClass: "NEARLINE",
615 },
616 Condition: &raw.BucketLifecycleRuleCondition{
617 Age: googleapi.Int64(10),
618 IsLive: googleapi.Bool(true),
619 CreatedBefore: "2017-01-02",
620 MatchesStorageClass: matchClasses,
621 NumNewerVersions: 3,
622 },
623 }},
624 },
625 RetentionPolicy: &raw.BucketRetentionPolicy{
626 RetentionPeriod: 3,
627 EffectiveTime: aTime.Format(time.RFC3339),
628 },
629 ObjectRetention: &raw.BucketObjectRetention{
630 Mode: "Enabled",
631 },
632 IamConfiguration: &raw.BucketIamConfiguration{
633 BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{
634 Enabled: true,
635 LockedTime: aTime.Format(time.RFC3339),
636 },
637 UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
638 Enabled: true,
639 LockedTime: aTime.Format(time.RFC3339),
640 },
641 },
642 Cors: []*raw.BucketCors{
643 {
644 MaxAgeSeconds: 3600,
645 Method: []string{"GET", "POST"},
646 Origin: []string{"*"},
647 ResponseHeader: []string{"FOO"},
648 },
649 },
650 Acl: []*raw.BucketAccessControl{
651 {Bucket: "name", Role: "READER", Email: "joe@example.com", Entity: "allUsers"},
652 },
653 LocationType: "dual-region",
654 Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key"},
655 Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
656 Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
657 ProjectNumber: 123231313,
658 Autoclass: &raw.BucketAutoclass{
659 Enabled: true,
660 ToggleTime: "2017-10-23T04:05:06Z",
661 TerminalStorageClass: "NEARLINE",
662 TerminalStorageClassUpdateTime: "2017-10-23T04:05:06Z",
663 },
664 SoftDeletePolicy: &raw.BucketSoftDeletePolicy{
665 EffectiveTime: "2017-10-23T04:05:06Z",
666 RetentionDurationSeconds: 3600,
667 },
668 }
669 want := &BucketAttrs{
670 Name: "name",
671 Location: "loc",
672 DefaultEventBasedHold: true,
673 MetaGeneration: 3,
674 StorageClass: "sc",
675 Created: time.Date(2017, 10, 23, 4, 5, 6, 0, time.UTC),
676 VersioningEnabled: true,
677 Labels: labels,
678 Etag: "Zkyw9ACJZUvcYmlFaKGChzhmtnE/dt1zHSfweiWpwzdGsqXwuJZqiD0",
679 RequesterPays: true,
680 Lifecycle: Lifecycle{
681 Rules: []LifecycleRule{
682 {
683 Action: LifecycleAction{
684 Type: SetStorageClassAction,
685 StorageClass: "NEARLINE",
686 },
687 Condition: LifecycleCondition{
688 AgeInDays: 10,
689 Liveness: Live,
690 CreatedBefore: time.Date(2017, 1, 2, 0, 0, 0, 0, time.UTC),
691 MatchesStorageClasses: matchClasses,
692 NumNewerVersions: 3,
693 },
694 },
695 },
696 },
697 RetentionPolicy: &RetentionPolicy{
698 EffectiveTime: aTime,
699 RetentionPeriod: 3 * time.Second,
700 },
701 ObjectRetentionMode: "Enabled",
702 BucketPolicyOnly: BucketPolicyOnly{Enabled: true, LockedTime: aTime},
703 UniformBucketLevelAccess: UniformBucketLevelAccess{Enabled: true, LockedTime: aTime},
704 CORS: []CORS{
705 {
706 MaxAge: time.Hour,
707 Methods: []string{"GET", "POST"},
708 Origins: []string{"*"},
709 ResponseHeaders: []string{"FOO"},
710 },
711 },
712 Encryption: &BucketEncryption{DefaultKMSKeyName: "key"},
713 Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
714 Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
715 ACL: []ACLRule{{Entity: "allUsers", Role: RoleReader, Email: "joe@example.com"}},
716 DefaultObjectACL: nil,
717 LocationType: "dual-region",
718 ProjectNumber: 123231313,
719 Autoclass: &Autoclass{
720 Enabled: true,
721 ToggleTime: time.Date(2017, 10, 23, 4, 5, 6, 0, time.UTC),
722 TerminalStorageClass: "NEARLINE",
723 TerminalStorageClassUpdateTime: time.Date(2017, 10, 23, 4, 5, 6, 0, time.UTC),
724 },
725 SoftDeletePolicy: &SoftDeletePolicy{
726 EffectiveTime: time.Date(2017, 10, 23, 4, 5, 6, 0, time.UTC),
727 RetentionDuration: time.Hour,
728 },
729 }
730 got, err := newBucket(rb)
731 if err != nil {
732 t.Fatal(err)
733 }
734 if diff := cmp.Diff(got, want); diff != "" {
735 t.Errorf("got=-, want=+:\n%s", diff)
736 }
737 }
738
739 func TestNewBucketFromProto(t *testing.T) {
740 autoclassTSC := "NEARLINE"
741 pb := &storagepb.Bucket{
742 Name: "name",
743 Acl: []*storagepb.BucketAccessControl{
744 {Entity: "bob@example.com", Role: "OWNER"},
745 },
746 DefaultObjectAcl: []*storagepb.ObjectAccessControl{
747 {Entity: "allUsers", Role: "READER"},
748 },
749 Location: "loc",
750 LocationType: "region",
751 StorageClass: "class",
752 RetentionPolicy: &storagepb.Bucket_RetentionPolicy{
753 RetentionDuration: durationpb.New(3 * time.Second),
754 EffectiveTime: toProtoTimestamp(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
755 },
756 IamConfig: &storagepb.Bucket_IamConfig{
757 UniformBucketLevelAccess: &storagepb.Bucket_IamConfig_UniformBucketLevelAccess{
758 Enabled: true,
759 LockTime: toProtoTimestamp(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
760 },
761 PublicAccessPrevention: "enforced",
762 },
763 Rpo: rpoAsyncTurbo,
764 Metageneration: int64(39),
765 CreateTime: toProtoTimestamp(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
766 Labels: map[string]string{"label": "value"},
767 Cors: []*storagepb.Bucket_Cors{
768 {
769 MaxAgeSeconds: 3600,
770 Method: []string{"GET", "POST"},
771 Origin: []string{"*"},
772 ResponseHeader: []string{"FOO"},
773 },
774 },
775 Encryption: &storagepb.Bucket_Encryption{DefaultKmsKey: "key"},
776 Logging: &storagepb.Bucket_Logging{LogBucket: "projects/_/buckets/lb", LogObjectPrefix: "p"},
777 Website: &storagepb.Bucket_Website{MainPageSuffix: "mps", NotFoundPage: "404"},
778 Autoclass: &storagepb.Bucket_Autoclass{
779 Enabled: true,
780 ToggleTime: toProtoTimestamp(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
781 TerminalStorageClass: &autoclassTSC,
782 TerminalStorageClassUpdateTime: toProtoTimestamp(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
783 },
784 SoftDeletePolicy: &storagepb.Bucket_SoftDeletePolicy{
785 RetentionDuration: durationpb.New(3 * time.Hour),
786 EffectiveTime: toProtoTimestamp(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
787 },
788 Lifecycle: &storagepb.Bucket_Lifecycle{
789 Rule: []*storagepb.Bucket_Lifecycle_Rule{
790 {
791 Action: &storagepb.Bucket_Lifecycle_Rule_Action{Type: "Delete"},
792 Condition: &storagepb.Bucket_Lifecycle_Rule_Condition{
793 AgeDays: proto.Int32(int32(10)),
794 },
795 },
796 },
797 },
798 }
799 want := &BucketAttrs{
800 Name: "name",
801 ACL: []ACLRule{{Entity: "bob@example.com", Role: RoleOwner}},
802 DefaultObjectACL: []ACLRule{{Entity: AllUsers, Role: RoleReader}},
803 Location: "loc",
804 LocationType: "region",
805 StorageClass: "class",
806 RetentionPolicy: &RetentionPolicy{
807 RetentionPeriod: 3 * time.Second,
808 EffectiveTime: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
809 },
810 BucketPolicyOnly: BucketPolicyOnly{Enabled: true, LockedTime: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)},
811 UniformBucketLevelAccess: UniformBucketLevelAccess{Enabled: true, LockedTime: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)},
812 PublicAccessPrevention: PublicAccessPreventionEnforced,
813 RPO: RPOAsyncTurbo,
814 MetaGeneration: 39,
815 Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
816 Labels: map[string]string{"label": "value"},
817 CORS: []CORS{
818 {
819 MaxAge: time.Hour,
820 Methods: []string{"GET", "POST"},
821 Origins: []string{"*"},
822 ResponseHeaders: []string{"FOO"},
823 },
824 },
825 Encryption: &BucketEncryption{DefaultKMSKeyName: "key"},
826 Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
827 Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
828 Autoclass: &Autoclass{Enabled: true, ToggleTime: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), TerminalStorageClass: "NEARLINE", TerminalStorageClassUpdateTime: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)},
829 SoftDeletePolicy: &SoftDeletePolicy{
830 EffectiveTime: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
831 RetentionDuration: time.Hour * 3,
832 },
833 Lifecycle: Lifecycle{
834 Rules: []LifecycleRule{{
835 Action: LifecycleAction{
836 Type: DeleteAction,
837 },
838 Condition: LifecycleCondition{
839 AgeInDays: 10,
840 },
841 }},
842 },
843 }
844 got := newBucketFromProto(pb)
845 if diff := cmp.Diff(got, want); diff != "" {
846 t.Errorf("got=-, want=+:\n%s", diff)
847 }
848 }
849
850 func TestBucketAttrsToProtoBucket(t *testing.T) {
851 t.Parallel()
852 attrs := &BucketAttrs{
853 Name: "name",
854 ACL: []ACLRule{{Entity: "bob@example.com", Role: RoleOwner, Domain: "d", Email: "e"}},
855 DefaultObjectACL: []ACLRule{{Entity: AllUsers, Role: RoleReader, EntityID: "eid",
856 ProjectTeam: &ProjectTeam{ProjectNumber: "17", Team: "t"}}},
857 Location: "loc",
858 StorageClass: "class",
859 RetentionPolicy: &RetentionPolicy{
860 RetentionPeriod: 3 * time.Second,
861 },
862 BucketPolicyOnly: BucketPolicyOnly{Enabled: true},
863 UniformBucketLevelAccess: UniformBucketLevelAccess{Enabled: true},
864 PublicAccessPrevention: PublicAccessPreventionEnforced,
865 VersioningEnabled: false,
866 RPO: RPOAsyncTurbo,
867 Created: time.Now(),
868 Labels: map[string]string{"label": "value"},
869 CORS: []CORS{
870 {
871 MaxAge: time.Hour,
872 Methods: []string{"GET", "POST"},
873 Origins: []string{"*"},
874 ResponseHeaders: []string{"FOO"},
875 },
876 },
877 Encryption: &BucketEncryption{DefaultKMSKeyName: "key"},
878 Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
879 Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
880 Autoclass: &Autoclass{Enabled: true, TerminalStorageClass: "ARCHIVE"},
881 SoftDeletePolicy: &SoftDeletePolicy{RetentionDuration: time.Hour * 2},
882 Lifecycle: Lifecycle{
883 Rules: []LifecycleRule{{
884 Action: LifecycleAction{
885 Type: DeleteAction,
886 },
887 Condition: LifecycleCondition{
888 AgeInDays: 10,
889 },
890 }},
891 },
892
893 MetaGeneration: 39,
894 Etag: "Zkyw9ACJZUvcYmlFaKGChzhmtnE/dt1zHSfweiWpwzdGsqXwuJZqiD0",
895 }
896 got := attrs.toProtoBucket()
897 autoclassTSC := "ARCHIVE"
898 want := &storagepb.Bucket{
899 Name: "name",
900 Acl: []*storagepb.BucketAccessControl{
901 {Entity: "bob@example.com", Role: "OWNER"},
902 },
903 DefaultObjectAcl: []*storagepb.ObjectAccessControl{
904 {Entity: "allUsers", Role: "READER"},
905 },
906 Location: "loc",
907 StorageClass: "class",
908 RetentionPolicy: &storagepb.Bucket_RetentionPolicy{
909 RetentionDuration: durationpb.New(3 * time.Second),
910 },
911 IamConfig: &storagepb.Bucket_IamConfig{
912 UniformBucketLevelAccess: &storagepb.Bucket_IamConfig_UniformBucketLevelAccess{
913 Enabled: true,
914 },
915 PublicAccessPrevention: "enforced",
916 },
917 Versioning: nil,
918 Rpo: rpoAsyncTurbo,
919 Labels: map[string]string{"label": "value"},
920 Cors: []*storagepb.Bucket_Cors{
921 {
922 MaxAgeSeconds: 3600,
923 Method: []string{"GET", "POST"},
924 Origin: []string{"*"},
925 ResponseHeader: []string{"FOO"},
926 },
927 },
928 Encryption: &storagepb.Bucket_Encryption{DefaultKmsKey: "key"},
929 Logging: &storagepb.Bucket_Logging{LogBucket: "projects/_/buckets/lb", LogObjectPrefix: "p"},
930 Website: &storagepb.Bucket_Website{MainPageSuffix: "mps", NotFoundPage: "404"},
931 Autoclass: &storagepb.Bucket_Autoclass{Enabled: true, TerminalStorageClass: &autoclassTSC},
932 SoftDeletePolicy: &storagepb.Bucket_SoftDeletePolicy{RetentionDuration: durationpb.New(2 * time.Hour)},
933 Lifecycle: &storagepb.Bucket_Lifecycle{
934 Rule: []*storagepb.Bucket_Lifecycle_Rule{
935 {
936 Action: &storagepb.Bucket_Lifecycle_Rule_Action{Type: "Delete"},
937 Condition: &storagepb.Bucket_Lifecycle_Rule_Condition{
938 AgeDays: proto.Int32(int32(10)),
939 NumNewerVersions: proto.Int32(int32(0)),
940 DaysSinceCustomTime: proto.Int32(int32(0)),
941 DaysSinceNoncurrentTime: proto.Int32(int32(0)),
942 },
943 },
944 },
945 },
946 }
947
948 if msg := testutil.Diff(got, want); msg != "" {
949 t.Error(msg)
950 }
951
952 attrs.VersioningEnabled = true
953 attrs.RequesterPays = true
954 got = attrs.toProtoBucket()
955 want.Versioning = &storagepb.Bucket_Versioning{Enabled: true}
956 want.Billing = &storagepb.Bucket_Billing{RequesterPays: true}
957 if msg := testutil.Diff(got, want); msg != "" {
958 t.Error(msg)
959 }
960
961
962
963
964 attrs.BucketPolicyOnly = BucketPolicyOnly{}
965 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: true}
966 got = attrs.toProtoBucket()
967 want.IamConfig = &storagepb.Bucket_IamConfig{
968 UniformBucketLevelAccess: &storagepb.Bucket_IamConfig_UniformBucketLevelAccess{
969 Enabled: true,
970 },
971 PublicAccessPrevention: "enforced",
972 }
973 if msg := testutil.Diff(got, want); msg != "" {
974 t.Errorf(msg)
975 }
976
977
978
979 attrs.BucketPolicyOnly = BucketPolicyOnly{Enabled: true}
980 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{}
981 got = attrs.toProtoBucket()
982 want.IamConfig = &storagepb.Bucket_IamConfig{
983 UniformBucketLevelAccess: &storagepb.Bucket_IamConfig_UniformBucketLevelAccess{
984 Enabled: true,
985 },
986 PublicAccessPrevention: "enforced",
987 }
988 if msg := testutil.Diff(got, want); msg != "" {
989 t.Errorf(msg)
990 }
991
992
993
994
995 attrs.BucketPolicyOnly = BucketPolicyOnly{Enabled: true}
996 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: true}
997 got = attrs.toProtoBucket()
998 want.IamConfig = &storagepb.Bucket_IamConfig{
999 UniformBucketLevelAccess: &storagepb.Bucket_IamConfig_UniformBucketLevelAccess{
1000 Enabled: true,
1001 },
1002 PublicAccessPrevention: "enforced",
1003 }
1004 if msg := testutil.Diff(got, want); msg != "" {
1005 t.Errorf(msg)
1006 }
1007
1008
1009
1010 attrs.BucketPolicyOnly = BucketPolicyOnly{}
1011 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{}
1012 got = attrs.toProtoBucket()
1013 want.IamConfig = &storagepb.Bucket_IamConfig{
1014 PublicAccessPrevention: "enforced",
1015 }
1016 if msg := testutil.Diff(got, want); msg != "" {
1017 t.Errorf(msg)
1018 }
1019
1020
1021
1022 attrs.PublicAccessPrevention = PublicAccessPreventionUnspecified
1023 got = attrs.toProtoBucket()
1024 want.IamConfig = &storagepb.Bucket_IamConfig{
1025 PublicAccessPrevention: "inherited",
1026 }
1027 if msg := testutil.Diff(got, want); msg != "" {
1028 t.Errorf(msg)
1029 }
1030
1031
1032
1033 attrs.PublicAccessPrevention = PublicAccessPreventionInherited
1034 got = attrs.toProtoBucket()
1035 want.IamConfig = &storagepb.Bucket_IamConfig{
1036 PublicAccessPrevention: "inherited",
1037 }
1038 if msg := testutil.Diff(got, want); msg != "" {
1039 t.Errorf(msg)
1040 }
1041
1042
1043 attrs.RPO = RPODefault
1044 got = attrs.toProtoBucket()
1045 want.Rpo = rpoDefault
1046 if msg := testutil.Diff(got, want); msg != "" {
1047 t.Errorf(msg)
1048 }
1049
1050
1051 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: true}
1052 got = attrs.toProtoBucket()
1053 want.IamConfig = &storagepb.Bucket_IamConfig{
1054 UniformBucketLevelAccess: &storagepb.Bucket_IamConfig_UniformBucketLevelAccess{
1055 Enabled: true,
1056 },
1057 PublicAccessPrevention: "inherited",
1058 }
1059 if msg := testutil.Diff(got, want); msg != "" {
1060 t.Errorf(msg)
1061 }
1062
1063
1064
1065 attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: false}
1066 attrs.PublicAccessPrevention = PublicAccessPreventionUnknown
1067 got = attrs.toProtoBucket()
1068 want.IamConfig = nil
1069 if msg := testutil.Diff(got, want); msg != "" {
1070 t.Errorf(msg)
1071 }
1072 }
1073
1074 func TestBucketRetryer(t *testing.T) {
1075 testCases := []struct {
1076 name string
1077 call func(b *BucketHandle) *BucketHandle
1078 want *retryConfig
1079 }{
1080 {
1081 name: "all defaults",
1082 call: func(b *BucketHandle) *BucketHandle {
1083 return b.Retryer()
1084 },
1085 want: &retryConfig{},
1086 },
1087 {
1088 name: "set all options",
1089 call: func(b *BucketHandle) *BucketHandle {
1090 return b.Retryer(
1091 WithBackoff(gax.Backoff{
1092 Initial: 2 * time.Second,
1093 Max: 30 * time.Second,
1094 Multiplier: 3,
1095 }),
1096 WithPolicy(RetryAlways),
1097 WithMaxAttempts(5),
1098 WithErrorFunc(func(err error) bool { return false }))
1099 },
1100 want: &retryConfig{
1101 backoff: &gax.Backoff{
1102 Initial: 2 * time.Second,
1103 Max: 30 * time.Second,
1104 Multiplier: 3,
1105 },
1106 policy: RetryAlways,
1107 maxAttempts: expectedAttempts(5),
1108 shouldRetry: func(err error) bool { return false },
1109 },
1110 },
1111 {
1112 name: "set some backoff options",
1113 call: func(b *BucketHandle) *BucketHandle {
1114 return b.Retryer(
1115 WithBackoff(gax.Backoff{
1116 Multiplier: 3,
1117 }))
1118 },
1119 want: &retryConfig{
1120 backoff: &gax.Backoff{
1121 Multiplier: 3,
1122 }},
1123 },
1124 {
1125 name: "set policy only",
1126 call: func(b *BucketHandle) *BucketHandle {
1127 return b.Retryer(WithPolicy(RetryNever))
1128 },
1129 want: &retryConfig{
1130 policy: RetryNever,
1131 },
1132 },
1133 {
1134 name: "set max retry attempts only",
1135 call: func(b *BucketHandle) *BucketHandle {
1136 return b.Retryer(WithMaxAttempts(5))
1137 },
1138 want: &retryConfig{
1139 maxAttempts: expectedAttempts(5),
1140 },
1141 },
1142 {
1143 name: "set ErrorFunc only",
1144 call: func(b *BucketHandle) *BucketHandle {
1145 return b.Retryer(
1146 WithErrorFunc(func(err error) bool { return false }))
1147 },
1148 want: &retryConfig{
1149 shouldRetry: func(err error) bool { return false },
1150 },
1151 },
1152 }
1153 for _, tc := range testCases {
1154 t.Run(tc.name, func(s *testing.T) {
1155 b := tc.call(&BucketHandle{})
1156 if diff := cmp.Diff(
1157 b.retry,
1158 tc.want,
1159 cmp.AllowUnexported(retryConfig{}, gax.Backoff{}),
1160
1161
1162 cmp.Comparer(func(a, b func(err error) bool) bool {
1163 return (a == nil && b == nil) || (a != nil && b != nil)
1164 }),
1165 ); diff != "" {
1166 s.Fatalf("retry not configured correctly: %v", diff)
1167 }
1168 })
1169 }
1170 }
1171
1172 func TestDetectDefaultGoogleAccessID(t *testing.T) {
1173 testCases := []struct {
1174 name string
1175 serviceAccount string
1176 creds func(string) string
1177 expectSuccess bool
1178 }{
1179 {
1180 name: "impersonated creds",
1181 serviceAccount: "default@my-project.iam.gserviceaccount.com",
1182 creds: func(sa string) string {
1183 return fmt.Sprintf(`{
1184 "delegates": [],
1185 "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken",
1186 "source_credentials": {
1187 "client_id": "id",
1188 "client_secret": "secret",
1189 "refresh_token": "token",
1190 "type": "authorized_user"
1191 },
1192 "type": "impersonated_service_account"
1193 }`, sa)
1194 },
1195 expectSuccess: true,
1196 },
1197 {
1198 name: "gcloud ADC creds",
1199 serviceAccount: "default@my-project.iam.gserviceaccount.com",
1200 creds: func(sa string) string {
1201 return fmt.Sprint(`{
1202 "client_id": "my-id.apps.googleusercontent.com",
1203 "client_secret": "secret",
1204 "quota_project_id": "",
1205 "refresh_token": "token",
1206 "type": "authorized_user"
1207 }`)
1208 },
1209 expectSuccess: false,
1210 },
1211 {
1212 name: "ADC private key",
1213 serviceAccount: "default@my-project.iam.gserviceaccount.com",
1214 creds: func(sa string) string {
1215 return fmt.Sprintf(`{
1216 "type": "service_account",
1217 "project_id": "my-project",
1218 "private_key_id": "my1",
1219 "private_key": "-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----\n",
1220 "client_email": "%s",
1221 "client_id": "01",
1222 "auth_uri": "https://accounts.google.com/o/oauth2/auth",
1223 "token_uri": "https://oauth2.googleapis.com/token",
1224 "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
1225 "client_x509_cert_url": "cert"
1226 }`, sa)
1227 },
1228 expectSuccess: true,
1229 },
1230 {
1231 name: "no creds",
1232 creds: func(_ string) string {
1233 return ""
1234 },
1235 expectSuccess: false,
1236 },
1237 {
1238 name: "malformed creds",
1239 serviceAccount: "default@my-project.iam.gserviceaccount.com",
1240 creds: func(sa string) string {
1241 return fmt.Sprintf(`{
1242 "type": "service_account"
1243 "project_id": "my-project",
1244 "private_key_id": "my1",
1245 "private_key": "-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----\n",
1246 "client_email": "%s",
1247 }`, sa)
1248 },
1249 expectSuccess: false,
1250 },
1251 {
1252 name: "external creds",
1253 serviceAccount: "default@my-project.iam.gserviceaccount.com",
1254 creds: func(sa string) string {
1255 return fmt.Sprintf(`{
1256 "type": "external_account",
1257 "audience": "//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID",
1258 "subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
1259 "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken",
1260 "token_url": "https://sts.googleapis.com/v1/token",
1261 "credential_source": {
1262 "environment_id": "id",
1263 "region_url": "region_url",
1264 "url": "url",
1265 "regional_cred_verification_url": "ver_url",
1266 "imdsv2_session_token_url": "tok_url"
1267 }
1268 }`, sa)
1269 },
1270 expectSuccess: true,
1271 },
1272 }
1273
1274 for _, tc := range testCases {
1275 t.Run(tc.name, func(t *testing.T) {
1276 bucket := BucketHandle{
1277 c: &Client{
1278 creds: &google.Credentials{
1279 JSON: []byte(tc.creds(tc.serviceAccount)),
1280 },
1281 },
1282 name: "my-bucket",
1283 }
1284
1285 id, err := bucket.detectDefaultGoogleAccessID()
1286 if tc.expectSuccess {
1287 if err != nil {
1288 t.Fatal(err)
1289 }
1290 if id != tc.serviceAccount {
1291 t.Errorf("service account not found correctly; got: %s, want: %s", id, tc.serviceAccount)
1292 }
1293 } else if err == nil {
1294 t.Error("expected error but detectDefaultGoogleAccessID did not return one")
1295 }
1296 })
1297 }
1298 }
1299
1300
1301
1302
1303 func TestBucketSignedURL_Endpoint_Emulator_Host(t *testing.T) {
1304 expires, _ := time.Parse(time.RFC3339, "2002-10-02T10:00:00-05:00")
1305 bucketName := "bucket-name"
1306 objectName := "obj-name"
1307
1308 localhost9000 := "localhost:9000"
1309 localhost6000Https := "https://localhost:6000"
1310
1311 tests := []struct {
1312 desc string
1313 emulatorHost string
1314 endpoint *string
1315 now time.Time
1316 opts *SignedURLOptions
1317
1318
1319
1320
1321
1322 want string
1323 }{
1324 {
1325 desc: "SignURLV4 creates link to resources in emulator",
1326 emulatorHost: localhost9000,
1327 now: expires.Add(-24 * time.Hour),
1328 opts: &SignedURLOptions{
1329 GoogleAccessID: "xxx@clientid",
1330 PrivateKey: dummyKey("rsa"),
1331 Method: "POST",
1332 Expires: expires,
1333 Scheme: SigningSchemeV4,
1334 Insecure: true,
1335 },
1336 want: "http://localhost:9000/" + bucketName + "/" + objectName +
1337 "?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
1338 "&X-Goog-Credential=xxx%40clientid%2F20021001%2Fauto%2Fstorage%2Fgoog4_request" +
1339 "&X-Goog-Date=20021001T100000Z&X-Goog-Expires=86400" +
1340 "&X-Goog-Signature=249c53142e57adf594b4f523a8a1f9c15f29b071e9abc0cf6665dbc5f692fc96fac4ab98bbea4c2397384367bc970a2e1771f2c86624475f3273970ecde8ff6df39d647e5c3f3263bf67a743e211c1958a96775edf53ece1f69ed337f0ab7fdc081c6c2b84e57b0922280d27f1da1bff47e77e3822fb1756e4c5cece9d220e6d0824ab9528e97e54f0cb09b352193b0e895344d894de11b3f5f9a2ec7d8fd6d0a4c487afd1896385a3ab9e8c3fcb3862ec0cad6ec10af1b574078eb7c79b558bcd85449a67079a0ee6da97fcbad074f1bf9fdfbdca12945336a8bd0a3b70b4c7708918cb83d10c7c4ff1f8b73275e9d1ba5d3db91069dffdf81eb7badf4e3c80" +
1341 "&X-Goog-SignedHeaders=host",
1342 },
1343 {
1344 desc: "SignURLV4 - endpoint",
1345 endpoint: &localhost9000,
1346 now: expires.Add(-24 * time.Hour),
1347 opts: &SignedURLOptions{
1348 GoogleAccessID: "xxx@clientid",
1349 PrivateKey: dummyKey("rsa"),
1350 Method: "POST",
1351 Expires: expires,
1352 Scheme: SigningSchemeV4,
1353 Insecure: true,
1354 },
1355 want: "http://localhost:9000/" + bucketName + "/" + objectName +
1356 "?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
1357 "&X-Goog-Credential=xxx%40clientid%2F20021001%2Fauto%2Fstorage%2Fgoog4_request" +
1358 "&X-Goog-Date=20021001T100000Z&X-Goog-Expires=86400" +
1359 "&X-Goog-Signature=249c53142e57adf594b4f523a8a1f9c15f29b071e9abc0cf6665dbc5f692fc96fac4ab98bbea4c2397384367bc970a2e1771f2c86624475f3273970ecde8ff6df39d647e5c3f3263bf67a743e211c1958a96775edf53ece1f69ed337f0ab7fdc081c6c2b84e57b0922280d27f1da1bff47e77e3822fb1756e4c5cece9d220e6d0824ab9528e97e54f0cb09b352193b0e895344d894de11b3f5f9a2ec7d8fd6d0a4c487afd1896385a3ab9e8c3fcb3862ec0cad6ec10af1b574078eb7c79b558bcd85449a67079a0ee6da97fcbad074f1bf9fdfbdca12945336a8bd0a3b70b4c7708918cb83d10c7c4ff1f8b73275e9d1ba5d3db91069dffdf81eb7badf4e3c80" +
1360 "&X-Goog-SignedHeaders=host",
1361 },
1362 {
1363 desc: "SignURLV4 - endpoint takes precedence over emulator",
1364 endpoint: &localhost9000,
1365 emulatorHost: "localhost:8000",
1366 now: expires.Add(-24 * time.Hour),
1367 opts: &SignedURLOptions{
1368 GoogleAccessID: "xxx@clientid",
1369 PrivateKey: dummyKey("rsa"),
1370 Method: "POST",
1371 Expires: expires,
1372 Scheme: SigningSchemeV4,
1373 Insecure: true,
1374 },
1375 want: "http://localhost:9000/" + bucketName + "/" + objectName +
1376 "?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
1377 "&X-Goog-Credential=xxx%40clientid%2F20021001%2Fauto%2Fstorage%2Fgoog4_request" +
1378 "&X-Goog-Date=20021001T100000Z&X-Goog-Expires=86400" +
1379 "&X-Goog-Signature=249c53142e57adf594b4f523a8a1f9c15f29b071e9abc0cf6665dbc5f692fc96fac4ab98bbea4c2397384367bc970a2e1771f2c86624475f3273970ecde8ff6df39d647e5c3f3263bf67a743e211c1958a96775edf53ece1f69ed337f0ab7fdc081c6c2b84e57b0922280d27f1da1bff47e77e3822fb1756e4c5cece9d220e6d0824ab9528e97e54f0cb09b352193b0e895344d894de11b3f5f9a2ec7d8fd6d0a4c487afd1896385a3ab9e8c3fcb3862ec0cad6ec10af1b574078eb7c79b558bcd85449a67079a0ee6da97fcbad074f1bf9fdfbdca12945336a8bd0a3b70b4c7708918cb83d10c7c4ff1f8b73275e9d1ba5d3db91069dffdf81eb7badf4e3c80" +
1380 "&X-Goog-SignedHeaders=host",
1381 },
1382 {
1383 desc: "SigningSchemeV2 - emulator",
1384 emulatorHost: "localhost:8000",
1385 now: expires.Add(-24 * time.Hour),
1386 opts: &SignedURLOptions{
1387 GoogleAccessID: "xxx@clientid",
1388 PrivateKey: dummyKey("rsa"),
1389 Method: "POST",
1390 Expires: expires,
1391 Scheme: SigningSchemeV2,
1392 },
1393 want: "https://localhost:8000/" + bucketName + "/" + objectName +
1394 "?Expires=1033570800" +
1395 "&GoogleAccessId=xxx%40clientid" +
1396 "&Signature=oRi3y2tBTmoDto7FezNx4AjC0RXA6fpJjTBa0hINeVroZ%2ByOeRU8MRwJbKg1IkBbV0IjtlPaGwv5YoUH16UYdipBjCXOS%2B1qgRWyzl8AnzvU%2BfwSXSlCk9zPtHHoBkFT7G4cZQOdDTLRrSG%2FmRJ3K09KEHYg%2Fc6R5Dd92inD1tLE2tiFMyHFs5uQHRMsepY4wrWiIQ4u53tPvk%2Fwiq1%2B9yL6x3QGblhdWwjX0BTVBOxexyKTlwczJW0XlWX8wpcTFfzQnJZuujbhanf2g9MGzSmkv3ylyuQdHMJDYp4Bzq%2FmnkNUg0Vp6iEvh9tyVdRNkwXeg3D8qn%2BFSOxcF%2B9vJw%3D%3D",
1397 },
1398 {
1399 desc: "SigningSchemeV2 - endpoint",
1400 emulatorHost: "localhost:8000",
1401 endpoint: &localhost9000,
1402 now: expires.Add(-24 * time.Hour),
1403 opts: &SignedURLOptions{
1404 GoogleAccessID: "xxx@clientid",
1405 PrivateKey: dummyKey("rsa"),
1406 Method: "POST",
1407 Expires: expires,
1408 Scheme: SigningSchemeV2,
1409 },
1410 want: "https://localhost:9000/" + bucketName + "/" + objectName +
1411 "?Expires=1033570800" +
1412 "&GoogleAccessId=xxx%40clientid" +
1413 "&Signature=oRi3y2tBTmoDto7FezNx4AjC0RXA6fpJjTBa0hINeVroZ%2ByOeRU8MRwJbKg1IkBbV0IjtlPaGwv5YoUH16UYdipBjCXOS%2B1qgRWyzl8AnzvU%2BfwSXSlCk9zPtHHoBkFT7G4cZQOdDTLRrSG%2FmRJ3K09KEHYg%2Fc6R5Dd92inD1tLE2tiFMyHFs5uQHRMsepY4wrWiIQ4u53tPvk%2Fwiq1%2B9yL6x3QGblhdWwjX0BTVBOxexyKTlwczJW0XlWX8wpcTFfzQnJZuujbhanf2g9MGzSmkv3ylyuQdHMJDYp4Bzq%2FmnkNUg0Vp6iEvh9tyVdRNkwXeg3D8qn%2BFSOxcF%2B9vJw%3D%3D",
1414 },
1415 {
1416 desc: "VirtualHostedStyle - emulator",
1417 emulatorHost: "localhost:8000",
1418 now: expires.Add(-24 * time.Hour),
1419 opts: &SignedURLOptions{
1420 GoogleAccessID: "xxx@clientid",
1421 PrivateKey: dummyKey("rsa"),
1422 Method: "POST",
1423 Expires: expires,
1424 Scheme: SigningSchemeV4,
1425 Style: VirtualHostedStyle(),
1426 Insecure: true,
1427 },
1428 want: "http://" + bucketName + ".localhost:8000/" + objectName +
1429 "?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
1430 "&X-Goog-Credential=xxx%40clientid%2F20021001%2Fauto%2Fstorage%2Fgoog4_request" +
1431 "&X-Goog-Date=20021001T100000Z&X-Goog-Expires=86400" +
1432 "&X-Goog-Signature=35e0b9d33901a2518956821175f88c2c4eb3f4461b725af74b37c36d23f8bbe927558ac57b0be40d345f20bca55ba0652d38b7a620f8da68d4f733706ad104da468c3a039459acf35f3022e388760cd49893c998c33fe3ccc8c022d7034ab98bdbdcac4b680bb24ae5ed586a42ee9495a873ffc484e297853a8a3892d0d6385c980cb7e3c5c8bdd4939b4c17105f10fe8b5b9744017bf59431ff176c1550ae1c64ddd6628096eb6895c97c5da4d850aca72c14b7f5018c15b34d4b00ec63ff2ccb688ddbef2d32648e247ffd0137498080f320f293eb811a94fb526227324bbbd01335446388797803e67d802f97b52565deba3d2387ecabf4f3094662236017" +
1433 "&X-Goog-SignedHeaders=host",
1434 },
1435 {
1436 desc: "VirtualHostedStyle - endpoint overrides emulator",
1437 emulatorHost: "localhost:8000",
1438 endpoint: &localhost9000,
1439 now: expires.Add(-24 * time.Hour),
1440 opts: &SignedURLOptions{
1441 GoogleAccessID: "xxx@clientid",
1442 PrivateKey: dummyKey("rsa"),
1443 Method: "POST",
1444 Expires: expires,
1445 Scheme: SigningSchemeV4,
1446 Style: VirtualHostedStyle(),
1447 Insecure: true,
1448 },
1449 want: "http://" + bucketName + ".localhost:9000/" + objectName +
1450 "?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
1451 "&X-Goog-Credential=xxx%40clientid%2F20021001%2Fauto%2Fstorage%2Fgoog4_request" +
1452 "&X-Goog-Date=20021001T100000Z&X-Goog-Expires=86400" +
1453 "&X-Goog-Signature=35e0b9d33901a2518956821175f88c2c4eb3f4461b725af74b37c36d23f8bbe927558ac57b0be40d345f20bca55ba0652d38b7a620f8da68d4f733706ad104da468c3a039459acf35f3022e388760cd49893c998c33fe3ccc8c022d7034ab98bdbdcac4b680bb24ae5ed586a42ee9495a873ffc484e297853a8a3892d0d6385c980cb7e3c5c8bdd4939b4c17105f10fe8b5b9744017bf59431ff176c1550ae1c64ddd6628096eb6895c97c5da4d850aca72c14b7f5018c15b34d4b00ec63ff2ccb688ddbef2d32648e247ffd0137498080f320f293eb811a94fb526227324bbbd01335446388797803e67d802f97b52565deba3d2387ecabf4f3094662236017" +
1454 "&X-Goog-SignedHeaders=host",
1455 },
1456 {
1457 desc: "BucketBoundHostname - emulator",
1458 emulatorHost: "localhost:8000",
1459 now: expires.Add(-24 * time.Hour),
1460 opts: &SignedURLOptions{
1461 GoogleAccessID: "xxx@clientid",
1462 PrivateKey: dummyKey("rsa"),
1463 Method: "POST",
1464 Expires: expires,
1465 Scheme: SigningSchemeV4,
1466 Style: BucketBoundHostname("myhost"),
1467 },
1468 want: "https://" + "myhost/" + objectName +
1469 "?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
1470 "&X-Goog-Credential=xxx%40clientid%2F20021001%2Fauto%2Fstorage%2Fgoog4_request" +
1471 "&X-Goog-Date=20021001T100000Z&X-Goog-Expires=86400" +
1472 "&X-Goog-Signature=15fe19f6c61bcbdbd6473c32f2bec29caa8a5fa3b2ce32cfb5329a71edaa0d4e5ffe6469f32ed4c23ca2fbed3882fdf1ed107c6a98c2c4995dda6036c64bae51e6cb542c353618f483832aa1f3ef85342ddadd69c13ad4c69fd3f573ea5cf325a58056e3d5a37005217662af63b49fef8688de3c5c7a2f7b43651a030edd0813eb7f7713989a4c29a8add65133ce652895fea9de7dbc6248ee11b4d7c6c1e152df87700100e896e544ba8eeea96584078f56e699665140b750e90550b9b79633f4e7c8409efa807be5670d6e987eeee04a4180be9b9e30bb8557597beaf390a3805cc602c87a3e34800f8bc01449c3dd10ac2f2263e55e55b91e445052548d5e" +
1473 "&X-Goog-SignedHeaders=host",
1474 },
1475 {
1476 desc: "BucketBoundHostname - endpoint",
1477 endpoint: &localhost9000,
1478 now: expires.Add(-24 * time.Hour),
1479 opts: &SignedURLOptions{
1480 GoogleAccessID: "xxx@clientid",
1481 PrivateKey: dummyKey("rsa"),
1482 Method: "POST",
1483 Expires: expires,
1484 Scheme: SigningSchemeV4,
1485 Style: BucketBoundHostname("myhost"),
1486 },
1487 want: "https://" + "myhost/" + objectName +
1488 "?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
1489 "&X-Goog-Credential=xxx%40clientid%2F20021001%2Fauto%2Fstorage%2Fgoog4_request" +
1490 "&X-Goog-Date=20021001T100000Z&X-Goog-Expires=86400" +
1491 "&X-Goog-Signature=15fe19f6c61bcbdbd6473c32f2bec29caa8a5fa3b2ce32cfb5329a71edaa0d4e5ffe6469f32ed4c23ca2fbed3882fdf1ed107c6a98c2c4995dda6036c64bae51e6cb542c353618f483832aa1f3ef85342ddadd69c13ad4c69fd3f573ea5cf325a58056e3d5a37005217662af63b49fef8688de3c5c7a2f7b43651a030edd0813eb7f7713989a4c29a8add65133ce652895fea9de7dbc6248ee11b4d7c6c1e152df87700100e896e544ba8eeea96584078f56e699665140b750e90550b9b79633f4e7c8409efa807be5670d6e987eeee04a4180be9b9e30bb8557597beaf390a3805cc602c87a3e34800f8bc01449c3dd10ac2f2263e55e55b91e445052548d5e" +
1492 "&X-Goog-SignedHeaders=host",
1493 },
1494 {
1495 desc: "emulator host specifies scheme",
1496 emulatorHost: "https://localhost:6000",
1497 now: expires.Add(-24 * time.Hour),
1498 opts: &SignedURLOptions{
1499 GoogleAccessID: "xxx@clientid",
1500 PrivateKey: dummyKey("rsa"),
1501 Method: "POST",
1502 Expires: expires,
1503 Scheme: SigningSchemeV4,
1504 Insecure: true,
1505 },
1506 want: "http://localhost:6000/" + bucketName + "/" + objectName +
1507 "?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
1508 "&X-Goog-Credential=xxx%40clientid%2F20021001%2Fauto%2Fstorage%2Fgoog4_request" +
1509 "&X-Goog-Date=20021001T100000Z&X-Goog-Expires=86400" +
1510 "&X-Goog-Signature=249c53142e57adf594b4f523a8a1f9c15f29b071e9abc0cf6665dbc5f692fc96fac4ab98bbea4c2397384367bc970a2e1771f2c86624475f3273970ecde8ff6df39d647e5c3f3263bf67a743e211c1958a96775edf53ece1f69ed337f0ab7fdc081c6c2b84e57b0922280d27f1da1bff47e77e3822fb1756e4c5cece9d220e6d0824ab9528e97e54f0cb09b352193b0e895344d894de11b3f5f9a2ec7d8fd6d0a4c487afd1896385a3ab9e8c3fcb3862ec0cad6ec10af1b574078eb7c79b558bcd85449a67079a0ee6da97fcbad074f1bf9fdfbdca12945336a8bd0a3b70b4c7708918cb83d10c7c4ff1f8b73275e9d1ba5d3db91069dffdf81eb7badf4e3c80" +
1511 "&X-Goog-SignedHeaders=host",
1512 },
1513 {
1514 desc: "endpoint specifies scheme",
1515 endpoint: &localhost6000Https,
1516 now: expires.Add(-24 * time.Hour),
1517 opts: &SignedURLOptions{
1518 GoogleAccessID: "xxx@clientid",
1519 PrivateKey: dummyKey("rsa"),
1520 Method: "POST",
1521 Expires: expires,
1522 Scheme: SigningSchemeV4,
1523 Insecure: true,
1524 },
1525 want: "http://localhost:6000/" + bucketName + "/" + objectName +
1526 "?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
1527 "&X-Goog-Credential=xxx%40clientid%2F20021001%2Fauto%2Fstorage%2Fgoog4_request" +
1528 "&X-Goog-Date=20021001T100000Z&X-Goog-Expires=86400" +
1529 "&X-Goog-Signature=249c53142e57adf594b4f523a8a1f9c15f29b071e9abc0cf6665dbc5f692fc96fac4ab98bbea4c2397384367bc970a2e1771f2c86624475f3273970ecde8ff6df39d647e5c3f3263bf67a743e211c1958a96775edf53ece1f69ed337f0ab7fdc081c6c2b84e57b0922280d27f1da1bff47e77e3822fb1756e4c5cece9d220e6d0824ab9528e97e54f0cb09b352193b0e895344d894de11b3f5f9a2ec7d8fd6d0a4c487afd1896385a3ab9e8c3fcb3862ec0cad6ec10af1b574078eb7c79b558bcd85449a67079a0ee6da97fcbad074f1bf9fdfbdca12945336a8bd0a3b70b4c7708918cb83d10c7c4ff1f8b73275e9d1ba5d3db91069dffdf81eb7badf4e3c80" +
1530 "&X-Goog-SignedHeaders=host",
1531 },
1532 {
1533 desc: "emulator host specifies scheme using SigningSchemeV2",
1534 emulatorHost: "https://localhost:8000",
1535 now: expires.Add(-24 * time.Hour),
1536 opts: &SignedURLOptions{
1537 GoogleAccessID: "xxx@clientid",
1538 PrivateKey: dummyKey("rsa"),
1539 Method: "POST",
1540 Expires: expires,
1541 Scheme: SigningSchemeV2,
1542 },
1543 want: "https://localhost:8000/" + bucketName + "/" + objectName +
1544 "?Expires=1033570800" +
1545 "&GoogleAccessId=xxx%40clientid" +
1546 "&Signature=oRi3y2tBTmoDto7FezNx4AjC0RXA6fpJjTBa0hINeVroZ%2ByOeRU8MRwJbKg1IkBbV0IjtlPaGwv5YoUH16UYdipBjCXOS%2B1qgRWyzl8AnzvU%2BfwSXSlCk9zPtHHoBkFT7G4cZQOdDTLRrSG%2FmRJ3K09KEHYg%2Fc6R5Dd92inD1tLE2tiFMyHFs5uQHRMsepY4wrWiIQ4u53tPvk%2Fwiq1%2B9yL6x3QGblhdWwjX0BTVBOxexyKTlwczJW0XlWX8wpcTFfzQnJZuujbhanf2g9MGzSmkv3ylyuQdHMJDYp4Bzq%2FmnkNUg0Vp6iEvh9tyVdRNkwXeg3D8qn%2BFSOxcF%2B9vJw%3D%3D",
1547 },
1548 {
1549 desc: "endpoint specifies scheme using SigningSchemeV2",
1550 endpoint: &localhost6000Https,
1551 now: expires.Add(-24 * time.Hour),
1552 opts: &SignedURLOptions{
1553 GoogleAccessID: "xxx@clientid",
1554 PrivateKey: dummyKey("rsa"),
1555 Method: "POST",
1556 Expires: expires,
1557 Scheme: SigningSchemeV2,
1558 },
1559 want: "https://localhost:6000/" + bucketName + "/" + objectName +
1560 "?Expires=1033570800" +
1561 "&GoogleAccessId=xxx%40clientid" +
1562 "&Signature=oRi3y2tBTmoDto7FezNx4AjC0RXA6fpJjTBa0hINeVroZ%2ByOeRU8MRwJbKg1IkBbV0IjtlPaGwv5YoUH16UYdipBjCXOS%2B1qgRWyzl8AnzvU%2BfwSXSlCk9zPtHHoBkFT7G4cZQOdDTLRrSG%2FmRJ3K09KEHYg%2Fc6R5Dd92inD1tLE2tiFMyHFs5uQHRMsepY4wrWiIQ4u53tPvk%2Fwiq1%2B9yL6x3QGblhdWwjX0BTVBOxexyKTlwczJW0XlWX8wpcTFfzQnJZuujbhanf2g9MGzSmkv3ylyuQdHMJDYp4Bzq%2FmnkNUg0Vp6iEvh9tyVdRNkwXeg3D8qn%2BFSOxcF%2B9vJw%3D%3D",
1563 },
1564 {
1565 desc: "hostname in opts overrides all else",
1566 endpoint: &localhost9000,
1567 emulatorHost: "https://localhost:8000",
1568 now: expires.Add(-24 * time.Hour),
1569 opts: &SignedURLOptions{
1570 GoogleAccessID: "xxx@clientid",
1571 PrivateKey: dummyKey("rsa"),
1572 Method: "POST",
1573 Expires: expires,
1574 Scheme: SigningSchemeV2,
1575 Hostname: "localhost:6000",
1576 },
1577 want: "https://localhost:6000/" + bucketName + "/" + objectName +
1578 "?Expires=1033570800" +
1579 "&GoogleAccessId=xxx%40clientid" +
1580 "&Signature=oRi3y2tBTmoDto7FezNx4AjC0RXA6fpJjTBa0hINeVroZ%2ByOeRU8MRwJbKg1IkBbV0IjtlPaGwv5YoUH16UYdipBjCXOS%2B1qgRWyzl8AnzvU%2BfwSXSlCk9zPtHHoBkFT7G4cZQOdDTLRrSG%2FmRJ3K09KEHYg%2Fc6R5Dd92inD1tLE2tiFMyHFs5uQHRMsepY4wrWiIQ4u53tPvk%2Fwiq1%2B9yL6x3QGblhdWwjX0BTVBOxexyKTlwczJW0XlWX8wpcTFfzQnJZuujbhanf2g9MGzSmkv3ylyuQdHMJDYp4Bzq%2FmnkNUg0Vp6iEvh9tyVdRNkwXeg3D8qn%2BFSOxcF%2B9vJw%3D%3D",
1581 },
1582 }
1583 oldUTCNow := utcNow
1584 defer func() {
1585 utcNow = oldUTCNow
1586 }()
1587
1588 for _, test := range tests {
1589 t.Run(test.desc, func(s *testing.T) {
1590 utcNow = func() time.Time {
1591 return test.now
1592 }
1593
1594 t.Setenv("STORAGE_EMULATOR_HOST", test.emulatorHost)
1595
1596 var opts []option.ClientOption
1597 if test.endpoint != nil {
1598 opts = append(opts, option.WithEndpoint(*test.endpoint), option.WithoutAuthentication())
1599 }
1600 c, err := NewClient(context.Background(), opts...)
1601 if err != nil {
1602 t.Fatalf("NewClient: %v", err)
1603 }
1604
1605 got, err := c.Bucket(bucketName).SignedURL(objectName, test.opts)
1606 if err != nil {
1607 s.Fatal(err)
1608 }
1609
1610 if got != test.want {
1611 s.Fatalf("bucket.SidnedURL:\n\tgot:\t%v\n\twant:\t%v", got, test.want)
1612 }
1613 })
1614 }
1615 }
1616
View as plain text