> binding : bindings().entrySet()) {
com.google.api.services.cloudresourcemanager.model.Binding bindingPb =
new com.google.api.services.cloudresourcemanager.model.Binding();
- bindingPb.setRole(binding.getKey().strValue());
+ bindingPb.setRole(binding.getKey().value());
bindingPb.setMembers(
Lists.transform(
new ArrayList<>(binding.getValue()),
diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java
index 4d12a31274c0..46b142c5aa53 100644
--- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java
+++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java
@@ -157,10 +157,10 @@ public Project reload() {
* completes, the project is not retrievable by the {@link ResourceManager#get} and
* {@link ResourceManager#list} methods. The caller must have modify permissions for this project.
*
- * @see Cloud
- * Resource Manager delete
* @throws ResourceManagerException upon failure
+ * @see Cloud
+ * Resource Manager delete
*/
public void delete() {
resourceManager.delete(projectId());
@@ -174,10 +174,10 @@ public void delete() {
* state of {@link ProjectInfo.State#DELETE_IN_PROGRESS}, the project cannot be restored. The
* caller must have modify permissions for this project.
*
- * @see Cloud
- * Resource Manager undelete
* @throws ResourceManagerException upon failure (including when the project can't be restored)
+ * @see Cloud
+ * Resource Manager undelete
*/
public void undelete() {
resourceManager.undelete(projectId());
@@ -188,11 +188,11 @@ public void undelete() {
*
* The caller must have modify permissions for this project.
*
- * @see Cloud
- * Resource Manager update
* @return the Project representing the new project metadata
* @throws ResourceManagerException upon failure
+ * @see Cloud
+ * Resource Manager update
*/
public Project replace() {
return resourceManager.replace(this);
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 a463937f875c..f14d47f2a676 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
@@ -18,10 +18,12 @@
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
+import com.google.gcloud.IamPolicy;
import com.google.gcloud.Page;
import com.google.gcloud.Service;
import com.google.gcloud.resourcemanager.spi.ResourceManagerRpc;
+import java.util.List;
import java.util.Set;
/**
@@ -167,6 +169,38 @@ public static ProjectListOption fields(ProjectField... fields) {
}
}
+ /**
+ * The permissions associated with a Google Cloud project. These values can be used when calling
+ * {@link #testPermissions}.
+ *
+ * @see
+ * Project-level roles
+ */
+ enum Permission {
+ DELETE("delete"),
+ GET("get"),
+ GET_POLICY("getIamPolicy"),
+ REPLACE("update"),
+ REPLACE_POLICY("setIamPolicy"),
+ UNDELETE("undelete");
+
+ private static final String PREFIX = "resourcemanager.projects.";
+
+ private final String value;
+
+ Permission(String suffix) {
+ this.value = PREFIX + suffix;
+ }
+
+ /**
+ * Returns the string representation of the permission.
+ */
+ public String value() {
+ return value;
+ }
+ }
+
/**
* Creates a new project.
*
@@ -174,13 +208,13 @@ public static ProjectListOption fields(ProjectField... fields) {
* grant permission to others to read or update the project. Several APIs are activated
* automatically for the project, including Google Cloud Storage.
*
- * @see Cloud
- * Resource Manager create
* @return Project object representing the new project's metadata. The returned object will
* include the following read-only fields supplied by the server: project number, lifecycle
* state, and creation time.
* @throws ResourceManagerException upon failure
+ * @see Cloud
+ * Resource Manager create
*/
Project create(ProjectInfo project);
@@ -201,10 +235,10 @@ public static ProjectListOption fields(ProjectField... fields) {
* completes, the project is not retrievable by the {@link ResourceManager#get} and
* {@link ResourceManager#list} methods. The caller must have modify permissions for this project.
*
- * @see Cloud
- * Resource Manager delete
* @throws ResourceManagerException upon failure
+ * @see Cloud
+ * Resource Manager delete
*/
void delete(String projectId);
@@ -214,10 +248,9 @@ public static ProjectListOption fields(ProjectField... fields) {
*
Returns {@code null} if the project is not found or if the user doesn't have read
* permissions for the project.
*
- * @see Cloud
- * Resource Manager get
* @throws ResourceManagerException upon failure
+ * @see
+ * Cloud Resource Manager get
*/
Project get(String projectId, ProjectGetOption... options);
@@ -228,11 +261,11 @@ public static ProjectListOption fields(ProjectField... fields) {
* at the end of the list. Use {@link ProjectListOption} to filter this list, set page size, and
* set page tokens.
*
- * @see Cloud
- * Resource Manager list
* @return {@code Page}, a page of projects
* @throws ResourceManagerException upon failure
+ * @see Cloud
+ * Resource Manager list
*/
Page list(ProjectListOption... options);
@@ -241,11 +274,11 @@ public static ProjectListOption fields(ProjectField... fields) {
*
* The caller must have modify permissions for this project.
*
- * @see Cloud
- * Resource Manager update
* @return the Project representing the new project metadata
* @throws ResourceManagerException upon failure
+ * @see Cloud
+ * Resource Manager update
*/
Project replace(ProjectInfo newProject);
@@ -257,10 +290,98 @@ public static ProjectListOption fields(ProjectField... fields) {
* state of {@link ProjectInfo.State#DELETE_IN_PROGRESS}, the project cannot be restored. The
* caller must have modify permissions for this project.
*
- * @see Cloud
- * Resource Manager undelete
* @throws ResourceManagerException upon failure
+ * @see Cloud
+ * Resource Manager undelete
*/
void undelete(String projectId);
+
+ /**
+ * Returns the IAM access control policy for the specified project. Returns {@code null} if the
+ * resource does not exist or if you do not have adequate permission to view the project or get
+ * the policy.
+ *
+ * @throws ResourceManagerException upon failure
+ * @see
+ * Resource Manager getIamPolicy
+ */
+ 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 {@link IamPolicy#etag 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 the
+ * etags don't match, a {@code ResourceManagerException} is thrown, denoting that the server
+ * aborted update. 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.replacePolicy("my-project-id", modified);
+ * }
+ *
+ *
+ * @throws ResourceManagerException upon failure
+ * @see
+ * Resource Manager setIamPolicy
+ */
+ 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.
+ *
+ * @return A list of booleans representing whether the caller has the permissions specified (in
+ * the order of the given permissions)
+ * @throws ResourceManagerException upon failure
+ * @see
+ * Resource Manager testIamPermissions
+ */
+ List testPermissions(String projectId, List permissions);
+
+ /**
+ * 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.
+ *
+ * @return A list of booleans representing whether the caller has the permissions specified (in
+ * the order of the given permissions)
+ * @throws ResourceManagerException upon failure
+ * @see
+ * Resource Manager testIamPermissions
+ */
+ List testPermissions(String projectId, Permission first, Permission... others);
}
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 fb699dcb06f0..d9911b911f0b 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.resourcemanager.spi.ResourceManagerRpc;
import com.google.gcloud.resourcemanager.spi.ResourceManagerRpc.Tuple;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -55,8 +57,8 @@ public com.google.api.services.cloudresourcemanager.model.Project call() {
return resourceManagerRpc.create(project.toPb());
}
}, options().retryParams(), EXCEPTION_HANDLER));
- } catch (RetryHelperException e) {
- throw ResourceManagerException.translateAndThrow(e);
+ } catch (RetryHelperException ex) {
+ throw ResourceManagerException.translateAndThrow(ex);
}
}
@@ -70,8 +72,8 @@ public Void call() {
return null;
}
}, options().retryParams(), EXCEPTION_HANDLER);
- } catch (RetryHelperException e) {
- throw ResourceManagerException.translateAndThrow(e);
+ } catch (RetryHelperException ex) {
+ throw ResourceManagerException.translateAndThrow(ex);
}
}
@@ -87,8 +89,8 @@ public com.google.api.services.cloudresourcemanager.model.Project call() {
}
}, options().retryParams(), EXCEPTION_HANDLER);
return answer == null ? null : Project.fromPb(this, answer);
- } catch (RetryHelperException e) {
- throw ResourceManagerException.translateAndThrow(e);
+ } catch (RetryHelperException ex) {
+ throw ResourceManagerException.translateAndThrow(ex);
}
}
@@ -146,8 +148,8 @@ public Project apply(
});
return new PageImpl<>(
new ProjectPageFetcher(serviceOptions, cursor, optionsMap), cursor, projects);
- } catch (RetryHelperException e) {
- throw ResourceManagerException.translateAndThrow(e);
+ } catch (RetryHelperException ex) {
+ throw ResourceManagerException.translateAndThrow(ex);
}
}
@@ -161,8 +163,8 @@ public com.google.api.services.cloudresourcemanager.model.Project call() {
return resourceManagerRpc.replace(newProject.toPb());
}
}, options().retryParams(), EXCEPTION_HANDLER));
- } catch (RetryHelperException e) {
- throw ResourceManagerException.translateAndThrow(e);
+ } catch (RetryHelperException ex) {
+ throw ResourceManagerException.translateAndThrow(ex);
}
}
@@ -176,11 +178,69 @@ public Void call() {
return null;
}
}, options().retryParams(), EXCEPTION_HANDLER);
- } catch (RetryHelperException e) {
- throw ResourceManagerException.translateAndThrow(e);
+ } catch (RetryHelperException ex) {
+ throw ResourceManagerException.translateAndThrow(ex);
}
}
+ @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 ex) {
+ throw ResourceManagerException.translateAndThrow(ex);
+ }
+ }
+
+ @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 ex) {
+ throw ResourceManagerException.translateAndThrow(ex);
+ }
+ }
+
+ @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 permission) {
+ return permission.value();
+ }
+ }));
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ } catch (RetryHelperException ex) {
+ throw ResourceManagerException.translateAndThrow(ex);
+ }
+ }
+
+ @Override
+ public List testPermissions(String projectId, Permission first, Permission... others) {
+ return testPermissions(projectId, Lists.asList(first, others));
+ }
+
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/spi/DefaultResourceManagerRpc.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/spi/DefaultResourceManagerRpc.java
index 2ef0d8c65ff2..9f92ff545874 100644
--- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/spi/DefaultResourceManagerRpc.java
+++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/spi/DefaultResourceManagerRpc.java
@@ -1,5 +1,6 @@
package com.google.gcloud.resourcemanager.spi;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.gcloud.resourcemanager.spi.ResourceManagerRpc.Option.FIELDS;
import static com.google.gcloud.resourcemanager.spi.ResourceManagerRpc.Option.FILTER;
import static com.google.gcloud.resourcemanager.spi.ResourceManagerRpc.Option.PAGE_SIZE;
@@ -11,13 +12,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 +117,51 @@ 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(firstNonNull(response.getPermissions(), ImmutableList.of()));
+ 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/resourcemanager/spi/ResourceManagerRpc.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/spi/ResourceManagerRpc.java
index 54531edd5ed5..d6ec068a92a3 100644
--- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/spi/ResourceManagerRpc.java
+++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/spi/ResourceManagerRpc.java
@@ -16,9 +16,11 @@
package com.google.gcloud.resourcemanager.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.
+ *
+ * @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/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/testing/LocalResourceManagerHelper.java
index cda2dd1e00ea..8ddca18b6261 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;
@@ -13,6 +18,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteStreams;
import com.google.gcloud.AuthCredentials;
+import com.google.gcloud.resourcemanager.ResourceManager.Permission;
import com.google.gcloud.resourcemanager.ResourceManagerOptions;
import com.sun.net.httpserver.Headers;
@@ -30,11 +36,14 @@
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
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;
@@ -46,7 +55,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 {
@@ -62,8 +89,12 @@ public class LocalResourceManagerHelper {
private static final Pattern LIST_FIELDS_PATTERN =
Pattern.compile("(.*?)projects\\((.*?)\\)(.*?)");
private static final String[] NO_FIELDS = {};
+ private static final Set PERMISSIONS = new HashSet<>();
static {
+ for (Permission permission : Permission.values()) {
+ PERMISSIONS.add(permission.value());
+ }
try {
BASE_CONTEXT = new URI(CONTEXT);
} catch (URISyntaxException e) {
@@ -78,6 +109,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 {
@@ -99,6 +131,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"),
@@ -150,13 +183,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));
@@ -187,6 +214,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();
@@ -259,7 +310,7 @@ private static Map parseListOptions(String query) throws IOExcep
options.put("pageToken", argEntry[1]);
break;
case "pageSize":
- int pageSize = Integer.valueOf(argEntry[1]);
+ int pageSize = Integer.parseInt(argEntry[1]);
if (pageSize < 1) {
throw new IOException("Page size must be greater than 0.");
}
@@ -317,7 +368,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);
@@ -329,6 +380,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);
@@ -540,6 +596,58 @@ 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);
+ }
+ }
+
+ synchronized Response testPermissions(String projectId, List permissions) {
+ if (!projects.containsKey(projectId)) {
+ return Error.PERMISSION_DENIED.response("Project " + projectId + " not found.");
+ }
+ for (String p : permissions) {
+ if (!PERMISSIONS.contains(p)) {
+ 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 " + permissions);
+ }
+ }
+
private LocalResourceManagerHelper() {
try {
server = HttpServer.create(new InetSocketAddress(0), 0);
@@ -611,6 +719,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/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/LocalResourceManagerHelperTest.java
index c9b2970a4efa..829094816664 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.spi.DefaultResourceManagerRpc;
import com.google.gcloud.resourcemanager.spi.ResourceManagerRpc;
@@ -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.emptyList(), 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,65 @@ 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.emptyList(), 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);
+ fail("Nonexistent project.");
+ } 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 +710,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/PolicyTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java
index 05d1b85bdbed..e6d0105838b7 100644
--- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java
+++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/PolicyTest.java
@@ -17,9 +17,13 @@
package com.google.gcloud.resourcemanager;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
import com.google.common.collect.ImmutableSet;
import com.google.gcloud.Identity;
+import com.google.gcloud.resourcemanager.Policy.Role;
+import com.google.gcloud.resourcemanager.Policy.Role.Type;
import org.junit.Test;
@@ -33,8 +37,10 @@ public class PolicyTest {
private static final Identity GROUP = Identity.group("group@gmail.com");
private static final Identity DOMAIN = Identity.domain("google.com");
private static final Policy SIMPLE_POLICY = Policy.builder()
- .addBinding(Policy.Role.VIEWER, ImmutableSet.of(USER, SERVICE_ACCOUNT, ALL_USERS))
- .addBinding(Policy.Role.EDITOR, ImmutableSet.of(ALL_AUTH_USERS, GROUP, DOMAIN))
+ .addBinding(Role.owner(), ImmutableSet.of(USER))
+ .addBinding(Role.viewer(), ImmutableSet.of(ALL_USERS))
+ .addBinding(Role.editor(), ImmutableSet.of(ALL_AUTH_USERS, DOMAIN))
+ .addBinding(Role.rawRole("some-role"), ImmutableSet.of(SERVICE_ACCOUNT, GROUP))
.build();
private static final Policy FULL_POLICY =
new Policy.Builder(SIMPLE_POLICY.bindings(), "etag", 1).build();
@@ -50,4 +56,24 @@ public void testPolicyToAndFromPb() {
assertEquals(FULL_POLICY, Policy.fromPb(FULL_POLICY.toPb()));
assertEquals(SIMPLE_POLICY, Policy.fromPb(SIMPLE_POLICY.toPb()));
}
+
+ @Test
+ public void testRoleType() {
+ assertEquals(Type.OWNER, Role.owner().type());
+ assertEquals(Type.EDITOR, Role.editor().type());
+ assertEquals(Type.VIEWER, Role.viewer().type());
+ assertNull(Role.rawRole("raw-role").type());
+ }
+
+ @Test
+ public void testEquals() {
+ Policy copy = Policy.builder()
+ .addBinding(Role.owner(), ImmutableSet.of(USER))
+ .addBinding(Role.viewer(), ImmutableSet.of(ALL_USERS))
+ .addBinding(Role.editor(), ImmutableSet.of(ALL_AUTH_USERS, DOMAIN))
+ .addBinding(Role.rawRole("some-role"), ImmutableSet.of(SERVICE_ACCOUNT, GROUP))
+ .build();
+ assertEquals(SIMPLE_POLICY, copy);
+ assertNotEquals(SIMPLE_POLICY, FULL_POLICY);
+ }
}
diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java
index 4e239acc45ef..882ec77197f3 100644
--- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java
+++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertNull;
import com.google.common.collect.ImmutableMap;
+import com.google.gcloud.resourcemanager.ProjectInfo.ResourceId;
import org.junit.After;
import org.junit.Before;
@@ -84,12 +85,12 @@ public void testToBuilder() {
@Test
public void testBuilder() {
- initializeExpectedProject(4);
- expect(resourceManager.options()).andReturn(mockOptions).times(4);
+ expect(resourceManager.options()).andReturn(mockOptions).times(7);
replay(resourceManager);
Project.Builder builder =
- new Project.Builder(new Project(resourceManager, new ProjectInfo.BuilderImpl(PROJECT_ID)));
- Project project = builder.name(NAME)
+ new Project.Builder(new Project(resourceManager, new ProjectInfo.BuilderImpl("wrong-id")));
+ Project project = builder.projectId(PROJECT_ID)
+ .name(NAME)
.labels(LABELS)
.projectNumber(PROJECT_NUMBER)
.createTimeMillis(CREATE_TIME_MILLIS)
@@ -102,6 +103,23 @@ public void testBuilder() {
assertEquals(CREATE_TIME_MILLIS, project.createTimeMillis());
assertEquals(STATE, project.state());
assertEquals(resourceManager.options(), project.resourceManager().options());
+ assertNull(project.parent());
+ ResourceId parent = new ResourceId("id", "type");
+ project = project.toBuilder()
+ .clearLabels()
+ .addLabel("k3", "v3")
+ .addLabel("k4", "v4")
+ .removeLabel("k4")
+ .parent(parent)
+ .build();
+ assertEquals(PROJECT_ID, project.projectId());
+ assertEquals(NAME, project.name());
+ assertEquals(ImmutableMap.of("k3", "v3"), project.labels());
+ assertEquals(PROJECT_NUMBER, project.projectNumber());
+ assertEquals(CREATE_TIME_MILLIS, project.createTimeMillis());
+ assertEquals(STATE, project.state());
+ assertEquals(resourceManager.options(), project.resourceManager().options());
+ assertEquals(parent, project.parent());
}
@Test
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 5b172d6a070e..a69880c5d064 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,20 @@
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.Policy.Role;
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 +48,7 @@
import org.junit.rules.ExpectedException;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
public class ResourceManagerImplTest {
@@ -65,6 +71,10 @@ public class ResourceManagerImplTest {
.parent(PARENT)
.build();
private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of();
+ private static final Policy POLICY = Policy.builder()
+ .addBinding(Role.owner(), Identity.user("me@gmail.com"))
+ .addBinding(Role.editor(), Identity.serviceAccount("serviceaccount@gmail.com"))
+ .build();
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -320,6 +330,60 @@ 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);
+ fail("Nonexistent project");
+ } 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));
+ assertEquals(ImmutableList.of(true, true), RESOURCE_MANAGER.testPermissions(
+ PARTIAL_PROJECT.projectId(), Permission.DELETE, Permission.GET));
+ }
+
@Test
public void testRetryableException() {
ResourceManagerRpcFactory rpcFactoryMock = EasyMock.createMock(ResourceManagerRpcFactory.class);
diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java
index 35b72ae1713f..f71f5d7989d6 100644
--- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java
+++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java
@@ -56,7 +56,7 @@ public class SerializationTest {
private static final ResourceManager.ProjectListOption PROJECT_LIST_OPTION =
ResourceManager.ProjectListOption.filter("name:*");
private static final Policy POLICY = Policy.builder()
- .addBinding(Policy.Role.VIEWER, ImmutableSet.of(Identity.user("abc@gmail.com")))
+ .addBinding(Policy.Role.viewer(), ImmutableSet.of(Identity.user("abc@gmail.com")))
.build();
@Test