Skip to content

Commit 2eec791

Browse files
authored
feat: implement GrpcStorageImpl ObjectAccessControl operations (#1818)
Newly implemented methods (including any overloads): 1. GrpcStorageImpl#createAcl 2. GrpcStorageImpl#getAcl 3. GrpcStorageImpl#listAcl 4. GrpcStorageImpl#updateAcl 5. GrpcStorageImpl#deleteAcl 6. Blob#createAcl 7. Blob#getAcl 8. Blob#listAcl 9. Blob#updateAcl 10. Blob#deleteAcl
1 parent 2852655 commit 2eec791

File tree

6 files changed

+511
-20
lines changed

6 files changed

+511
-20
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,7 @@ public URL signUrl(long duration, TimeUnit unit, SignUrlOption... options) {
10631063
*
10641064
* @throws StorageException upon failure
10651065
*/
1066-
@TransportCompatibility({Transport.HTTP})
1066+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
10671067
public Acl getAcl(Entity entity) {
10681068
return storage.getAcl(getBlobId(), entity);
10691069
}
@@ -1085,7 +1085,7 @@ public Acl getAcl(Entity entity) {
10851085
* @return {@code true} if the ACL was deleted, {@code false} if it was not found
10861086
* @throws StorageException upon failure
10871087
*/
1088-
@TransportCompatibility({Transport.HTTP})
1088+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
10891089
public boolean deleteAcl(Entity entity) {
10901090
return storage.deleteAcl(getBlobId(), entity);
10911091
}
@@ -1101,7 +1101,7 @@ public boolean deleteAcl(Entity entity) {
11011101
*
11021102
* @throws StorageException upon failure
11031103
*/
1104-
@TransportCompatibility({Transport.HTTP})
1104+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
11051105
public Acl createAcl(Acl acl) {
11061106
return storage.createAcl(getBlobId(), acl);
11071107
}
@@ -1117,7 +1117,7 @@ public Acl createAcl(Acl acl) {
11171117
*
11181118
* @throws StorageException upon failure
11191119
*/
1120-
@TransportCompatibility({Transport.HTTP})
1120+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
11211121
public Acl updateAcl(Acl acl) {
11221122
return storage.updateAcl(getBlobId(), acl);
11231123
}
@@ -1136,7 +1136,7 @@ public Acl updateAcl(Acl acl) {
11361136
*
11371137
* @throws StorageException upon failure
11381138
*/
1139-
@TransportCompatibility({Transport.HTTP})
1139+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
11401140
public List<Acl> listAcls() {
11411141
return storage.listAcls(getBlobId());
11421142
}

google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java

+144-8
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,7 @@ public boolean deleteAcl(String bucket, Entity entity, BucketSourceOption... opt
904904
}
905905
long metageneration = resp.getMetageneration();
906906

907-
UpdateBucketRequest req = createUpdateAclRequest(bucket, newAcls, metageneration);
907+
UpdateBucketRequest req = createUpdateBucketAclRequest(bucket, newAcls, metageneration);
908908

909909
com.google.storage.v2.Bucket updateResult = updateBucket(req);
910910
// read the response to ensure there is no longer an acl for the specified entity
@@ -954,7 +954,7 @@ public Acl updateAcl(String bucket, Acl acl, BucketSourceOption... options) {
954954
.collect(ImmutableList.toImmutableList());
955955

956956
UpdateBucketRequest req =
957-
createUpdateAclRequest(bucket, newDefaultAcls, resp.getMetageneration());
957+
createUpdateBucketAclRequest(bucket, newDefaultAcls, resp.getMetageneration());
958958

959959
com.google.storage.v2.Bucket updateResult = updateBucket(req);
960960

@@ -1109,27 +1109,117 @@ public List<Acl> listDefaultAcls(String bucket) {
11091109

11101110
@Override
11111111
public Acl getAcl(BlobId blob, Entity entity) {
1112-
return throwNotYetImplemented(fmtMethodName("getAcl", BlobId.class, Entity.class));
1112+
try {
1113+
Object req = codecs.blobId().encode(blob);
1114+
Object resp = getObjectWithAcls(req);
1115+
1116+
Predicate<ObjectAccessControl> entityPredicate =
1117+
objectAclEntityOrAltEq(codecs.entity().encode(entity));
1118+
1119+
Optional<ObjectAccessControl> first =
1120+
resp.getAclList().stream().filter(entityPredicate).findFirst();
1121+
1122+
// HttpStorageRpc defaults to null if Not Found
1123+
return first.map(codecs.objectAcl()::decode).orElse(null);
1124+
} catch (NotFoundException e) {
1125+
return null;
1126+
} catch (StorageException se) {
1127+
if (se.getCode() == 404) {
1128+
return null;
1129+
} else {
1130+
throw se;
1131+
}
1132+
}
11131133
}
11141134

11151135
@Override
11161136
public boolean deleteAcl(BlobId blob, Entity entity) {
1117-
return throwNotYetImplemented(fmtMethodName("deleteAcl", BlobId.class, Entity.class));
1137+
try {
1138+
Object obj = codecs.blobId().encode(blob);
1139+
Object resp = getObjectWithAcls(obj);
1140+
String encode = codecs.entity().encode(entity);
1141+
1142+
Predicate<ObjectAccessControl> entityPredicate = objectAclEntityOrAltEq(encode);
1143+
1144+
List<ObjectAccessControl> currentDefaultAcls = resp.getAclList();
1145+
ImmutableList<ObjectAccessControl> newDefaultAcls =
1146+
currentDefaultAcls.stream()
1147+
.filter(entityPredicate.negate())
1148+
.collect(ImmutableList.toImmutableList());
1149+
if (newDefaultAcls.equals(currentDefaultAcls)) {
1150+
// we didn't actually filter anything out, no need to send an RPC, simply return false
1151+
return false;
1152+
}
1153+
long metageneration = resp.getMetageneration();
1154+
1155+
UpdateObjectRequest req = createUpdateObjectAclRequest(obj, newDefaultAcls, metageneration);
1156+
1157+
Object updateResult = updateObject(req);
1158+
// read the response to ensure there is no longer an acl for the specified entity
1159+
Optional<ObjectAccessControl> first =
1160+
updateResult.getAclList().stream().filter(entityPredicate).findFirst();
1161+
return !first.isPresent();
1162+
} catch (NotFoundException e) {
1163+
// HttpStorageRpc returns false if the bucket doesn't exist :(
1164+
return false;
1165+
} catch (StorageException se) {
1166+
if (se.getCode() == 404) {
1167+
return false;
1168+
} else {
1169+
throw se;
1170+
}
1171+
}
11181172
}
11191173

11201174
@Override
11211175
public Acl createAcl(BlobId blob, Acl acl) {
1122-
return throwNotYetImplemented(fmtMethodName("createAcl", BlobId.class, Acl.class));
1176+
return updateAcl(blob, acl);
11231177
}
11241178

11251179
@Override
11261180
public Acl updateAcl(BlobId blob, Acl acl) {
1127-
return throwNotYetImplemented(fmtMethodName("updateAcl", BlobId.class, Acl.class));
1181+
try {
1182+
Object obj = codecs.blobId().encode(blob);
1183+
Object resp = getObjectWithAcls(obj);
1184+
ObjectAccessControl encode = codecs.objectAcl().encode(acl);
1185+
String entity = encode.getEntity();
1186+
1187+
Predicate<ObjectAccessControl> entityPredicate = objectAclEntityOrAltEq(entity);
1188+
1189+
ImmutableList<ObjectAccessControl> newDefaultAcls =
1190+
Streams.concat(
1191+
resp.getAclList().stream().filter(entityPredicate.negate()), Stream.of(encode))
1192+
.collect(ImmutableList.toImmutableList());
1193+
1194+
UpdateObjectRequest req =
1195+
createUpdateObjectAclRequest(obj, newDefaultAcls, resp.getMetageneration());
1196+
1197+
Object updateResult = updateObject(req);
1198+
1199+
Optional<Acl> first =
1200+
updateResult.getAclList().stream()
1201+
.filter(entityPredicate)
1202+
.findFirst()
1203+
.map(codecs.objectAcl()::decode);
1204+
1205+
return first.orElseThrow(
1206+
() -> new StorageException(0, "Acl update call success, but not in response"));
1207+
} catch (NotFoundException e) {
1208+
throw StorageException.coalesce(e);
1209+
}
11281210
}
11291211

11301212
@Override
11311213
public List<Acl> listAcls(BlobId blob) {
1132-
return throwNotYetImplemented(fmtMethodName("listAcls", BlobId.class));
1214+
try {
1215+
Object req = codecs.blobId().encode(blob);
1216+
Object resp = getObjectWithAcls(req);
1217+
return resp.getAclList().stream()
1218+
.map(codecs.objectAcl()::decode)
1219+
.collect(ImmutableList.toImmutableList());
1220+
} catch (NotFoundException e) {
1221+
throw StorageException.coalesce(e);
1222+
}
11331223
}
11341224

11351225
@Override
@@ -1637,7 +1727,7 @@ private static UpdateBucketRequest createUpdateDefaultAclRequest(
16371727
.build();
16381728
}
16391729

1640-
private static UpdateBucketRequest createUpdateAclRequest(
1730+
private static UpdateBucketRequest createUpdateBucketAclRequest(
16411731
String bucket, ImmutableList<BucketAccessControl> newDefaultAcls, long metageneration) {
16421732
com.google.storage.v2.Bucket update =
16431733
com.google.storage.v2.Bucket.newBuilder()
@@ -1653,4 +1743,50 @@ private static UpdateBucketRequest createUpdateAclRequest(
16531743
.setBucket(update)
16541744
.build();
16551745
}
1746+
1747+
private Object getObjectWithAcls(Object obj) {
1748+
Fields fields =
1749+
UnifiedOpts.fields(ImmutableSet.of(BucketField.ACL, BucketField.METAGENERATION));
1750+
GrpcCallContext grpcCallContext = GrpcCallContext.createDefault();
1751+
GetObjectRequest req =
1752+
fields
1753+
.getObject()
1754+
.apply(GetObjectRequest.newBuilder())
1755+
.setBucket(obj.getBucket())
1756+
.setObject(obj.getName())
1757+
.build();
1758+
1759+
return Retrying.run(
1760+
getOptions(),
1761+
retryAlgorithmManager.getFor(req),
1762+
() -> storageClient.getObjectCallable().call(req, grpcCallContext),
1763+
Decoder.identity());
1764+
}
1765+
1766+
private static UpdateObjectRequest createUpdateObjectAclRequest(
1767+
Object obj, ImmutableList<ObjectAccessControl> newAcls, long metageneration) {
1768+
Object update =
1769+
Object.newBuilder()
1770+
.setBucket(obj.getBucket())
1771+
.setName(obj.getName())
1772+
.addAllAcl(newAcls)
1773+
.build();
1774+
Opts<BucketTargetOpt> opts =
1775+
Opts.from(
1776+
UnifiedOpts.fields(ImmutableSet.of(BlobField.ACL)),
1777+
UnifiedOpts.metagenerationMatch(metageneration));
1778+
return opts.updateObjectsRequest()
1779+
.apply(UpdateObjectRequest.newBuilder())
1780+
.setObject(update)
1781+
.build();
1782+
}
1783+
1784+
private Object updateObject(UpdateObjectRequest req) {
1785+
GrpcCallContext grpcCallContext = GrpcCallContext.createDefault();
1786+
return Retrying.run(
1787+
getOptions(),
1788+
retryAlgorithmManager.getFor(req),
1789+
() -> storageClient.updateObjectCallable().call(req, grpcCallContext),
1790+
Decoder.identity());
1791+
}
16561792
}

google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -3599,7 +3599,7 @@ PostPolicyV4 generateSignedPostPolicyV4(
35993599
*
36003600
* @throws StorageException upon failure
36013601
*/
3602-
@TransportCompatibility({Transport.HTTP})
3602+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
36033603
Acl getAcl(BlobId blob, Entity entity);
36043604

36053605
/**
@@ -3623,7 +3623,7 @@ PostPolicyV4 generateSignedPostPolicyV4(
36233623
* @return {@code true} if the ACL was deleted, {@code false} if it was not found
36243624
* @throws StorageException upon failure
36253625
*/
3626-
@TransportCompatibility({Transport.HTTP})
3626+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
36273627
boolean deleteAcl(BlobId blob, Entity entity);
36283628

36293629
/**
@@ -3651,7 +3651,7 @@ PostPolicyV4 generateSignedPostPolicyV4(
36513651
*
36523652
* @throws StorageException upon failure
36533653
*/
3654-
@TransportCompatibility({Transport.HTTP})
3654+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
36553655
Acl createAcl(BlobId blob, Acl acl);
36563656

36573657
/**
@@ -3669,7 +3669,7 @@ PostPolicyV4 generateSignedPostPolicyV4(
36693669
*
36703670
* @throws StorageException upon failure
36713671
*/
3672-
@TransportCompatibility({Transport.HTTP})
3672+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
36733673
Acl updateAcl(BlobId blob, Acl acl);
36743674

36753675
/**
@@ -3690,7 +3690,7 @@ PostPolicyV4 generateSignedPostPolicyV4(
36903690
*
36913691
* @throws StorageException upon failure
36923692
*/
3693-
@TransportCompatibility({Transport.HTTP})
3693+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
36943694
List<Acl> listAcls(BlobId blob);
36953695

36963696
/**

google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,12 @@ public Mapper<ListObjectsRequest.Builder> listObjects() {
713713
}
714714

715715
static final class Fields extends RpcOptVal<ImmutableSet<NamedField>>
716-
implements ObjectSourceOpt, ObjectListOpt, BucketSourceOpt, BucketTargetOpt, BucketListOpt {
716+
implements ObjectSourceOpt,
717+
ObjectListOpt,
718+
ObjectTargetOpt,
719+
BucketSourceOpt,
720+
BucketTargetOpt,
721+
BucketListOpt {
717722

718723
/**
719724
* Apiary and gRPC have differing handling of where the field selector is evaluated relative to
@@ -772,6 +777,16 @@ public Mapper<ReadObjectRequest.Builder> readObject() {
772777
return b -> b.setReadMask(FieldMask.newBuilder().addAllPaths(getPaths()).build());
773778
}
774779

780+
@Override
781+
public Mapper<UpdateObjectRequest.Builder> updateObject() {
782+
return b -> b.setUpdateMask(FieldMask.newBuilder().addAllPaths(getPaths()).build());
783+
}
784+
785+
@Override
786+
public Mapper<RewriteObjectRequest.Builder> rewriteObject() {
787+
return Mapper.identity();
788+
}
789+
775790
/**
776791
* Define a decoder which can clear out any fields which may have not been selected.
777792
*

google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITAccessTest.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -1125,10 +1125,14 @@ public void testBlobAcl() {
11251125

11261126
static ImmutableList<Acl> dropEtags(List<Acl> defaultAcls) {
11271127
return defaultAcls.stream()
1128-
.map(acl -> Acl.of(acl.getEntity(), acl.getRole()))
1128+
.map(ITAccessTest::dropEtag)
11291129
.collect(ImmutableList.toImmutableList());
11301130
}
11311131

1132+
static Acl dropEtag(Acl acl) {
1133+
return Acl.of(acl.getEntity(), acl.getRole());
1134+
}
1135+
11321136
static Predicate<Acl> hasRole(Acl.Role expected) {
11331137
return acl -> acl.getRole().equals(expected);
11341138
}

0 commit comments

Comments
 (0)