You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

462 lines
14 KiB

  1. // Copyright 2017 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package storage
  15. import (
  16. "net/http"
  17. "reflect"
  18. "testing"
  19. "time"
  20. "cloud.google.com/go/internal/testutil"
  21. "github.com/google/go-cmp/cmp"
  22. "google.golang.org/api/googleapi"
  23. raw "google.golang.org/api/storage/v1"
  24. )
  25. func TestBucketAttrsToRawBucket(t *testing.T) {
  26. t.Parallel()
  27. attrs := &BucketAttrs{
  28. Name: "name",
  29. ACL: []ACLRule{{Entity: "bob@example.com", Role: RoleOwner, Domain: "d", Email: "e"}},
  30. DefaultObjectACL: []ACLRule{{Entity: AllUsers, Role: RoleReader, EntityID: "eid",
  31. ProjectTeam: &ProjectTeam{ProjectNumber: "17", Team: "t"}}},
  32. Location: "loc",
  33. StorageClass: "class",
  34. RetentionPolicy: &RetentionPolicy{
  35. RetentionPeriod: 3 * time.Second,
  36. },
  37. BucketPolicyOnly: BucketPolicyOnly{Enabled: true},
  38. VersioningEnabled: false,
  39. // should be ignored:
  40. MetaGeneration: 39,
  41. Created: time.Now(),
  42. Labels: map[string]string{"label": "value"},
  43. CORS: []CORS{
  44. {
  45. MaxAge: time.Hour,
  46. Methods: []string{"GET", "POST"},
  47. Origins: []string{"*"},
  48. ResponseHeaders: []string{"FOO"},
  49. },
  50. },
  51. Encryption: &BucketEncryption{DefaultKMSKeyName: "key"},
  52. Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
  53. Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
  54. Lifecycle: Lifecycle{
  55. Rules: []LifecycleRule{{
  56. Action: LifecycleAction{
  57. Type: SetStorageClassAction,
  58. StorageClass: "NEARLINE",
  59. },
  60. Condition: LifecycleCondition{
  61. AgeInDays: 10,
  62. Liveness: Live,
  63. CreatedBefore: time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC),
  64. MatchesStorageClasses: []string{"MULTI_REGIONAL", "REGIONAL", "STANDARD"},
  65. NumNewerVersions: 3,
  66. },
  67. }, {
  68. Action: LifecycleAction{
  69. Type: DeleteAction,
  70. },
  71. Condition: LifecycleCondition{
  72. AgeInDays: 30,
  73. Liveness: Live,
  74. CreatedBefore: time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC),
  75. MatchesStorageClasses: []string{"NEARLINE"},
  76. NumNewerVersions: 10,
  77. },
  78. }, {
  79. Action: LifecycleAction{
  80. Type: DeleteAction,
  81. },
  82. Condition: LifecycleCondition{
  83. Liveness: Archived,
  84. },
  85. }},
  86. },
  87. }
  88. got := attrs.toRawBucket()
  89. want := &raw.Bucket{
  90. Name: "name",
  91. Acl: []*raw.BucketAccessControl{
  92. {Entity: "bob@example.com", Role: "OWNER"}, // other fields ignored on create/update
  93. },
  94. DefaultObjectAcl: []*raw.ObjectAccessControl{
  95. {Entity: "allUsers", Role: "READER"}, // other fields ignored on create/update
  96. },
  97. Location: "loc",
  98. StorageClass: "class",
  99. RetentionPolicy: &raw.BucketRetentionPolicy{
  100. RetentionPeriod: 3,
  101. },
  102. IamConfiguration: &raw.BucketIamConfiguration{
  103. BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{
  104. Enabled: true,
  105. },
  106. },
  107. Versioning: nil, // ignore VersioningEnabled if false
  108. Labels: map[string]string{"label": "value"},
  109. Cors: []*raw.BucketCors{
  110. {
  111. MaxAgeSeconds: 3600,
  112. Method: []string{"GET", "POST"},
  113. Origin: []string{"*"},
  114. ResponseHeader: []string{"FOO"},
  115. },
  116. },
  117. Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key"},
  118. Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
  119. Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
  120. Lifecycle: &raw.BucketLifecycle{
  121. Rule: []*raw.BucketLifecycleRule{{
  122. Action: &raw.BucketLifecycleRuleAction{
  123. Type: SetStorageClassAction,
  124. StorageClass: "NEARLINE",
  125. },
  126. Condition: &raw.BucketLifecycleRuleCondition{
  127. Age: 10,
  128. IsLive: googleapi.Bool(true),
  129. CreatedBefore: "2017-01-02",
  130. MatchesStorageClass: []string{"MULTI_REGIONAL", "REGIONAL", "STANDARD"},
  131. NumNewerVersions: 3,
  132. },
  133. }, {
  134. Action: &raw.BucketLifecycleRuleAction{
  135. Type: DeleteAction,
  136. },
  137. Condition: &raw.BucketLifecycleRuleCondition{
  138. Age: 30,
  139. IsLive: googleapi.Bool(true),
  140. CreatedBefore: "2017-01-02",
  141. MatchesStorageClass: []string{"NEARLINE"},
  142. NumNewerVersions: 10,
  143. },
  144. }, {
  145. Action: &raw.BucketLifecycleRuleAction{
  146. Type: DeleteAction,
  147. },
  148. Condition: &raw.BucketLifecycleRuleCondition{
  149. IsLive: googleapi.Bool(false),
  150. },
  151. }},
  152. },
  153. }
  154. if msg := testutil.Diff(got, want); msg != "" {
  155. t.Error(msg)
  156. }
  157. attrs.VersioningEnabled = true
  158. attrs.RequesterPays = true
  159. got = attrs.toRawBucket()
  160. want.Versioning = &raw.BucketVersioning{Enabled: true}
  161. want.Billing = &raw.BucketBilling{RequesterPays: true}
  162. if msg := testutil.Diff(got, want); msg != "" {
  163. t.Error(msg)
  164. }
  165. }
  166. func TestBucketAttrsToUpdateToRawBucket(t *testing.T) {
  167. t.Parallel()
  168. au := &BucketAttrsToUpdate{
  169. VersioningEnabled: false,
  170. RequesterPays: false,
  171. BucketPolicyOnly: &BucketPolicyOnly{Enabled: false},
  172. DefaultEventBasedHold: false,
  173. RetentionPolicy: &RetentionPolicy{RetentionPeriod: time.Hour},
  174. Encryption: &BucketEncryption{DefaultKMSKeyName: "key2"},
  175. Lifecycle: &Lifecycle{
  176. Rules: []LifecycleRule{
  177. {
  178. Action: LifecycleAction{Type: "Delete"},
  179. Condition: LifecycleCondition{AgeInDays: 30},
  180. },
  181. },
  182. },
  183. Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
  184. Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
  185. }
  186. au.SetLabel("a", "foo")
  187. au.DeleteLabel("b")
  188. au.SetLabel("c", "")
  189. got := au.toRawBucket()
  190. want := &raw.Bucket{
  191. Versioning: &raw.BucketVersioning{
  192. Enabled: false,
  193. ForceSendFields: []string{"Enabled"},
  194. },
  195. Labels: map[string]string{
  196. "a": "foo",
  197. "c": "",
  198. },
  199. Billing: &raw.BucketBilling{
  200. RequesterPays: false,
  201. ForceSendFields: []string{"RequesterPays"},
  202. },
  203. DefaultEventBasedHold: false,
  204. RetentionPolicy: &raw.BucketRetentionPolicy{RetentionPeriod: 3600},
  205. IamConfiguration: &raw.BucketIamConfiguration{
  206. BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{
  207. Enabled: false,
  208. },
  209. },
  210. Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key2"},
  211. NullFields: []string{"Labels.b"},
  212. Lifecycle: &raw.BucketLifecycle{
  213. Rule: []*raw.BucketLifecycleRule{
  214. {
  215. Action: &raw.BucketLifecycleRuleAction{Type: "Delete"},
  216. Condition: &raw.BucketLifecycleRuleCondition{Age: 30},
  217. },
  218. },
  219. },
  220. Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
  221. Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
  222. ForceSendFields: []string{"DefaultEventBasedHold"},
  223. }
  224. if msg := testutil.Diff(got, want); msg != "" {
  225. t.Error(msg)
  226. }
  227. var au2 BucketAttrsToUpdate
  228. au2.DeleteLabel("b")
  229. got = au2.toRawBucket()
  230. want = &raw.Bucket{
  231. Labels: map[string]string{},
  232. ForceSendFields: []string{"Labels"},
  233. NullFields: []string{"Labels.b"},
  234. }
  235. if msg := testutil.Diff(got, want); msg != "" {
  236. t.Error(msg)
  237. }
  238. // Test nulls.
  239. au3 := &BucketAttrsToUpdate{
  240. RetentionPolicy: &RetentionPolicy{},
  241. Encryption: &BucketEncryption{},
  242. Logging: &BucketLogging{},
  243. Website: &BucketWebsite{},
  244. }
  245. got = au3.toRawBucket()
  246. want = &raw.Bucket{
  247. NullFields: []string{"RetentionPolicy", "Encryption", "Logging", "Website"},
  248. }
  249. if msg := testutil.Diff(got, want); msg != "" {
  250. t.Error(msg)
  251. }
  252. }
  253. func TestCallBuilders(t *testing.T) {
  254. rc, err := raw.New(&http.Client{})
  255. if err != nil {
  256. t.Fatal(err)
  257. }
  258. c := &Client{raw: rc}
  259. const metagen = 17
  260. b := c.Bucket("name")
  261. bm := b.If(BucketConditions{MetagenerationMatch: metagen}).UserProject("p")
  262. equal := func(x, y interface{}) bool {
  263. return testutil.Equal(x, y,
  264. cmp.AllowUnexported(
  265. raw.BucketsGetCall{},
  266. raw.BucketsDeleteCall{},
  267. raw.BucketsPatchCall{},
  268. ),
  269. cmp.FilterPath(func(p cmp.Path) bool {
  270. return p[len(p)-1].Type() == reflect.TypeOf(&raw.Service{})
  271. }, cmp.Ignore()),
  272. )
  273. }
  274. for i, test := range []struct {
  275. callFunc func(*BucketHandle) (interface{}, error)
  276. want interface {
  277. Header() http.Header
  278. }
  279. metagenFunc func(interface{})
  280. }{
  281. {
  282. func(b *BucketHandle) (interface{}, error) { return b.newGetCall() },
  283. rc.Buckets.Get("name").Projection("full"),
  284. func(req interface{}) { req.(*raw.BucketsGetCall).IfMetagenerationMatch(metagen).UserProject("p") },
  285. },
  286. {
  287. func(b *BucketHandle) (interface{}, error) { return b.newDeleteCall() },
  288. rc.Buckets.Delete("name"),
  289. func(req interface{}) { req.(*raw.BucketsDeleteCall).IfMetagenerationMatch(metagen).UserProject("p") },
  290. },
  291. {
  292. func(b *BucketHandle) (interface{}, error) {
  293. return b.newPatchCall(&BucketAttrsToUpdate{
  294. VersioningEnabled: false,
  295. RequesterPays: false,
  296. })
  297. },
  298. rc.Buckets.Patch("name", &raw.Bucket{
  299. Versioning: &raw.BucketVersioning{
  300. Enabled: false,
  301. ForceSendFields: []string{"Enabled"},
  302. },
  303. Billing: &raw.BucketBilling{
  304. RequesterPays: false,
  305. ForceSendFields: []string{"RequesterPays"},
  306. },
  307. }).Projection("full"),
  308. func(req interface{}) { req.(*raw.BucketsPatchCall).IfMetagenerationMatch(metagen).UserProject("p") },
  309. },
  310. } {
  311. got, err := test.callFunc(b)
  312. if err != nil {
  313. t.Fatal(err)
  314. }
  315. setClientHeader(test.want.Header())
  316. if !equal(got, test.want) {
  317. t.Errorf("#%d: got %#v, want %#v", i, got, test.want)
  318. }
  319. got, err = test.callFunc(bm)
  320. if err != nil {
  321. t.Fatal(err)
  322. }
  323. test.metagenFunc(test.want)
  324. if !equal(got, test.want) {
  325. t.Errorf("#%d:\ngot %#v\nwant %#v", i, got, test.want)
  326. }
  327. }
  328. // Error.
  329. bm = b.If(BucketConditions{MetagenerationMatch: 1, MetagenerationNotMatch: 2})
  330. if _, err := bm.newGetCall(); err == nil {
  331. t.Errorf("got nil, want error")
  332. }
  333. if _, err := bm.newDeleteCall(); err == nil {
  334. t.Errorf("got nil, want error")
  335. }
  336. if _, err := bm.newPatchCall(&BucketAttrsToUpdate{}); err == nil {
  337. t.Errorf("got nil, want error")
  338. }
  339. }
  340. func TestNewBucket(t *testing.T) {
  341. labels := map[string]string{"a": "b"}
  342. matchClasses := []string{"MULTI_REGIONAL", "REGIONAL", "STANDARD"}
  343. aTime := time.Date(2017, 1, 2, 0, 0, 0, 0, time.UTC)
  344. rb := &raw.Bucket{
  345. Name: "name",
  346. Location: "loc",
  347. DefaultEventBasedHold: true,
  348. Metageneration: 3,
  349. StorageClass: "sc",
  350. TimeCreated: "2017-10-23T04:05:06Z",
  351. Versioning: &raw.BucketVersioning{Enabled: true},
  352. Labels: labels,
  353. Billing: &raw.BucketBilling{RequesterPays: true},
  354. Lifecycle: &raw.BucketLifecycle{
  355. Rule: []*raw.BucketLifecycleRule{{
  356. Action: &raw.BucketLifecycleRuleAction{
  357. Type: "SetStorageClass",
  358. StorageClass: "NEARLINE",
  359. },
  360. Condition: &raw.BucketLifecycleRuleCondition{
  361. Age: 10,
  362. IsLive: googleapi.Bool(true),
  363. CreatedBefore: "2017-01-02",
  364. MatchesStorageClass: matchClasses,
  365. NumNewerVersions: 3,
  366. },
  367. }},
  368. },
  369. RetentionPolicy: &raw.BucketRetentionPolicy{
  370. RetentionPeriod: 3,
  371. EffectiveTime: aTime.Format(time.RFC3339),
  372. },
  373. IamConfiguration: &raw.BucketIamConfiguration{
  374. BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{
  375. Enabled: true,
  376. LockedTime: aTime.Format(time.RFC3339),
  377. },
  378. },
  379. Cors: []*raw.BucketCors{
  380. {
  381. MaxAgeSeconds: 3600,
  382. Method: []string{"GET", "POST"},
  383. Origin: []string{"*"},
  384. ResponseHeader: []string{"FOO"},
  385. },
  386. },
  387. Acl: []*raw.BucketAccessControl{
  388. {Bucket: "name", Role: "READER", Email: "joe@example.com", Entity: "allUsers"},
  389. },
  390. Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key"},
  391. Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
  392. Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
  393. }
  394. want := &BucketAttrs{
  395. Name: "name",
  396. Location: "loc",
  397. DefaultEventBasedHold: true,
  398. MetaGeneration: 3,
  399. StorageClass: "sc",
  400. Created: time.Date(2017, 10, 23, 4, 5, 6, 0, time.UTC),
  401. VersioningEnabled: true,
  402. Labels: labels,
  403. RequesterPays: true,
  404. Lifecycle: Lifecycle{
  405. Rules: []LifecycleRule{
  406. {
  407. Action: LifecycleAction{
  408. Type: SetStorageClassAction,
  409. StorageClass: "NEARLINE",
  410. },
  411. Condition: LifecycleCondition{
  412. AgeInDays: 10,
  413. Liveness: Live,
  414. CreatedBefore: time.Date(2017, 1, 2, 0, 0, 0, 0, time.UTC),
  415. MatchesStorageClasses: matchClasses,
  416. NumNewerVersions: 3,
  417. },
  418. },
  419. },
  420. },
  421. RetentionPolicy: &RetentionPolicy{
  422. EffectiveTime: aTime,
  423. RetentionPeriod: 3 * time.Second,
  424. },
  425. BucketPolicyOnly: BucketPolicyOnly{Enabled: true, LockedTime: aTime},
  426. CORS: []CORS{
  427. {
  428. MaxAge: time.Hour,
  429. Methods: []string{"GET", "POST"},
  430. Origins: []string{"*"},
  431. ResponseHeaders: []string{"FOO"},
  432. },
  433. },
  434. Encryption: &BucketEncryption{DefaultKMSKeyName: "key"},
  435. Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
  436. Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
  437. ACL: []ACLRule{{Entity: "allUsers", Role: RoleReader, Email: "joe@example.com"}},
  438. DefaultObjectACL: nil,
  439. }
  440. got, err := newBucket(rb)
  441. if err != nil {
  442. t.Fatal(err)
  443. }
  444. if diff := testutil.Diff(got, want); diff != "" {
  445. t.Errorf("got=-, want=+:\n%s", diff)
  446. }
  447. }