diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java
index f9ddf6872414..de5c430687ec 100644
--- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java
+++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java
@@ -22,6 +22,7 @@
import com.google.gcloud.Service;
import com.google.gcloud.spi.ResourceManagerRpc;
+import java.util.List;
import java.util.Set;
/**
@@ -168,7 +169,34 @@ public static ProjectListOption fields(ProjectField... fields) {
}
/**
- * Creates a new project.
+ * The permissions associated with a Google Cloud project. These values can be used when calling
+ * {@link #testPermissions}.
+ */
+ public enum Permission {
+ CREATE("create"),
+ DELETE("delete"),
+ GET("get"),
+ GET_POLICY("getIamPolicy"),
+ LIST("list"),
+ OWN("own"),
+ REPLACE("update"),
+ REPLACE_POLICY("setIamPolicy"),
+ SET_BILLING("setBillingAccount"),
+ UNDELETE("undelete");
+
+ private final String strValue;
+
+ Permission(String suffix) {
+ this.strValue = "resourcemanager.projects." + suffix;
+ }
+
+ String strValue() {
+ return strValue;
+ }
+ }
+
+ /**
+ * Create a new project.
*
*
Initially, the project resource is owned by its creator exclusively. The creator can later
* grant permission to others to read or update the project. Several APIs are activated
@@ -263,4 +291,72 @@ public static ProjectListOption fields(ProjectField... fields) {
* @throws ResourceManagerException upon failure
*/
void undelete(String projectId);
+
+ /**
+ * Returns the IAM access control policy for the specified project. Returns null if the resource
+ * does not exist or if you do not have adequate permission to view the project.
+ *
+ * @see
+ * Resource Manager getIamPolicy
+ * @throws ResourceManagerException upon failure
+ */
+ Policy getPolicy(String projectId);
+
+ /**
+ * Sets the IAM access control policy for the specified project. Replaces any existing policy. The
+ * following constraints apply:
+ *
+ * - Projects currently support only user:{emailid} and serviceAccount:{emailid}
+ * members in a binding of a policy.
+ *
- To be added as an owner, a user must be invited via Cloud Platform console and must accept
+ * the invitation.
+ *
- Members cannot be added to more than one role in the same policy.
+ *
- There must be at least one owner who has accepted the Terms of Service (ToS) agreement in
+ * the policy. An attempt to set a policy that removes the last ToS-accepted owner from the
+ * policy will fail.
+ *
- Calling this method requires enabling the App Engine Admin API.
+ *
+ * Note: Removing service accounts from policies or changing their roles can render services
+ * completely inoperable. It is important to understand how the service account is being used
+ * before removing or updating its roles.
+ *
+ * It is recommended that you use the read-modify-write pattern. This pattern entails reading the
+ * project's current policy, updating it locally, and then sending the modified policy for
+ * writing. Cloud IAM solves the problem of conflicting processes simultaneously attempting to
+ * modify a policy by using the etag property. This property is used to verify whether the
+ * policy has changed since the last request. When you make a request to Cloud IAM with an etag
+ * value, Cloud IAM compares the etag value in the request with the existing etag value associated
+ * with the policy. It writes the policy only if the etag values match. If an etag is not
+ * provided, the policy is overwritten blindly.
+ *
+ * An example of using the read-write-modify pattern is as follows:
+ * {@code
+ * Policy currentPolicy = resourceManager.getPolicy("my-project-id");
+ * Policy modifiedPolicy =
+ * current.toBuilder().removeIdentity(Role.VIEWER, Identity.user("user@gmail.com"));
+ * Policy newPolicy = resourceManager.setPolicy("my-project-id", modified);
+ * }
+ *
+ *
+ * @see
+ * Resource Manager setIamPolicy
+ * @throw ResourceManagerException upon failure
+ */
+ Policy replacePolicy(String projectId, Policy newPolicy);
+
+ /**
+ * Returns the permissions that a caller has on the specified project. You typically don't call
+ * this method if you're using Google Cloud Platform directly to manage permissions. This method
+ * is intended for integration with your proprietary software, such as a customized graphical user
+ * interface. For example, the Cloud Platform Console tests IAM permissions internally to
+ * determine which UI should be available to the logged-in user.
+ *
+ * @see
+ * Resource Manager testIamPermissions
+ * @throw ResourceManagerException upon failure
+ */
+ List testPermissions(String projectId, List permissions);
}
diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java
index 4176b4e610ba..09f8a9bb9229 100644
--- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java
+++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java
@@ -23,6 +23,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gcloud.BaseService;
import com.google.gcloud.Page;
@@ -32,6 +33,7 @@
import com.google.gcloud.spi.ResourceManagerRpc;
import com.google.gcloud.spi.ResourceManagerRpc.Tuple;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -181,6 +183,61 @@ public Void call() {
}
}
+ @Override
+ public Policy getPolicy(final String projectId) {
+ try {
+ com.google.api.services.cloudresourcemanager.model.Policy answer =
+ runWithRetries(
+ new Callable() {
+ @Override
+ public com.google.api.services.cloudresourcemanager.model.Policy call() {
+ return resourceManagerRpc.getPolicy(projectId);
+ }
+ },
+ options().retryParams(),
+ EXCEPTION_HANDLER);
+ return answer == null ? null : Policy.fromPb(answer);
+ } catch (RetryHelperException e) {
+ throw ResourceManagerException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Policy replacePolicy(final String projectId, final Policy newPolicy) {
+ try {
+ return Policy.fromPb(runWithRetries(
+ new Callable() {
+ @Override
+ public com.google.api.services.cloudresourcemanager.model.Policy call() {
+ return resourceManagerRpc.replacePolicy(projectId, newPolicy.toPb());
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER));
+ } catch (RetryHelperException e) {
+ throw ResourceManagerException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public List testPermissions(final String projectId, final List permissions) {
+ try {
+ return runWithRetries(
+ new Callable>() {
+ @Override
+ public List call() {
+ return resourceManagerRpc.testPermissions(projectId,
+ Lists.transform(permissions, new Function() {
+ @Override
+ public String apply(Permission p) {
+ return p.strValue();
+ }
+ }));
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ } catch (RetryHelperException e) {
+ throw ResourceManagerException.translateAndThrow(e);
+ }
+ }
+
private Map optionMap(Option... options) {
Map temp = Maps.newEnumMap(ResourceManagerRpc.Option.class);
for (Option option : options) {
diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java
index a077eb6144a5..b8bfcb77b76c 100644
--- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java
+++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java
@@ -5,7 +5,12 @@
import static java.net.HttpURLConnection.HTTP_OK;
import com.google.api.client.json.JsonFactory;
+import com.google.api.services.cloudresourcemanager.model.Binding;
+import com.google.api.services.cloudresourcemanager.model.Policy;
import com.google.api.services.cloudresourcemanager.model.Project;
+import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest;
+import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsRequest;
+import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsResponse;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
@@ -29,11 +34,14 @@
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -45,7 +53,25 @@
* Utility to create a local Resource Manager mock for testing.
*
* The mock runs in a separate thread, listening for HTTP requests on the local machine at an
- * ephemeral port.
+ * ephemeral port. While this mock attempts to simulate the Cloud Resource Manager, there are some
+ * divergences in behavior. The following is a non-exhaustive list of some of those behavioral
+ * differences:
+ *
+ *
+ * - This mock assumes you have adequate permissions for any action. Related to this,
+ * testIamPermissions always indicates that the caller has all permissions listed in the
+ * request.
+ *
- IAM policies are set to an empty policy with version 0 (only legacy roles supported) upon
+ * project creation. The actual service will not have an empty list of bindings and may also
+ * set your version to 1.
+ *
- There is no input validation for the policy provided when replacing a policy.
+ *
- In this mock, projects never move from the DELETE_REQUESTED lifecycle state to
+ * DELETE_IN_PROGRESS without an explicit call to the utility method
+ * {@link #changeLifecycleState}. Similarly, a project is never completely removed without an
+ * explicit call to the utility method {@link #removeProject}.
+ *
- The messages in the error responses given by this mock do not necessarily match the messages
+ * given by the actual service.
+ *
*/
@SuppressWarnings("restriction")
public class LocalResourceManagerHelper {
@@ -61,6 +87,10 @@ public class LocalResourceManagerHelper {
private static final Pattern LIST_FIELDS_PATTERN =
Pattern.compile("(.*?)projects\\((.*?)\\)(.*?)");
private static final String[] NO_FIELDS = {};
+ private static final String PERMISSION_PREFIX = "resourcemanager.projects.";
+ private static final Set PERMISSION_SUFFIXES = ImmutableSet.of("create", "delete", "get",
+ "getIamPolicy", "list", "own", "setIamPolicy", "setBillingAccount", "testIamPolicy",
+ "undelete", "update");
static {
try {
@@ -77,6 +107,7 @@ public class LocalResourceManagerHelper {
private final HttpServer server;
private final ConcurrentSkipListMap projects = new ConcurrentSkipListMap<>();
+ private final Map policies = new HashMap<>();
private final int port;
private static class Response {
@@ -98,6 +129,7 @@ String body() {
}
private enum Error {
+ ABORTED(409, "global", "aborted", "ABORTED"),
ALREADY_EXISTS(409, "global", "alreadyExists", "ALREADY_EXISTS"),
PERMISSION_DENIED(403, "global", "forbidden", "PERMISSION_DENIED"),
FAILED_PRECONDITION(400, "global", "failedPrecondition", "FAILED_PRECONDITION"),
@@ -149,13 +181,7 @@ public void handle(HttpExchange exchange) {
try {
switch (requestMethod) {
case "POST":
- if (path.endsWith(":undelete")) {
- response = undelete(projectIdFromUri(path));
- } else {
- String requestBody =
- decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody());
- response = create(jsonFactory.fromString(requestBody, Project.class));
- }
+ response = handlePost(exchange, path);
break;
case "DELETE":
response = delete(projectIdFromUri(path));
@@ -186,6 +212,30 @@ public void handle(HttpExchange exchange) {
}
}
+ private Response handlePost(HttpExchange exchange, String path) throws IOException {
+ String requestBody = decodeContent(exchange.getRequestHeaders(), exchange.getRequestBody());
+ if (!path.contains(":")) {
+ return create(jsonFactory.fromString(requestBody, Project.class));
+ } else {
+ switch (path.split(":", 2)[1]) {
+ case "undelete":
+ return undelete(projectIdFromUri(path));
+ case "getIamPolicy":
+ return getPolicy(projectIdFromUri(path));
+ case "setIamPolicy":
+ return replacePolicy(projectIdFromUri(path),
+ jsonFactory.fromString(requestBody, SetIamPolicyRequest.class).getPolicy());
+ case "testIamPermissions":
+ return testPermissions(projectIdFromUri(path),
+ jsonFactory.fromString(requestBody, TestIamPermissionsRequest.class)
+ .getPermissions());
+ default:
+ return Error.BAD_REQUEST.response(
+ "The server could not understand the following request URI: POST " + path);
+ }
+ }
+ }
+
private static void writeResponse(HttpExchange exchange, Response response) {
exchange.getResponseHeaders().set("Content-type", "application/json; charset=UTF-8");
OutputStream outputStream = exchange.getResponseBody();
@@ -316,7 +366,7 @@ private static boolean isValidIdOrLabel(String value, int minLength, int maxLeng
return value.length() >= minLength && value.length() <= maxLength;
}
- Response create(Project project) {
+ synchronized Response create(Project project) {
String customErrorMessage = checkForProjectErrors(project);
if (customErrorMessage != null) {
return Error.INVALID_ARGUMENT.response(customErrorMessage);
@@ -328,6 +378,11 @@ Response create(Project project) {
return Error.ALREADY_EXISTS.response(
"A project with the same project ID (" + project.getProjectId() + ") already exists.");
}
+ Policy emptyPolicy = new Policy()
+ .setBindings(Collections.emptyList())
+ .setEtag(UUID.randomUUID().toString())
+ .setVersion(0);
+ policies.put(project.getProjectId(), emptyPolicy);
try {
String createdProjectStr = jsonFactory.toString(project);
return new Response(HTTP_OK, createdProjectStr);
@@ -539,6 +594,60 @@ synchronized Response undelete(String projectId) {
return response;
}
+ synchronized Response getPolicy(String projectId) {
+ Policy policy = policies.get(projectId);
+ if (policy == null) {
+ return Error.PERMISSION_DENIED.response("Project " + projectId + " not found.");
+ }
+ try {
+ return new Response(HTTP_OK, jsonFactory.toString(policy));
+ } catch (IOException e) {
+ return Error.INTERNAL_ERROR.response(
+ "Error when serializing the IAM policy for " + projectId);
+ }
+ }
+
+ synchronized Response replacePolicy(String projectId, Policy policy) {
+ Policy originalPolicy = policies.get(projectId);
+ if (originalPolicy == null) {
+ return Error.PERMISSION_DENIED.response("Error when replacing the policy for "
+ + projectId + " because the project was not found.");
+ }
+ String etag = policy.getEtag();
+ if (etag != null && !originalPolicy.getEtag().equals(etag)) {
+ return Error.ABORTED.response("Policy etag mismatch when replacing the policy for project "
+ + projectId + ", please retry the read.");
+ }
+ policy.setEtag(UUID.randomUUID().toString());
+ policy.setVersion(originalPolicy.getVersion());
+ policies.put(projectId, policy);
+ try {
+ return new Response(HTTP_OK, jsonFactory.toString(policy));
+ } catch (IOException e) {
+ return Error.INTERNAL_ERROR.response(
+ "Error when serializing the policy for project " + projectId);
+ }
+ }
+
+ Response testPermissions(String projectId, List permissions) {
+ if (!projects.containsKey(projectId)) {
+ return Error.PERMISSION_DENIED.response("Project " + projectId + " not found.");
+ }
+ for (String p : permissions) {
+ if (!p.startsWith(PERMISSION_PREFIX)
+ || !PERMISSION_SUFFIXES.contains(p.substring(PERMISSION_PREFIX.length()))) {
+ return Error.INVALID_ARGUMENT.response("Invalid permission: " + p);
+ }
+ }
+ try {
+ return new Response(HTTP_OK,
+ jsonFactory.toString(new TestIamPermissionsResponse().setPermissions(permissions)));
+ } catch (IOException e) {
+ return Error.INTERNAL_ERROR.response(
+ "Error when serializing permissions " + Arrays.asList(permissions));
+ }
+ }
+
private LocalResourceManagerHelper() {
try {
server = HttpServer.create(new InetSocketAddress(0), 0);
@@ -606,6 +715,7 @@ public synchronized boolean changeLifecycleState(String projectId, String lifecy
public synchronized boolean removeProject(String projectId) {
// Because this method is synchronized, any code that relies on non-atomic read/write operations
// should not fail if that code is also synchronized.
- return projects.remove(checkNotNull(projectId)) != null;
+ policies.remove(checkNotNull(projectId));
+ return projects.remove(projectId) != null;
}
}
diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java
index d30cd2df3627..fab14061d433 100644
--- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java
+++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/DefaultResourceManagerRpc.java
@@ -11,13 +11,22 @@
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.cloudresourcemanager.Cloudresourcemanager;
+import com.google.api.services.cloudresourcemanager.model.GetIamPolicyRequest;
import com.google.api.services.cloudresourcemanager.model.ListProjectsResponse;
+import com.google.api.services.cloudresourcemanager.model.Policy;
import com.google.api.services.cloudresourcemanager.model.Project;
+import com.google.api.services.cloudresourcemanager.model.SetIamPolicyRequest;
+import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsRequest;
+import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsResponse;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.gcloud.resourcemanager.ResourceManagerException;
import com.google.gcloud.resourcemanager.ResourceManagerOptions;
import java.io.IOException;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
public class DefaultResourceManagerRpc implements ResourceManagerRpc {
@@ -107,5 +116,50 @@ public Project replace(Project project) {
throw translate(ex);
}
}
-}
+ @Override
+ public Policy getPolicy(String projectId) throws ResourceManagerException {
+ try {
+ return resourceManager.projects()
+ .getIamPolicy(projectId, new GetIamPolicyRequest())
+ .execute();
+ } catch (IOException ex) {
+ ResourceManagerException translated = translate(ex);
+ if (translated.code() == HTTP_FORBIDDEN) {
+ // Service returns permission denied if policy doesn't exist.
+ return null;
+ } else {
+ throw translated;
+ }
+ }
+ }
+
+ @Override
+ public Policy replacePolicy(String projectId, Policy newPolicy) throws ResourceManagerException {
+ try {
+ return resourceManager.projects()
+ .setIamPolicy(projectId, new SetIamPolicyRequest().setPolicy(newPolicy)).execute();
+ } catch (IOException ex) {
+ throw translate(ex);
+ }
+ }
+
+ @Override
+ public List testPermissions(String projectId, List permissions)
+ throws ResourceManagerException {
+ try {
+ TestIamPermissionsResponse response = resourceManager.projects()
+ .testIamPermissions(
+ projectId, new TestIamPermissionsRequest().setPermissions(permissions))
+ .execute();
+ Set permissionsOwned = ImmutableSet.copyOf(response.getPermissions());
+ ImmutableList.Builder answer = ImmutableList.builder();
+ for (String p : permissions) {
+ answer.add(permissionsOwned.contains(p));
+ }
+ return answer.build();
+ } catch (IOException ex) {
+ throw translate(ex);
+ }
+ }
+}
diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java
index e1d72a6a373e..dd009c52b05e 100644
--- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java
+++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/spi/ResourceManagerRpc.java
@@ -16,9 +16,11 @@
package com.google.gcloud.spi;
+import com.google.api.services.cloudresourcemanager.model.Policy;
import com.google.api.services.cloudresourcemanager.model.Project;
import com.google.gcloud.resourcemanager.ResourceManagerException;
+import java.util.List;
import java.util.Map;
public interface ResourceManagerRpc {
@@ -121,5 +123,27 @@ public Y y() {
*/
Project replace(Project project);
+ /**
+ * Returns the IAM policy associated with a project.
+ *
+ * @throws ResourceManagerException upon failure
+ */
+ Policy getPolicy(String projectId);
+
+ /**
+ * Replaces the IAM policy associated with the given project ID.
+ *
+ * @throws ResourceManagerException upon failure
+ */
+ Policy replacePolicy(String projectId, Policy newPolicy);
+
+ /**
+ * Tests whether the caller has the given permissions. Returns a list of booleans corresponding to
+ * whether or not the user has the permission in the same position of input list.
+ *
+ * @throws ResourceManagerException upon failure
+ */
+ List testPermissions(String projectId, List permissions);
+
// TODO(ajaykannan): implement "Organization" functionality when available (issue #319)
}
diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java
index a3418fff98ab..3b140ccdb828 100644
--- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java
+++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java
@@ -2,11 +2,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.google.api.services.cloudresourcemanager.model.Binding;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gcloud.resourcemanager.testing.LocalResourceManagerHelper;
import com.google.gcloud.spi.DefaultResourceManagerRpc;
@@ -18,8 +21,10 @@
import org.junit.BeforeClass;
import org.junit.Test;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
public class LocalResourceManagerHelperTest {
@@ -45,7 +50,12 @@ public class LocalResourceManagerHelperTest {
.setLabels(ImmutableMap.of("k1", "v1", "k2", "v2"));
private static final com.google.api.services.cloudresourcemanager.model.Project
PROJECT_WITH_PARENT =
- copyFrom(COMPLETE_PROJECT).setProjectId("project-with-parent-id").setParent(PARENT);
+ copyFrom(COMPLETE_PROJECT).setProjectId("project-with-parent-id").setParent(PARENT);
+ private static final List BINDINGS = ImmutableList.of(
+ new Binding().setRole("roles/owner").setMembers(ImmutableList.of("user:me@gmail.com")),
+ new Binding().setRole("roles/viewer").setMembers(ImmutableList.of("group:group@gmail.com")));
+ private static final com.google.api.services.cloudresourcemanager.model.Policy POLICY =
+ new com.google.api.services.cloudresourcemanager.model.Policy().setBindings(BINDINGS);
@BeforeClass
public static void beforeClass() {
@@ -92,6 +102,13 @@ public void testCreate() {
assertNull(returnedProject.getParent());
assertNotNull(returnedProject.getProjectNumber());
assertNotNull(returnedProject.getCreateTime());
+ com.google.api.services.cloudresourcemanager.model.Policy policy =
+ rpc.getPolicy(PARTIAL_PROJECT.getProjectId());
+ assertEquals(Collections.EMPTY_LIST, policy.getBindings());
+ assertNotNull(policy.getEtag());
+ assertEquals(0, policy.getVersion().intValue());
+ rpc.replacePolicy(PARTIAL_PROJECT.getProjectId(), POLICY);
+ assertEquals(POLICY.getBindings(), rpc.getPolicy(PARTIAL_PROJECT.getProjectId()).getBindings());
try {
rpc.create(PARTIAL_PROJECT);
fail("Should fail, project already exists.");
@@ -99,6 +116,8 @@ public void testCreate() {
assertEquals(409, e.code());
assertTrue(e.getMessage().startsWith("A project with the same project ID")
&& e.getMessage().endsWith("already exists."));
+ assertEquals(
+ POLICY.getBindings(), rpc.getPolicy(PARTIAL_PROJECT.getProjectId()).getBindings());
}
returnedProject = rpc.create(PROJECT_WITH_PARENT);
compareReadWriteFields(PROJECT_WITH_PARENT, returnedProject);
@@ -609,6 +628,64 @@ public void testUndeleteWhenDeleteInProgress() {
}
}
+ @Test
+ public void testGetPolicy() {
+ assertNull(rpc.getPolicy("nonexistent-project"));
+ rpc.create(PARTIAL_PROJECT);
+ com.google.api.services.cloudresourcemanager.model.Policy policy =
+ rpc.getPolicy(PARTIAL_PROJECT.getProjectId());
+ assertEquals(Collections.EMPTY_LIST, policy.getBindings());
+ assertNotNull(policy.getEtag());
+ }
+
+ @Test
+ public void testReplacePolicy() {
+ try {
+ rpc.replacePolicy("nonexistent-project", POLICY);
+ fail("Project doesn't exist.");
+ } catch (ResourceManagerException e) {
+ assertEquals(403, e.code());
+ assertTrue(e.getMessage().contains("project was not found"));
+ }
+ rpc.create(PARTIAL_PROJECT);
+ com.google.api.services.cloudresourcemanager.model.Policy invalidPolicy =
+ new com.google.api.services.cloudresourcemanager.model.Policy().setEtag("wrong-etag");
+ try {
+ rpc.replacePolicy(PARTIAL_PROJECT.getProjectId(), invalidPolicy);
+ fail("Invalid etag.");
+ } catch (ResourceManagerException e) {
+ assertEquals(409, e.code());
+ assertTrue(e.getMessage().startsWith("Policy etag mismatch"));
+ }
+ String originalEtag = rpc.getPolicy(PARTIAL_PROJECT.getProjectId()).getEtag();
+ com.google.api.services.cloudresourcemanager.model.Policy newPolicy =
+ rpc.replacePolicy(PARTIAL_PROJECT.getProjectId(), POLICY);
+ assertEquals(POLICY.getBindings(), newPolicy.getBindings());
+ assertNotNull(newPolicy.getEtag());
+ assertNotEquals(originalEtag, newPolicy.getEtag());
+ }
+
+ @Test
+ public void testTestPermissions() {
+ List permissions = ImmutableList.of("resourcemanager.projects.get");
+ try {
+ rpc.testPermissions("nonexistent-project", permissions);
+ } catch (ResourceManagerException e) {
+ assertEquals(403, e.code());
+ assertEquals("Project nonexistent-project not found.", e.getMessage());
+ }
+ rpc.create(PARTIAL_PROJECT);
+ try {
+ rpc.testPermissions(PARTIAL_PROJECT.getProjectId(), ImmutableList.of("get"));
+ fail("Invalid permission.");
+ } catch (ResourceManagerException e) {
+ assertEquals(400, e.code());
+ assertEquals("Invalid permission: get", e.getMessage());
+ }
+ assertEquals(ImmutableList.of(true),
+ rpc.testPermissions(PARTIAL_PROJECT.getProjectId(), permissions));
+ }
+
@Test
public void testChangeLifecycleStatus() {
assertFalse(RESOURCE_MANAGER_HELPER.changeLifecycleState(
@@ -632,8 +709,10 @@ public void testChangeLifecycleStatus() {
public void testRemoveProject() {
assertFalse(RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId()));
rpc.create(COMPLETE_PROJECT);
+ assertNotNull(rpc.getPolicy(COMPLETE_PROJECT.getProjectId()));
assertTrue(RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.getProjectId()));
assertNull(rpc.get(COMPLETE_PROJECT.getProjectId(), EMPTY_RPC_OPTIONS));
+ assertNull(rpc.getPolicy(COMPLETE_PROJECT.getProjectId()));
}
private void compareReadWriteFields(
diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java
index 1bc233311a4d..68a70f65114a 100644
--- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java
+++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java
@@ -18,15 +18,19 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.gcloud.Identity;
import com.google.gcloud.Page;
import com.google.gcloud.resourcemanager.ProjectInfo.ResourceId;
+import com.google.gcloud.resourcemanager.ResourceManager.Permission;
import com.google.gcloud.resourcemanager.ResourceManager.ProjectField;
import com.google.gcloud.resourcemanager.ResourceManager.ProjectGetOption;
import com.google.gcloud.resourcemanager.ResourceManager.ProjectListOption;
@@ -43,6 +47,7 @@
import org.junit.rules.ExpectedException;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
public class ResourceManagerImplTest {
@@ -65,6 +70,10 @@ public class ResourceManagerImplTest {
.parent(PARENT)
.build();
private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of();
+ private static final Policy POLICY = Policy.builder()
+ .addBinding(Policy.Role.OWNER, Identity.user("me@gmail.com"))
+ .addBinding(Policy.Role.EDITOR, Identity.serviceAccount("serviceaccount@gmail.com"))
+ .build();
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -320,6 +329,57 @@ public void testUndelete() {
}
}
+ @Test
+ public void testGetPolicy() {
+ assertNull(RESOURCE_MANAGER.getPolicy(COMPLETE_PROJECT.projectId()));
+ RESOURCE_MANAGER.create(COMPLETE_PROJECT);
+ RESOURCE_MANAGER.replacePolicy(COMPLETE_PROJECT.projectId(), POLICY);
+ Policy retrieved = RESOURCE_MANAGER.getPolicy(COMPLETE_PROJECT.projectId());
+ assertEquals(POLICY.bindings(), retrieved.bindings());
+ assertNotNull(retrieved.etag());
+ assertEquals(0, retrieved.version().intValue());
+ }
+
+ @Test
+ public void testReplacePolicy() {
+ try {
+ RESOURCE_MANAGER.replacePolicy("nonexistent-project", POLICY);
+ fail("Project doesn't exist.");
+ } catch (ResourceManagerException e) {
+ assertEquals(403, e.code());
+ assertTrue(e.getMessage().endsWith("project was not found."));
+ }
+ RESOURCE_MANAGER.create(PARTIAL_PROJECT);
+ Policy oldPolicy = RESOURCE_MANAGER.getPolicy(PARTIAL_PROJECT.projectId());
+ RESOURCE_MANAGER.replacePolicy(PARTIAL_PROJECT.projectId(), POLICY);
+ try {
+ RESOURCE_MANAGER.replacePolicy(PARTIAL_PROJECT.projectId(), oldPolicy);
+ fail("Policy with an invalid etag didn't cause error.");
+ } catch (ResourceManagerException e) {
+ assertEquals(409, e.code());
+ assertTrue(e.getMessage().contains("Policy etag mismatch"));
+ }
+ String originalEtag = RESOURCE_MANAGER.getPolicy(PARTIAL_PROJECT.projectId()).etag();
+ Policy newPolicy = RESOURCE_MANAGER.replacePolicy(PARTIAL_PROJECT.projectId(), POLICY);
+ assertEquals(POLICY.bindings(), newPolicy.bindings());
+ assertNotNull(newPolicy.etag());
+ assertNotEquals(originalEtag, newPolicy.etag());
+ }
+
+ @Test
+ public void testTestPermissions() {
+ List permissions = ImmutableList.of(Permission.GET);
+ try {
+ RESOURCE_MANAGER.testPermissions("nonexistent-project", permissions);
+ } catch (ResourceManagerException e) {
+ assertEquals(403, e.code());
+ assertEquals("Project nonexistent-project not found.", e.getMessage());
+ }
+ RESOURCE_MANAGER.create(PARTIAL_PROJECT);
+ assertEquals(ImmutableList.of(true),
+ RESOURCE_MANAGER.testPermissions(PARTIAL_PROJECT.projectId(), permissions));
+ }
+
@Test
public void testRetryableException() {
ResourceManagerRpcFactory rpcFactoryMock = EasyMock.createMock(ResourceManagerRpcFactory.class);