Skip to content

Commit 8034c67

Browse files
feat: add support for db roles list (#1916)
* feat: add support for db roles * fix sample indentations * fix tests * Fix tests * Delete samples * skip unsupported emulator tests * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 7f5bac6 commit 8034c67

29 files changed

+726
-45
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

+50
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,54 @@
8181
<className>com/google/cloud/spanner/connection/Connection</className>
8282
<method>com.google.spanner.v1.ResultSetStats analyzeUpdate(com.google.cloud.spanner.Statement, com.google.cloud.spanner.ReadContext$QueryAnalyzeMode)</method>
8383
</difference>
84+
<difference>
85+
<differenceType>7012</differenceType>
86+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
87+
<method>com.google.api.gax.paging.Page listDatabaseRoles(java.lang.String, java.lang.String, com.google.cloud.spanner.Options$ListOption[])</method>
88+
</difference>
89+
<difference>
90+
<differenceType>7012</differenceType>
91+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
92+
<method>com.google.cloud.spanner.spi.v1.SpannerRpc$Paginated listDatabaseRoles(java.lang.String, int, java.lang.String)</method>
93+
</difference>
94+
<difference>
95+
<differenceType>7004</differenceType>
96+
<className>com/google/cloud/spanner/Database</className>
97+
<method>com.google.cloud.Policy getIAMPolicy()</method>
98+
</difference>
99+
<difference>
100+
<differenceType>7004</differenceType>
101+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
102+
<method>com.google.cloud.Policy getDatabaseIAMPolicy(java.lang.String, java.lang.String)</method>
103+
</difference>
104+
<difference>
105+
<differenceType>7004</differenceType>
106+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
107+
<method>com.google.spanner.v1.Session createSession(java.lang.String, java.util.Map, java.util.Map)</method>
108+
</difference>
109+
<difference>
110+
<differenceType>7004</differenceType>
111+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
112+
<method>java.util.List batchCreateSessions(java.lang.String, int, java.util.Map, java.util.Map)</method>
113+
</difference>
114+
<difference>
115+
<differenceType>7004</differenceType>
116+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
117+
<method>com.google.iam.v1.Policy getDatabaseAdminIAMPolicy(java.lang.String)</method>
118+
</difference>
119+
<difference>
120+
<differenceType>7004</differenceType>
121+
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
122+
<method>com.google.spanner.v1.Session createSession(java.lang.String, java.util.Map, java.util.Map)</method>
123+
</difference>
124+
<difference>
125+
<differenceType>7004</differenceType>
126+
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
127+
<method>java.util.List batchCreateSessions(java.lang.String, int, java.util.Map, java.util.Map)</method>
128+
</difference>
129+
<difference>
130+
<differenceType>7004</differenceType>
131+
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
132+
<method>com.google.iam.v1.Policy getDatabaseAdminIAMPolicy(java.lang.String)</method>
133+
</difference>
84134
</differences>

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java

+17-3
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,23 @@ public Page<Operation> listDatabaseOperations() {
143143
instance(), Options.filter(String.format(FILTER_DB_OPERATIONS_TEMPLATE, database())));
144144
}
145145

146-
/** Returns the IAM {@link Policy} for this database. */
147-
public Policy getIAMPolicy() {
148-
return dbClient.getDatabaseIAMPolicy(instance(), database());
146+
/**
147+
* Returns the IAM {@link Policy} for this database.
148+
*
149+
* <p>Version specifies the format used to create the policy, valid values are 0, 1, and 3.
150+
* Requests specifying an invalid value will be rejected. Requests for policies with any
151+
* conditional role bindings must specify version 3. Policies with no conditional role bindings
152+
* may specify any valid value or leave the field unset.
153+
*
154+
* <p>The policy in the response might use the policy version that you specified, or it might use
155+
* a lower policy version. For example, if you specify version 3, but the policy has no
156+
* conditional role bindings, the response uses version 1.
157+
*
158+
* <p>To learn which resources support conditions in their IAM policies, see the [IAM
159+
* documentation](https://cloud.google.com/iam/help/conditions/resource-policies).
160+
*/
161+
public Policy getIAMPolicy(int version) {
162+
return dbClient.getDatabaseIAMPolicy(instance(), database(), version);
149163
}
150164

151165
/**

google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java

+21-2
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,9 @@ OperationFuture<Database, RestoreDatabaseMetadata> restoreDatabase(Restore resto
339339
/** Lists long-running database operations on the specified instance. */
340340
Page<Operation> listDatabaseOperations(String instanceId, ListOption... options);
341341

342+
/** Lists database roles on the specified database. */
343+
Page<DatabaseRole> listDatabaseRoles(String instanceId, String databaseId, ListOption... options);
344+
342345
/** Lists long-running backup operations on the specified instance. */
343346
Page<Operation> listBackupOperations(String instanceId, ListOption... options);
344347

@@ -491,8 +494,24 @@ OperationFuture<Void, UpdateDatabaseDdlMetadata> updateDatabaseDdl(
491494
/** Gets the specified long-running operation. */
492495
Operation getOperation(String name);
493496

494-
/** Returns the IAM policy for the given database. */
495-
Policy getDatabaseIAMPolicy(String instanceId, String databaseId);
497+
/**
498+
* Returns the IAM policy for the given database.
499+
*
500+
* <p>Version specifies the format used to create the policy, valid values are 0, 1, and 3.
501+
* Requests specifying an invalid value will be rejected. Requests for policies with any
502+
* conditional role bindings must specify version 3. Policies with no conditional role bindings
503+
* may specify any valid value or leave the field unset.
504+
*
505+
* <p>The policy in the response might use the policy version that you specified, or it might use
506+
* a lower policy version. For example, if you specify version 3, but the policy has no
507+
* conditional role bindings, the response uses version 1.
508+
*
509+
* <p>To learn which resources support conditions in their IAM policies, see the
510+
*
511+
* @see <a href="https://cloud.google.com/iam/help/conditions/resource-policies">IAM
512+
* documentation</a>.
513+
*/
514+
Policy getDatabaseIAMPolicy(String instanceId, String databaseId, int version);
496515

497516
/**
498517
* Updates the IAM policy for the given database and returns the resulting policy. It is highly

google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java

+45-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.cloud.spanner.spi.v1.SpannerRpc.Paginated;
3030
import com.google.common.base.MoreObjects;
3131
import com.google.common.base.Preconditions;
32+
import com.google.iam.v1.GetPolicyOptions;
3233
import com.google.longrunning.Operation;
3334
import com.google.protobuf.Empty;
3435
import com.google.protobuf.FieldMask;
@@ -290,6 +291,43 @@ public Operation fromProto(Operation proto) {
290291
return pageFetcher.getNextPage();
291292
}
292293

294+
@Override
295+
public final Page<DatabaseRole> listDatabaseRoles(
296+
String instanceId, String databaseId, ListOption... options) {
297+
final String databaseName = getDatabaseName(instanceId, databaseId);
298+
final Options listOptions = Options.fromListOptions(options);
299+
final int pageSize = listOptions.hasPageSize() ? listOptions.pageSize() : 0;
300+
301+
PageFetcher<DatabaseRole, com.google.spanner.admin.database.v1.DatabaseRole> pageFetcher =
302+
new PageFetcher<DatabaseRole, com.google.spanner.admin.database.v1.DatabaseRole>() {
303+
@Override
304+
public Paginated<com.google.spanner.admin.database.v1.DatabaseRole> getNextPage(
305+
String nextPageToken) {
306+
try {
307+
return rpc.listDatabaseRoles(databaseName, pageSize, nextPageToken);
308+
} catch (SpannerException e) {
309+
throw SpannerExceptionFactory.newSpannerException(
310+
e.getErrorCode(),
311+
String.format(
312+
"Failed to list the databases roles of %s with pageToken %s: %s",
313+
databaseName,
314+
MoreObjects.firstNonNull(nextPageToken, "<null>"),
315+
e.getMessage()),
316+
e);
317+
}
318+
}
319+
320+
@Override
321+
public DatabaseRole fromProto(com.google.spanner.admin.database.v1.DatabaseRole proto) {
322+
return DatabaseRole.fromProto(proto);
323+
}
324+
};
325+
if (listOptions.hasPageToken()) {
326+
pageFetcher.setNextPageToken(listOptions.pageToken());
327+
}
328+
return pageFetcher.getNextPage();
329+
}
330+
293331
@Override
294332
public Page<Backup> listBackups(String instanceId, ListOption... options) {
295333
final String instanceName = getInstanceName(instanceId);
@@ -463,9 +501,13 @@ public Operation getOperation(String name) {
463501
}
464502

465503
@Override
466-
public Policy getDatabaseIAMPolicy(String instanceId, String databaseId) {
504+
public Policy getDatabaseIAMPolicy(String instanceId, String databaseId, int version) {
467505
final String databaseName = DatabaseId.of(projectId, instanceId, databaseId).getName();
468-
return policyMarshaller.fromPb(rpc.getDatabaseAdminIAMPolicy(databaseName));
506+
GetPolicyOptions options = null;
507+
if (version > 0) {
508+
options = GetPolicyOptions.newBuilder().setRequestedPolicyVersion(version).build();
509+
}
510+
return policyMarshaller.fromPb(rpc.getDatabaseAdminIAMPolicy(databaseName, options));
469511
}
470512

471513
@Override
@@ -487,7 +529,7 @@ public Iterable<String> testDatabaseIAMPermissions(
487529
@Override
488530
public Policy getBackupIAMPolicy(String instanceId, String backupId) {
489531
final String databaseName = BackupId.of(projectId, instanceId, backupId).getName();
490-
return policyMarshaller.fromPb(rpc.getDatabaseAdminIAMPolicy(databaseName));
532+
return policyMarshaller.fromPb(rpc.getDatabaseAdminIAMPolicy(databaseName, null));
491533
}
492534

493535
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner;
18+
19+
import static com.google.common.base.Preconditions.checkArgument;
20+
21+
import com.google.common.annotations.VisibleForTesting;
22+
import com.google.common.base.Preconditions;
23+
import java.util.Objects;
24+
25+
/** A Cloud Spanner database role. */
26+
public class DatabaseRole {
27+
28+
public static class Builder {
29+
30+
private final String name;
31+
32+
public Builder(String name) {
33+
this.name = Preconditions.checkNotNull(name);
34+
}
35+
36+
public DatabaseRole build() {
37+
return new DatabaseRole(this.name);
38+
}
39+
}
40+
41+
private final String name;
42+
43+
@VisibleForTesting
44+
DatabaseRole(String name) {
45+
this.name = name;
46+
}
47+
48+
public String getName() {
49+
return name;
50+
}
51+
52+
@Override
53+
public boolean equals(Object o) {
54+
if (this == o) {
55+
return true;
56+
}
57+
if (o == null || !getClass().equals(o.getClass())) {
58+
return false;
59+
}
60+
DatabaseRole databaseRole = (DatabaseRole) o;
61+
return Objects.equals(name, databaseRole.name);
62+
}
63+
64+
@Override
65+
public int hashCode() {
66+
return Objects.hash(name);
67+
}
68+
69+
@Override
70+
public String toString() {
71+
return String.format("DatabaseRole[%s]", name);
72+
}
73+
74+
static DatabaseRole fromProto(com.google.spanner.admin.database.v1.DatabaseRole proto) {
75+
checkArgument(!proto.getName().isEmpty(), "Missing expected 'name' field");
76+
return new DatabaseRole.Builder(proto.getName()).build();
77+
}
78+
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionClient.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,11 @@ SessionImpl createSession() {
211211
com.google.spanner.v1.Session session =
212212
spanner
213213
.getRpc()
214-
.createSession(db.getName(), spanner.getOptions().getSessionLabels(), options);
214+
.createSession(
215+
db.getName(),
216+
spanner.getOptions().getDatabaseRole(),
217+
spanner.getOptions().getSessionLabels(),
218+
options);
215219
return new SessionImpl(spanner, session.getName(), options);
216220
} catch (RuntimeException e) {
217221
TraceUtil.setWithFailure(span, e);
@@ -297,7 +301,11 @@ private List<SessionImpl> internalBatchCreateSessions(
297301
spanner
298302
.getRpc()
299303
.batchCreateSessions(
300-
db.getName(), sessionCount, spanner.getOptions().getSessionLabels(), options);
304+
db.getName(),
305+
sessionCount,
306+
spanner.getOptions().getDatabaseRole(),
307+
spanner.getOptions().getSessionLabels(),
308+
options);
301309
span.addAnnotation(
302310
String.format(
303311
"Request for %d sessions returned %d sessions", sessionCount, sessions.size()));

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java

+19
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
9999
private final int prefetchChunks;
100100
private final int numChannels;
101101
private final String transportChannelExecutorThreadNameFormat;
102+
private final String databaseRole;
102103
private final ImmutableMap<String, String> sessionLabels;
103104
private final SpannerStubSettings spannerStubSettings;
104105
private final InstanceAdminStubSettings instanceAdminStubSettings;
@@ -564,6 +565,7 @@ private SpannerOptions(Builder builder) {
564565
? builder.sessionPoolOptions
565566
: SessionPoolOptions.newBuilder().build();
566567
prefetchChunks = builder.prefetchChunks;
568+
databaseRole = builder.databaseRole;
567569
sessionLabels = builder.sessionLabels;
568570
try {
569571
spannerStubSettings = builder.spannerStubSettingsBuilder.build();
@@ -674,6 +676,7 @@ public static class Builder
674676

675677
private int prefetchChunks = DEFAULT_PREFETCH_CHUNKS;
676678
private SessionPoolOptions sessionPoolOptions;
679+
private String databaseRole;
677680
private ImmutableMap<String, String> sessionLabels;
678681
private SpannerStubSettings.Builder spannerStubSettingsBuilder =
679682
SpannerStubSettings.newBuilder();
@@ -730,6 +733,7 @@ private Builder() {
730733
options.transportChannelExecutorThreadNameFormat;
731734
this.sessionPoolOptions = options.sessionPoolOptions;
732735
this.prefetchChunks = options.prefetchChunks;
736+
this.databaseRole = options.databaseRole;
733737
this.sessionLabels = options.sessionLabels;
734738
this.spannerStubSettingsBuilder = options.spannerStubSettings.toBuilder();
735739
this.instanceAdminStubSettingsBuilder = options.instanceAdminStubSettings.toBuilder();
@@ -830,6 +834,17 @@ public Builder setSessionPoolOption(SessionPoolOptions sessionPoolOptions) {
830834
return this;
831835
}
832836

837+
/**
838+
* Sets the database role that should be used for connections that are created by this instance.
839+
* The database role that is used determines the access permissions that a connection has. This
840+
* can for example be used to create connections that are only permitted to access certain
841+
* tables.
842+
*/
843+
public Builder setDatabaseRole(String databaseRole) {
844+
this.databaseRole = databaseRole;
845+
return this;
846+
}
847+
833848
/**
834849
* Sets the labels to add to all Sessions created in this client.
835850
*
@@ -1215,6 +1230,10 @@ public SessionPoolOptions getSessionPoolOptions() {
12151230
return sessionPoolOptions;
12161231
}
12171232

1233+
public String getDatabaseRole() {
1234+
return databaseRole;
1235+
}
1236+
12181237
public Map<String, String> getSessionLabels() {
12191238
return sessionLabels;
12201239
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,8 @@ ClientSideStatement getClientSideStatement() {
320320
}
321321
}
322322

323-
static final Set<String> ddlStatements = ImmutableSet.of("CREATE", "DROP", "ALTER", "ANALYZE");
323+
static final Set<String> ddlStatements =
324+
ImmutableSet.of("CREATE", "DROP", "ALTER", "ANALYZE", "GRANT", "REVOKE");
324325
static final Set<String> selectStatements = ImmutableSet.of("SELECT", "WITH", "SHOW");
325326
static final Set<String> dmlStatements = ImmutableSet.of("INSERT", "UPDATE", "DELETE");
326327
private final Set<ClientSideStatementImpl> statements;

0 commit comments

Comments
 (0)