From e7da944eb828347f79ee41f89f79adb39b308b13 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Thu, 30 Aug 2018 18:31:40 -0400 Subject: [PATCH 1/3] WIP: Bigtable: add resource level IAM --- .../admin/v2/BigtableInstanceAdminClient.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java index b2a2f741126d..4fd1caa8fc34 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java @@ -15,9 +15,24 @@ */ package com.google.cloud.bigtable.admin.v2; +import com.google.api.core.ApiFunction; +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.bigtable.admin.v2.InstanceName; import com.google.bigtable.admin.v2.ProjectName; +import com.google.cloud.Policy; +import com.google.cloud.Policy.DefaultMarshaller; import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.iam.v1.GetIamPolicyRequest; +import com.google.iam.v1.SetIamPolicyRequest; +import com.google.iam.v1.TestIamPermissionsRequest; +import com.google.iam.v1.TestIamPermissionsResponse; import java.io.IOException; +import java.util.Arrays; +import java.util.List; import javax.annotation.Nonnull; /** @@ -105,4 +120,115 @@ public ProjectName getProjectName() { public void close() { stub.close(); } + + public Policy getIamPolicy(String instanceId) { + return awaitFuture(getIamPolicyAsync(instanceId)); + } + + public ApiFuture getIamPolicyAsync(String instanceId) { + InstanceName name = InstanceName.of(projectName.getProject(), instanceId); + + GetIamPolicyRequest request = GetIamPolicyRequest.newBuilder() + .setResource(name.toString()) + .build(); + + final PolicyMarshaller marshaller = new PolicyMarshaller(); + + return ApiFutures.transform( + stub.getIamPolicyCallable().futureCall(request), + new ApiFunction() { + @Override + public Policy apply(com.google.iam.v1.Policy proto) { + return marshaller.fromPb(proto); + } + }, + MoreExecutors.directExecutor() + ); + } + + public Policy setIamPolicy(String instanceId, Policy policy) { + return awaitFuture(setIamPolicyAsync(instanceId, policy)); + } + + public ApiFuture setIamPolicyAsync(String instanceId, Policy policy) { + InstanceName name = InstanceName.of(projectName.getProject(), instanceId); + final PolicyMarshaller marshaller = new PolicyMarshaller(); + + SetIamPolicyRequest request = SetIamPolicyRequest.newBuilder() + .setResource(name.toString()) + .setPolicy(marshaller.toPb(policy)) + .build(); + + return ApiFutures.transform( + stub.setIamPolicyCallable().futureCall(request), + new ApiFunction() { + @Override + public Policy apply(com.google.iam.v1.Policy proto) { + return marshaller.fromPb(proto); + } + }, + MoreExecutors.directExecutor() + ); + } + + public List testIamPermission(String instanceId, String... permissions) { + return awaitFuture(testIamPermissionAsync(instanceId, permissions)); + } + + public ApiFuture> testIamPermissionAsync(String instanceId, String... permissions) { + InstanceName name = InstanceName.of(projectName.getProject(), instanceId); + + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(name.toString()) + .addAllPermissions(Arrays.asList(permissions)) + .build(); + + return ApiFutures.transform( + stub.testIamPermissionsCallable().futureCall(request), + new ApiFunction>() { + @Override + public List apply(TestIamPermissionsResponse input) { + return input.getPermissionsList(); + } + }, + MoreExecutors.directExecutor() + ); + } + + private static class PolicyMarshaller extends DefaultMarshaller { + @Override + public Policy fromPb(com.google.iam.v1.Policy policyPb) { + return super.fromPb(policyPb); + } + + @Override + public com.google.iam.v1.Policy toPb(Policy policy) { + return super.toPb(policy); + } + } + + /** + * Awaits the result of a future, taking care to propagate errors while maintaining the call site + * in a suppressed exception. This allows semantic errors to be caught across threads, while + * preserving the call site in the error. The caller's stacktrace will be made available as a + * suppressed exception. + */ + // TODO(igorbernstein2): try to move this into gax + private T awaitFuture(ApiFuture future) { + RuntimeException error; + try { + return Futures.getUnchecked(future); + } catch (UncheckedExecutionException e) { + if (e.getCause() instanceof RuntimeException) { + error = (RuntimeException) e.getCause(); + } else { + error = e; + } + } catch (RuntimeException e) { + error = e; + } + // Add the caller's stack as a suppressed exception + error.addSuppressed(new RuntimeException("Encountered error while awaiting future")); + throw error; + } } From aa3e599f6ba3ce6dca90e7182e67f390d1ca0e06 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Fri, 31 Aug 2018 14:25:45 -0400 Subject: [PATCH 2/3] docs & tests --- .../admin/v2/BigtableInstanceAdminClient.java | 206 +++++++++++++++++- .../v2/BigtableInstanceAdminClientTest.java | 130 ++++++++++- 2 files changed, 328 insertions(+), 8 deletions(-) diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java index 4fd1caa8fc34..6dc78dd08d99 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java @@ -18,6 +18,7 @@ import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; +import com.google.api.resourcenames.ResourceName; import com.google.bigtable.admin.v2.InstanceName; import com.google.bigtable.admin.v2.ProjectName; import com.google.cloud.Policy; @@ -121,10 +122,53 @@ public void close() { stub.close(); } + /** + * Gets the IAM access control policy for the specified instance. + * + *

Sample code: + * + *

{@code
+   * Policy policy = client.getIamPolicy("my-instance");
+   * for(Map.Entry> entry : policy.getBindings().entrySet()) {
+   *   System.out.printf("Role: %s Identities: %s\n", entry.getKey(), entry.getValue());
+   * }
+   * }
+ * + * @see Instance-level + * IAM management + */ + @SuppressWarnings("WeakerAccess") public Policy getIamPolicy(String instanceId) { return awaitFuture(getIamPolicyAsync(instanceId)); } + /** + * Asynchronously gets the IAM access control policy for the specified instance. + * + *

Sample code: + * + *

{@code
+   * ApiFuture policyFuture = client.getIamPolicyAsync("my-instance");
+   *
+   *
+   * ApiFutures.addCallback(policyFuture,
+   *   new ApiFutureCallback() {
+   *     public void onSuccess(Policy policy) {
+   *       for (Entry> entry : policy.getBindings().entrySet()) {
+   *         System.out.printf("Role: %s Identities: %s\n", entry.getKey(), entry.getValue());
+   *       }
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Instance-level IAM management + */ + @SuppressWarnings("WeakerAccess") public ApiFuture getIamPolicyAsync(String instanceId) { InstanceName name = InstanceName.of(projectName.getProject(), instanceId); @@ -132,7 +176,7 @@ public ApiFuture getIamPolicyAsync(String instanceId) { .setResource(name.toString()) .build(); - final PolicyMarshaller marshaller = new PolicyMarshaller(); + final IamPolicyMarshaller marshaller = new IamPolicyMarshaller(); return ApiFutures.transform( stub.getIamPolicyCallable().futureCall(request), @@ -146,13 +190,59 @@ public Policy apply(com.google.iam.v1.Policy proto) { ); } + /** + * Replaces the IAM policy associated with the specified instance. + * + *

Sample code: + * + *

{@code
+   * Policy newPolicy = client.setIamPolicy("my-instance",
+   *   Policy.newBuilder()
+   *     .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com"))
+   *     .addIdentity(Role.of("bigtable.admin"), Identity.group("admins@example.com"))
+   *     .build());
+   * }
+ * + * @see Instance-level IAM management + */ + @SuppressWarnings("WeakerAccess") public Policy setIamPolicy(String instanceId, Policy policy) { return awaitFuture(setIamPolicyAsync(instanceId, policy)); } + /** + * Asynchronously replaces the IAM policy associated with the specified instance. + * + *

Sample code: + * + *

{@code
+   * ApiFuture newPolicyFuture = client.setIamPolicyAsync("my-instance",
+   *   Policy.newBuilder()
+   *     .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com"))
+   *     .addIdentity(Role.of("bigtable.admin"), Identity.group("admins@example.com"))
+   *     .build());
+   *
+   * ApiFutures.addCallback(policyFuture,
+   *   new ApiFutureCallback() {
+   *     public void onSuccess(Policy policy) {
+   *       for (Entry> entry : policy.getBindings().entrySet()) {
+   *         System.out.printf("Role: %s Identities: %s\n", entry.getKey(), entry.getValue());
+   *       }
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Instance-level IAM management + */ + @SuppressWarnings("WeakerAccess") public ApiFuture setIamPolicyAsync(String instanceId, Policy policy) { InstanceName name = InstanceName.of(projectName.getProject(), instanceId); - final PolicyMarshaller marshaller = new PolicyMarshaller(); + final IamPolicyMarshaller marshaller = new IamPolicyMarshaller(); SetIamPolicyRequest request = SetIamPolicyRequest.newBuilder() .setResource(name.toString()) @@ -171,15 +261,115 @@ public Policy apply(com.google.iam.v1.Policy proto) { ); } + /** + * Tests whether the caller has the given permissions for the specified instance. + * Returns a subset of the specified permissions that the caller has. + * + *

Sample code: + * + *

{@code
+   * List grantedPermissions = client.testIamPermission("my-instance",
+   *   "bigtable.tables.readRows", "bigtable.tables.mutateRows");
+   * }
+ * + * System.out.println("Has read access: " + grantedPermissions.contains("bigtable.tables.readRows")); + * System.out.println("Has write access: " + grantedPermissions.contains("bigtable.tables.mutateRows")); + * + * @see Cloud Bigtable permissions + */ + @SuppressWarnings("WeakerAccess") public List testIamPermission(String instanceId, String... permissions) { - return awaitFuture(testIamPermissionAsync(instanceId, permissions)); + return testIamPermission(InstanceName.of(projectName.getProject(), instanceId), permissions); } + /** + * Asynchronously tests whether the caller has the given permissions for the specified instance. + * Returns a subset of the specified permissions that the caller has. + * + *

Sample code: + * + *

{@code
+   * ApiFuture> grantedPermissionsFuture = client.testIamPermission("my-instance",
+   *   "bigtable.tables.readRows", "bigtable.tables.mutateRows");
+   *
+   * ApiFutures.addCallback(grantedPermissionsFuture,
+   *   new ApiFutureCallback>() {
+   *     public void onSuccess(List grantedPermissions) {
+   *       System.out.println("Has read access: " + grantedPermissions.contains("bigtable.tables.readRows"));
+   *       System.out.println("Has write access: " + grantedPermissions.contains("bigtable.tables.mutateRows"));
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Cloud Bigtable permissions + */ + @SuppressWarnings("WeakerAccess") public ApiFuture> testIamPermissionAsync(String instanceId, String... permissions) { - InstanceName name = InstanceName.of(projectName.getProject(), instanceId); + return testIamPermissionAsync(InstanceName.of(projectName.getProject(), instanceId), permissions); + } + + /** + * Tests whether the caller has the given permissions for the specified absolute resource name + * (note that the current project of the client is ignored). + * + * Returns a subset of the specified permissions that the caller has. + * + *

Sample code: + * + *

{@code
+   * List grantedPermissions = client.testIamPermission(
+   *   TableName.of("my-project", "my-instance", "my-table"),
+   *   "bigtable.tables.readRows", "bigtable.tables.mutateRows");
+   *
+   * System.out.println("Has read access: " + grantedPermissions.contains("bigtable.tables.readRows"));
+   * System.out.println("Has write access: " + grantedPermissions.contains("bigtable.tables.mutateRows"));
+   * }
+ * + * @see Cloud Bigtable permissions + */ + @SuppressWarnings("WeakerAccess") + public List testIamPermission(ResourceName resourceName, String... permissions) { + return awaitFuture(testIamPermissionAsync(resourceName, permissions)); + } + + /** + * Asynchronously tests whether the caller has the given permissions for the the specified + * absolute resource name (note that the current project of the client is ignored). + * Returns a subset of the specified permissions that the caller has. + * + *

Sample code: + * + *

{@code
+   * ApiFuture> grantedPermissionsFuture = client.testIamPermission(
+   *   TableName.of("my-project", "my-instance", "my-table"),
+   *   "bigtable.tables.readRows", "bigtable.tables.mutateRows");
+   *
+   * ApiFutures.addCallback(grantedPermissionsFuture,
+   *   new ApiFutureCallback>() {
+   *     public void onSuccess(List grantedPermissions) {
+   *       System.out.println("Has read access: " + grantedPermissions.contains("bigtable.tables.readRows"));
+   *       System.out.println("Has write access: " + grantedPermissions.contains("bigtable.tables.mutateRows"));
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Cloud Bigtable permissions + */ + @SuppressWarnings("WeakerAccess") + public ApiFuture> testIamPermissionAsync(ResourceName resourceName, String... permissions) { TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() - .setResource(name.toString()) + .setResource(resourceName.toString()) .addAllPermissions(Arrays.asList(permissions)) .build(); @@ -195,7 +385,11 @@ public List apply(TestIamPermissionsResponse input) { ); } - private static class PolicyMarshaller extends DefaultMarshaller { + /** + * Simple adapter to expose {@link DefaultMarshaller} to this class. It enables this client to + * convert to/from IAM wrappers and protobufs. + */ + private static class IamPolicyMarshaller extends DefaultMarshaller { @Override public Policy fromPb(com.google.iam.v1.Policy policyPb) { return super.fromPb(policyPb); diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java index d76193b7afb5..dc2f1622e425 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java @@ -17,8 +17,17 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.api.core.ApiFutures; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.bigtable.admin.v2.InstanceName; import com.google.bigtable.admin.v2.ProjectName; +import com.google.cloud.Identity; +import com.google.cloud.Policy; +import com.google.cloud.Role; import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub; +import com.google.common.io.BaseEncoding; +import com.google.protobuf.ByteString; +import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,19 +37,34 @@ @RunWith(MockitoJUnitRunner.class) public class BigtableInstanceAdminClientTest { + private static final ProjectName PROJECT_NAME = ProjectName.of("my-project"); + private static final InstanceName INSTANCE_NAME = + InstanceName.of(PROJECT_NAME.getProject(), "my-instance"); + private BigtableInstanceAdminClient adminClient; + @Mock private BigtableInstanceAdminStub mockStub; + @Mock + private UnaryCallable mockGetIamPolicyCallable; + @Mock + private UnaryCallable mockSetIamPolicyCallable; + @Mock + private UnaryCallable mockTestIamPermissionsCallable; @Before public void setUp() { adminClient = BigtableInstanceAdminClient - .create(ProjectName.of("[PROJECT]"), mockStub); + .create(PROJECT_NAME, mockStub); + + Mockito.when(mockStub.getIamPolicyCallable()).thenReturn(mockGetIamPolicyCallable); + Mockito.when(mockStub.setIamPolicyCallable()).thenReturn(mockSetIamPolicyCallable); + Mockito.when(mockStub.testIamPermissionsCallable()).thenReturn(mockTestIamPermissionsCallable); } @Test public void testProjectName() { - assertThat(adminClient.getProjectName()).isEqualTo(ProjectName.of("[PROJECT]")); + assertThat(adminClient.getProjectName()).isEqualTo(PROJECT_NAME); } @Test @@ -48,4 +72,106 @@ public void testClose() { adminClient.close(); Mockito.verify(mockStub).close(); } + + @Test + public void testGetIamPolicy() { + // Setup + com.google.iam.v1.GetIamPolicyRequest expectedRequest = + com.google.iam.v1.GetIamPolicyRequest.newBuilder() + .setResource(INSTANCE_NAME.toString()) + .build(); + + com.google.iam.v1.Policy expectedResponse = + com.google.iam.v1.Policy.newBuilder() + .addBindings( + com.google.iam.v1.Binding.newBuilder() + .setRole("roles/bigtable.user") + .addMembers("user:someone@example.com") + ) + .setEtag(ByteString.copyFromUtf8("my-etag")) + .build(); + + Mockito.when(mockGetIamPolicyCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + Policy actualResult = adminClient.getIamPolicy(INSTANCE_NAME.getInstance()); + + // Verify + assertThat(actualResult).isEqualTo( + Policy.newBuilder() + .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com")) + .setEtag(BaseEncoding.base64().encode("my-etag".getBytes())) + .build() + ); + } + + @Test + public void testSetIamPolicy() { + // Setup + com.google.iam.v1.SetIamPolicyRequest expectedRequest = + com.google.iam.v1.SetIamPolicyRequest.newBuilder() + .setResource(INSTANCE_NAME.toString()) + .setPolicy( + com.google.iam.v1.Policy.newBuilder() + .addBindings( + com.google.iam.v1.Binding.newBuilder() + .setRole("roles/bigtable.user") + .addMembers("user:someone@example.com") + ) + ) + .build(); + + com.google.iam.v1.Policy expectedResponse = + com.google.iam.v1.Policy.newBuilder() + .addBindings( + com.google.iam.v1.Binding.newBuilder() + .setRole("roles/bigtable.user") + .addMembers("user:someone@example.com") + ) + .setEtag(ByteString.copyFromUtf8("my-etag")) + .build(); + + Mockito.when(mockSetIamPolicyCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + Policy actualResult = adminClient.setIamPolicy(INSTANCE_NAME.getInstance(), + Policy.newBuilder() + .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com")) + .build()); + + // Verify + assertThat(actualResult).isEqualTo( + Policy.newBuilder() + .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com")) + .setEtag(BaseEncoding.base64().encode("my-etag".getBytes())) + .build() + ); + } + + @Test + public void testTestIamPermissions() { + // Setup + com.google.iam.v1.TestIamPermissionsRequest expectedRequest = + com.google.iam.v1.TestIamPermissionsRequest.newBuilder() + .setResource(INSTANCE_NAME.toString()) + .addPermissions("bigtable.tables.readRows") + .build(); + + com.google.iam.v1.TestIamPermissionsResponse expectedResponse = + com.google.iam.v1.TestIamPermissionsResponse.newBuilder() + .addPermissions("bigtable.tables.readRows") + .build(); + + Mockito.when(mockTestIamPermissionsCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + List actualResult = adminClient.testIamPermission(INSTANCE_NAME.getInstance(), + "bigtable.tables.readRows"); + + // Verify + assertThat(actualResult).containsExactly("bigtable.tables.readRows"); + } } From 712f29b5e9509184835ceb8f096627550b4beca6 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Tue, 4 Sep 2018 13:59:01 -0400 Subject: [PATCH 3/3] fixups --- .../cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java index 381e9f76be60..24b8bfc5f8ef 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java @@ -1028,7 +1028,6 @@ public Policy getIamPolicy(String instanceId) { *
{@code
    * ApiFuture policyFuture = client.getIamPolicyAsync("my-instance");
    *
-   *
    * ApiFutures.addCallback(policyFuture,
    *   new ApiFutureCallback() {
    *     public void onSuccess(Policy policy) {