Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for db roles list #1916

Merged
merged 7 commits into from Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -49,20 +49,20 @@ If you are using Maven without BOM, add this to your dependencies:
If you are using Gradle 5.x or later, add this to your dependencies:

```Groovy
implementation platform('com.google.cloud:libraries-bom:26.0.0')
implementation platform('com.google.cloud:libraries-bom:26.1.0')

implementation 'com.google.cloud:google-cloud-spanner'
```
If you are using Gradle without BOM, add this to your dependencies:

```Groovy
implementation 'com.google.cloud:google-cloud-spanner:6.27.0'
implementation 'com.google.cloud:google-cloud-spanner:6.28.0'
```

If you are using SBT, add this to your dependencies:

```Scala
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.27.0"
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.28.0"
```

## Authentication
Expand Down
50 changes: 50 additions & 0 deletions google-cloud-spanner/clirr-ignored-differences.xml
Expand Up @@ -81,4 +81,54 @@
<className>com/google/cloud/spanner/connection/Connection</className>
<method>com.google.spanner.v1.ResultSetStats analyzeUpdate(com.google.cloud.spanner.Statement, com.google.cloud.spanner.ReadContext$QueryAnalyzeMode)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
<method>com.google.api.gax.paging.Page listDatabaseRoles(java.lang.String, java.lang.String, com.google.cloud.spanner.Options$ListOption[])</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
<method>com.google.cloud.spanner.spi.v1.SpannerRpc$Paginated listDatabaseRoles(java.lang.String, int, java.lang.String)</method>
</difference>
<difference>
<differenceType>7004</differenceType>
<className>com/google/cloud/spanner/Database</className>
<method>com.google.cloud.Policy getIAMPolicy()</method>
</difference>
<difference>
<differenceType>7004</differenceType>
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
<method>com.google.cloud.Policy getDatabaseIAMPolicy(java.lang.String, java.lang.String)</method>
</difference>
<difference>
<differenceType>7004</differenceType>
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
<method>com.google.spanner.v1.Session createSession(java.lang.String, java.util.Map, java.util.Map)</method>
</difference>
<difference>
<differenceType>7004</differenceType>
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
<method>java.util.List batchCreateSessions(java.lang.String, int, java.util.Map, java.util.Map)</method>
</difference>
<difference>
<differenceType>7004</differenceType>
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
<method>com.google.iam.v1.Policy getDatabaseAdminIAMPolicy(java.lang.String)</method>
</difference>
<difference>
<differenceType>7004</differenceType>
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
<method>com.google.spanner.v1.Session createSession(java.lang.String, java.util.Map, java.util.Map)</method>
</difference>
<difference>
<differenceType>7004</differenceType>
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
<method>java.util.List batchCreateSessions(java.lang.String, int, java.util.Map, java.util.Map)</method>
</difference>
<difference>
<differenceType>7004</differenceType>
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
<method>com.google.iam.v1.Policy getDatabaseAdminIAMPolicy(java.lang.String)</method>
</difference>
</differences>
Expand Up @@ -143,9 +143,23 @@ public Page<Operation> listDatabaseOperations() {
instance(), Options.filter(String.format(FILTER_DB_OPERATIONS_TEMPLATE, database())));
}

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

/**
Expand Down
Expand Up @@ -339,6 +339,9 @@ OperationFuture<Database, RestoreDatabaseMetadata> restoreDatabase(Restore resto
/** Lists long-running database operations on the specified instance. */
Page<Operation> listDatabaseOperations(String instanceId, ListOption... options);

/** Lists database roles on the specified database. */
Page<DatabaseRole> listDatabaseRoles(String instanceId, String databaseId, ListOption... options);

/** Lists long-running backup operations on the specified instance. */
Page<Operation> listBackupOperations(String instanceId, ListOption... options);

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

/** Returns the IAM policy for the given database. */
Policy getDatabaseIAMPolicy(String instanceId, String databaseId);
/**
* Returns the IAM policy for the given database.
*
* <p>Version specifies the format used to create the policy, valid values are 0, 1, and 3.
* Requests specifying an invalid value will be rejected. Requests for policies with any
* conditional role bindings must specify version 3. Policies with no conditional role bindings
* may specify any valid value or leave the field unset.
*
* <p>The policy in the response might use the policy version that you specified, or it might use
* a lower policy version. For example, if you specify version 3, but the policy has no
* conditional role bindings, the response uses version 1.
*
* <p>To learn which resources support conditions in their IAM policies, see the
*
* @see <a href="https://cloud.google.com/iam/help/conditions/resource-policies">IAM
* documentation</a>.
*/
Policy getDatabaseIAMPolicy(String instanceId, String databaseId, int version);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version argument needs some Javadoc. What does it do and when/why should I use it?


/**
* Updates the IAM policy for the given database and returns the resulting policy. It is highly
Expand Down
Expand Up @@ -29,6 +29,7 @@
import com.google.cloud.spanner.spi.v1.SpannerRpc.Paginated;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.iam.v1.GetPolicyOptions;
import com.google.longrunning.Operation;
import com.google.protobuf.Empty;
import com.google.protobuf.FieldMask;
Expand Down Expand Up @@ -290,6 +291,43 @@ public Operation fromProto(Operation proto) {
return pageFetcher.getNextPage();
}

@Override
public final Page<DatabaseRole> listDatabaseRoles(
String instanceId, String databaseId, ListOption... options) {
final String databaseName = getDatabaseName(instanceId, databaseId);
final Options listOptions = Options.fromListOptions(options);
final int pageSize = listOptions.hasPageSize() ? listOptions.pageSize() : 0;

PageFetcher<DatabaseRole, com.google.spanner.admin.database.v1.DatabaseRole> pageFetcher =
new PageFetcher<DatabaseRole, com.google.spanner.admin.database.v1.DatabaseRole>() {
@Override
public Paginated<com.google.spanner.admin.database.v1.DatabaseRole> getNextPage(
String nextPageToken) {
try {
return rpc.listDatabaseRoles(databaseName, pageSize, nextPageToken);
} catch (SpannerException e) {
throw SpannerExceptionFactory.newSpannerException(
e.getErrorCode(),
String.format(
"Failed to list the databases roles of %s with pageToken %s: %s",
databaseName,
MoreObjects.firstNonNull(nextPageToken, "<null>"),
e.getMessage()),
e);
}
}

@Override
public DatabaseRole fromProto(com.google.spanner.admin.database.v1.DatabaseRole proto) {
return DatabaseRole.fromProto(proto);
}
};
if (listOptions.hasPageToken()) {
pageFetcher.setNextPageToken(listOptions.pageToken());
}
return pageFetcher.getNextPage();
}

@Override
public Page<Backup> listBackups(String instanceId, ListOption... options) {
final String instanceName = getInstanceName(instanceId);
Expand Down Expand Up @@ -463,9 +501,13 @@ public Operation getOperation(String name) {
}

@Override
public Policy getDatabaseIAMPolicy(String instanceId, String databaseId) {
public Policy getDatabaseIAMPolicy(String instanceId, String databaseId, int version) {
final String databaseName = DatabaseId.of(projectId, instanceId, databaseId).getName();
return policyMarshaller.fromPb(rpc.getDatabaseAdminIAMPolicy(databaseName));
GetPolicyOptions options = null;
if (version > 0) {
options = GetPolicyOptions.newBuilder().setRequestedPolicyVersion(version).build();
}
return policyMarshaller.fromPb(rpc.getDatabaseAdminIAMPolicy(databaseName, options));
}

@Override
Expand All @@ -487,7 +529,7 @@ public Iterable<String> testDatabaseIAMPermissions(
@Override
public Policy getBackupIAMPolicy(String instanceId, String backupId) {
final String databaseName = BackupId.of(projectId, instanceId, backupId).getName();
return policyMarshaller.fromPb(rpc.getDatabaseAdminIAMPolicy(databaseName));
return policyMarshaller.fromPb(rpc.getDatabaseAdminIAMPolicy(databaseName, null));
}

@Override
Expand Down
@@ -0,0 +1,78 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.spanner;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.util.Objects;

/** A Cloud Spanner database role. */
public class DatabaseRole {

public static class Builder {

private final String name;

public Builder(String name) {
this.name = Preconditions.checkNotNull(name);
}

public DatabaseRole build() {
return new DatabaseRole(this.name);
}
}

private final String name;

@VisibleForTesting
DatabaseRole(String name) {
this.name = name;
}

public String getName() {
return name;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || !getClass().equals(o.getClass())) {
return false;
}
DatabaseRole databaseRole = (DatabaseRole) o;
return Objects.equals(name, databaseRole.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}

@Override
public String toString() {
return String.format("DatabaseRole[%s]", name);
}

static DatabaseRole fromProto(com.google.spanner.admin.database.v1.DatabaseRole proto) {
checkArgument(!proto.getName().isEmpty(), "Missing expected 'name' field");
return new DatabaseRole.Builder(proto.getName()).build();
}
}
Expand Up @@ -211,7 +211,11 @@ SessionImpl createSession() {
com.google.spanner.v1.Session session =
spanner
.getRpc()
.createSession(db.getName(), spanner.getOptions().getSessionLabels(), options);
.createSession(
db.getName(),
spanner.getOptions().getDatabaseRole(),
spanner.getOptions().getSessionLabels(),
options);
return new SessionImpl(spanner, session.getName(), options);
} catch (RuntimeException e) {
TraceUtil.setWithFailure(span, e);
Expand Down Expand Up @@ -297,7 +301,11 @@ private List<SessionImpl> internalBatchCreateSessions(
spanner
.getRpc()
.batchCreateSessions(
db.getName(), sessionCount, spanner.getOptions().getSessionLabels(), options);
db.getName(),
sessionCount,
spanner.getOptions().getDatabaseRole(),
spanner.getOptions().getSessionLabels(),
options);
span.addAnnotation(
String.format(
"Request for %d sessions returned %d sessions", sessionCount, sessions.size()));
Expand Down
Expand Up @@ -99,6 +99,7 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
private final int prefetchChunks;
private final int numChannels;
private final String transportChannelExecutorThreadNameFormat;
private final String databaseRole;
private final ImmutableMap<String, String> sessionLabels;
private final SpannerStubSettings spannerStubSettings;
private final InstanceAdminStubSettings instanceAdminStubSettings;
Expand Down Expand Up @@ -564,6 +565,7 @@ private SpannerOptions(Builder builder) {
? builder.sessionPoolOptions
: SessionPoolOptions.newBuilder().build();
prefetchChunks = builder.prefetchChunks;
databaseRole = builder.databaseRole;
sessionLabels = builder.sessionLabels;
try {
spannerStubSettings = builder.spannerStubSettingsBuilder.build();
Expand Down Expand Up @@ -674,6 +676,7 @@ public static class Builder

private int prefetchChunks = DEFAULT_PREFETCH_CHUNKS;
private SessionPoolOptions sessionPoolOptions;
private String databaseRole;
private ImmutableMap<String, String> sessionLabels;
private SpannerStubSettings.Builder spannerStubSettingsBuilder =
SpannerStubSettings.newBuilder();
Expand Down Expand Up @@ -730,6 +733,7 @@ private Builder() {
options.transportChannelExecutorThreadNameFormat;
this.sessionPoolOptions = options.sessionPoolOptions;
this.prefetchChunks = options.prefetchChunks;
this.databaseRole = options.databaseRole;
this.sessionLabels = options.sessionLabels;
this.spannerStubSettingsBuilder = options.spannerStubSettings.toBuilder();
this.instanceAdminStubSettingsBuilder = options.instanceAdminStubSettings.toBuilder();
Expand Down Expand Up @@ -830,6 +834,17 @@ public Builder setSessionPoolOption(SessionPoolOptions sessionPoolOptions) {
return this;
}

/**
* Sets the database role that should be used for connections that are created by this instance.
* The database role that is used determines the access permissions that a connection has. This
* can for example be used to create connections that are only permitted to access certain
* tables.
*/
public Builder setDatabaseRole(String databaseRole) {
this.databaseRole = databaseRole;
return this;
}

/**
* Sets the labels to add to all Sessions created in this client.
*
Expand Down Expand Up @@ -1215,6 +1230,10 @@ public SessionPoolOptions getSessionPoolOptions() {
return sessionPoolOptions;
}

public String getDatabaseRole() {
return databaseRole;
}

public Map<String, String> getSessionLabels() {
return sessionLabels;
}
Expand Down
Expand Up @@ -320,7 +320,8 @@ ClientSideStatement getClientSideStatement() {
}
}

static final Set<String> ddlStatements = ImmutableSet.of("CREATE", "DROP", "ALTER", "ANALYZE");
static final Set<String> ddlStatements =
ImmutableSet.of("CREATE", "DROP", "ALTER", "ANALYZE", "GRANT", "REVOKE");
static final Set<String> selectStatements = ImmutableSet.of("SELECT", "WITH", "SHOW");
static final Set<String> dmlStatements = ImmutableSet.of("INSERT", "UPDATE", "DELETE");
private final Set<ClientSideStatementImpl> statements;
Expand Down