Skip to content

Commit

Permalink
feat: add support for db roles list (#1916)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
rahul2393 and gcf-owl-bot[bot] committed Aug 22, 2022
1 parent 7f5bac6 commit 8034c67
Show file tree
Hide file tree
Showing 29 changed files with 726 additions and 45 deletions.
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);

/**
* 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

0 comments on commit 8034c67

Please sign in to comment.