Configure the Graph RBAC API and allow mocking service endpoints
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeApi.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeApi.java
index afc8413..1f6b726 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeApi.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeApi.java
@@ -24,6 +24,7 @@
import org.jclouds.azurecompute.arm.features.AvailabilitySetApi;
import org.jclouds.azurecompute.arm.features.DeploymentApi;
import org.jclouds.azurecompute.arm.features.DiskApi;
+import org.jclouds.azurecompute.arm.features.GraphRBACApi;
import org.jclouds.azurecompute.arm.features.ImageApi;
import org.jclouds.azurecompute.arm.features.JobApi;
import org.jclouds.azurecompute.arm.features.LoadBalancerApi;
@@ -243,6 +244,15 @@
MetricDefinitionsApi getMetricsDefinitionsApi(@PathParam("resourceid") String resourceid);
/**
+ * The Azure Active Directory Graph API provides programmatic access to Azure
+ * AD through REST API endpoints.
+ *
+ * @see <a href="https://docs.microsoft.com/en-us/rest/api/graphrbac/">docs</a>
+ */
+ @Delegate
+ GraphRBACApi getGraphRBACApi();
+
+ /**
* Returns the information about the current service principal.
*/
@Provides
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java
index 6848167..335de98 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java
@@ -37,11 +37,11 @@
import java.net.URI;
import java.util.Properties;
-import org.jclouds.azurecompute.arm.config.AzureComputeHttpApiModule.CurrentServicePrincipal;
import org.jclouds.azurecompute.arm.domain.Region;
import org.jclouds.azurecompute.arm.features.AvailabilitySetApi;
import org.jclouds.azurecompute.arm.features.DeploymentApi;
import org.jclouds.azurecompute.arm.features.DiskApi;
+import org.jclouds.azurecompute.arm.features.GraphRBACApi;
import org.jclouds.azurecompute.arm.features.ImageApi;
import org.jclouds.azurecompute.arm.features.LoadBalancerApi;
import org.jclouds.azurecompute.arm.features.LocationApi;
@@ -125,7 +125,7 @@
properties.put(API_VERSION_PREFIX + MetricDefinitionsApi.class.getSimpleName(), "2017-05-01-preview");
properties.put(API_VERSION_PREFIX + MetricsApi.class.getSimpleName(), "2016-09-01");
properties.put(API_VERSION_PREFIX + VirtualMachineScaleSetApi.class.getSimpleName(), "2017-03-30");
- properties.put(API_VERSION_PREFIX + CurrentServicePrincipal.class.getSimpleName(), "1.6");
+ properties.put(API_VERSION_PREFIX + GraphRBACApi.class.getSimpleName(), "1.6");
return properties;
}
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureManagementApiMetadata.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureManagementApiMetadata.java
index d0c3e21..9c73e99 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureManagementApiMetadata.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureManagementApiMetadata.java
@@ -16,6 +16,8 @@
*/
package org.jclouds.azurecompute.arm;
+import static org.jclouds.reflect.Reflection2.typeToken;
+
import java.net.URI;
import java.util.Properties;
@@ -32,8 +34,6 @@
import com.google.common.collect.ImmutableSet;
import com.google.inject.Module;
-import static org.jclouds.reflect.Reflection2.typeToken;
-
/**
* Implementation of {@link ApiMetadata} for Microsoft Azure Resource Manager REST API
*/
@@ -43,9 +43,13 @@
public Builder toBuilder() {
return new Builder().fromApiMetadata(this);
}
+
+ public static Builder builder() {
+ return new Builder();
+ }
public AzureManagementApiMetadata() {
- this(new Builder());
+ this(builder());
}
protected AzureManagementApiMetadata(final Builder builder) {
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeHttpApiModule.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeHttpApiModule.java
index 8d417ca..7ba23db 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeHttpApiModule.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeHttpApiModule.java
@@ -17,7 +17,6 @@
package org.jclouds.azurecompute.arm.config;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
-import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
import java.net.URI;
import java.util.concurrent.TimeUnit;
@@ -26,14 +25,10 @@
import java.util.regex.Pattern;
import javax.inject.Singleton;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.core.MediaType;
import org.jclouds.azurecompute.arm.AzureComputeApi;
+import org.jclouds.azurecompute.arm.config.GraphRBAC.GraphRBACForTenant;
import org.jclouds.azurecompute.arm.domain.ServicePrincipal;
-import org.jclouds.azurecompute.arm.filters.ApiVersionFilter;
import org.jclouds.azurecompute.arm.handlers.AzureComputeErrorHandler;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.annotation.ClientError;
@@ -43,28 +38,22 @@
import org.jclouds.location.suppliers.implicit.FirstRegion;
import org.jclouds.oauth.v2.config.OAuthConfigFactory;
import org.jclouds.oauth.v2.config.OAuthScopes;
-import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ConfiguresHttpApi;
-import org.jclouds.rest.annotations.Endpoint;
-import org.jclouds.rest.annotations.OnlyElement;
-import org.jclouds.rest.annotations.QueryParams;
-import org.jclouds.rest.annotations.RequestFilters;
-import org.jclouds.rest.annotations.SelectJson;
import org.jclouds.rest.config.HttpApiModule;
import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier;
import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
import com.google.inject.Provides;
import com.google.inject.Scopes;
+import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
@ConfiguresHttpApi
public class AzureComputeHttpApiModule extends HttpApiModule<AzureComputeApi> {
private static final Pattern OAUTH_TENANT_PATTERN = Pattern
- .compile("https://login.microsoftonline.com/([^/]+)/oauth2/token");
+ .compile("https://login.microsoft(?:online)?.com/([^/]+)/oauth2/token");
@Override
protected void bindErrorHandlers() {
@@ -82,15 +71,20 @@
@Override
protected void configure() {
super.configure();
- bindHttpApi(binder(), CurrentServicePrincipal.class);
bind(OAuthScopes.class).toInstance(OAuthScopes.NoScopes.create());
bind(OAuthConfigFactory.class).to(AzureOAuthConfigFactory.class).in(Scopes.SINGLETON);
+ bindServiceEndpoints();
+ }
+
+ protected void bindServiceEndpoints() {
+ bind(new TypeLiteral<Supplier<URI>>() {
+ }).annotatedWith(GraphRBAC.class).to(GraphRBACForTenant.class).in(Scopes.SINGLETON);
}
@Provides
@Singleton
@Tenant
- protected String provideTenant(@Named("oauth.endpoint") final String oauthEndpoint) {
+ protected final String provideTenant(@Named("oauth.endpoint") final String oauthEndpoint) {
Matcher m = OAUTH_TENANT_PATTERN.matcher(oauthEndpoint);
if (!m.matches()) {
throw new IllegalArgumentException("Could not parse tenantId from: " + oauthEndpoint);
@@ -100,37 +94,15 @@
@Provides
@Singleton
- @GraphRBAC
- protected Supplier<URI> graphRBACEndpoint(@Tenant String tenantId) {
- return Suppliers.ofInstance(URI.create(GraphRBAC.ENDPOINT + tenantId));
- }
-
- @Provides
- @Singleton
- protected Supplier<ServicePrincipal> provideServicePrincipal(final CurrentServicePrincipal currentServicePrincipal,
+ protected final Supplier<ServicePrincipal> provideServicePrincipal(final AzureComputeApi api,
AtomicReference<AuthorizationException> authException, @Named(PROPERTY_SESSION_INTERVAL) long seconds) {
// This supplier must be defensive against any auth exception.
return MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(authException,
new Supplier<ServicePrincipal>() {
@Override
public ServicePrincipal get() {
- return currentServicePrincipal.get();
+ return api.getGraphRBACApi().getCurrentServicePrincipal();
}
}, seconds, TimeUnit.SECONDS);
}
-
- @RequestFilters({ OAuthFilter.class, ApiVersionFilter.class })
- @Consumes(MediaType.APPLICATION_JSON)
- @Endpoint(GraphRBAC.class)
- @OAuthResource(GraphRBAC.ENDPOINT)
- public interface CurrentServicePrincipal {
-
- @Named("servicePrincipal:get")
- @GET
- @Path("/servicePrincipals")
- @QueryParams(keys = "$filter", values = "appId eq '{jclouds.identity}'")
- @SelectJson("value")
- @OnlyElement
- ServicePrincipal get();
- }
}
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/GraphRBAC.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/GraphRBAC.java
index 6853782..a7f8b4f 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/GraphRBAC.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/GraphRBAC.java
@@ -20,9 +20,13 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.net.URI;
+import javax.inject.Inject;
import javax.inject.Qualifier;
+import com.google.common.base.Supplier;
+
/**
* Provides the Graph RBAC API endpoint for the current tenant.
*/
@@ -32,4 +36,19 @@
public @interface GraphRBAC {
String ENDPOINT = "https://graph.windows.net/";
+
+ static class GraphRBACForTenant implements Supplier<URI> {
+ private final String tenantId;
+
+ @Inject
+ GraphRBACForTenant(@Tenant String tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public URI get() {
+ return URI.create(GraphRBAC.ENDPOINT + tenantId);
+ }
+
+ }
}
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/GraphRBACApi.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/GraphRBACApi.java
new file mode 100644
index 0000000..fe2bccc
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/GraphRBACApi.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.azurecompute.arm.features;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.azurecompute.arm.config.GraphRBAC;
+import org.jclouds.azurecompute.arm.config.OAuthResource;
+import org.jclouds.azurecompute.arm.domain.ServicePrincipal;
+import org.jclouds.azurecompute.arm.filters.ApiVersionFilter;
+import org.jclouds.oauth.v2.filters.OAuthFilter;
+import org.jclouds.rest.annotations.Endpoint;
+import org.jclouds.rest.annotations.OnlyElement;
+import org.jclouds.rest.annotations.QueryParams;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+import com.google.inject.name.Named;
+
+@RequestFilters({ OAuthFilter.class, ApiVersionFilter.class })
+@Consumes(MediaType.APPLICATION_JSON)
+@Endpoint(GraphRBAC.class)
+@OAuthResource(GraphRBAC.ENDPOINT)
+public interface GraphRBACApi {
+
+ @Named("servicePrincipal:get")
+ @GET
+ @Path("/servicePrincipals")
+ @QueryParams(keys = "$filter", values = "appId eq '{jclouds.identity}'")
+ @SelectJson("value")
+ @OnlyElement
+ ServicePrincipal getCurrentServicePrincipal();
+}
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/config/ParseTenantIdTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/config/ParseTenantIdTest.java
new file mode 100644
index 0000000..5894505
--- /dev/null
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/config/ParseTenantIdTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.azurecompute.arm.config;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "ParseTenantIdTest")
+public class ParseTenantIdTest {
+
+ @Test
+ public void testParseTenantId() {
+ AzureComputeHttpApiModule module = new AzureComputeHttpApiModule();
+
+ assertEquals(module.provideTenant("https://login.microsoftonline.com/tenantId/oauth2/token"), "tenantId");
+ assertEquals(module.provideTenant("https://login.microsoft.com/tenant2/oauth2/token"), "tenant2");
+
+ assertInvalid(module, "https://login.microsoftonline.com/a/b/c/oauth2/token");
+ assertInvalid(module, "https://login.microsoft.com/a/b/c/oauth2/token");
+ assertInvalid(module, "https://login.microsoftonline.com//oauth2/token");
+ assertInvalid(module, "https://login.microsoft.com//oauth2/token");
+ assertInvalid(module, "https://login.microsoftabc.com/tenant/oauth2/token");
+ }
+
+ private static void assertInvalid(AzureComputeHttpApiModule module, String endpoint) {
+ try {
+ module.provideTenant(endpoint);
+ fail("Expected an IllegalArgumentException for endpoint: " + endpoint);
+ } catch (IllegalArgumentException ex) {
+ assertEquals(ex.getMessage(), "Could not parse tenantId from: " + endpoint);
+ }
+ }
+
+}
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/GraphRBACApiMockTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/GraphRBACApiMockTest.java
new file mode 100644
index 0000000..20b95e2
--- /dev/null
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/GraphRBACApiMockTest.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.azurecompute.arm.features;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.azurecompute.arm.domain.ServicePrincipal;
+import org.jclouds.azurecompute.arm.internal.BaseAzureComputeApiMockTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "GraphRBACApiMockTest", singleThreaded = true)
+public class GraphRBACApiMockTest extends BaseAzureComputeApiMockTest {
+
+ public void testGetCurrentServicePrincipal() throws IOException, InterruptedException {
+ server.enqueue(jsonResponse("/serviceprincipals.json"));
+
+ ServicePrincipal sp = api.getGraphRBACApi().getCurrentServicePrincipal();
+
+ assertEquals(sp.appId(), "applicationId");
+ assertSent(server, "GET", "/graphrbac/tenant-id/servicePrincipals?$filter=appId%20eq%20%27mock%27&api-version=1.6");
+ }
+
+}
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineScaleSetApiLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineScaleSetApiLiveTest.java
index d827d70..ff93998 100644
--- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineScaleSetApiLiveTest.java
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineScaleSetApiLiveTest.java
@@ -17,7 +17,6 @@
package org.jclouds.azurecompute.arm.features;
import com.google.common.collect.ImmutableMap;
-import org.jclouds.azurecompute.arm.domain.DataDisk;
import org.jclouds.azurecompute.arm.domain.Extension;
import org.jclouds.azurecompute.arm.domain.ExtensionProfile;
import org.jclouds.azurecompute.arm.domain.ExtensionProfileSettings;
@@ -69,8 +68,6 @@
private String subscriptionid;
private String vmssName;
- private VirtualMachineScaleSetSKU SKU;
- private String nicName;
private String virtualNetworkName;
private String subnetId;
private Subnet subnet;
@@ -151,25 +148,6 @@
return VirtualMachineScaleSetUpgradePolicy.create("Manual");
}
- private List<DataDisk> getDataDisks() {
- List<DataDisk> datadisks = new ArrayList<DataDisk>();
-
- datadisks.add(DataDisk.create(null, "10", 1, null,
- null, "FromImage",
- "None", getManagedDiskParameters(),
- null));
-
- return datadisks;
- }
-
- private StorageProfile getStorageProfile() {
- return StorageProfile.create(getWindowsImageReference(), getWindowsOSDisk(), getDataDisks());
- }
-
- private StorageProfile getWindowsStorageProfile_Default() {
- return StorageProfile.create(getWindowsImageReference(), getWindowsOSDisk(), null);
- }
-
private StorageProfile getLinuxStorageProfile_Default() {
return StorageProfile.create(getLinuxImageReference(), getLinuxOSDisk(), null);
}
@@ -178,21 +156,11 @@
return ManagedDiskParameters.create(null, "Standard_LRS");
}
- private OSDisk getWindowsOSDisk() {
- return OSDisk.create("Windows", null, null, null, "FromImage",
- null, getManagedDiskParameters(), null);
- }
-
private OSDisk getLinuxOSDisk() {
return OSDisk.create("Linux", null, null, null, "FromImage",
null, getManagedDiskParameters(), null);
}
- private ImageReference getWindowsImageReference() {
- return ImageReference.create(null, "Microsoft.Windows", "Windows2016",
- "Enterprise", "latest");
- }
-
private ImageReference getLinuxImageReference() {
return ImageReference.create(null, "Canonical", "UbuntuServer",
"16.04-LTS", "latest");
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiMockTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiMockTest.java
index 1a83407..9d5eab0 100644
--- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiMockTest.java
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiMockTest.java
@@ -15,30 +15,44 @@
* limitations under the License.
*/
package org.jclouds.azurecompute.arm.internal;
+
+import static com.google.common.base.Predicates.not;
+import static com.google.common.collect.Iterables.filter;
import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
+import static org.assertj.core.util.Sets.newHashSet;
import static org.jclouds.oauth.v2.config.CredentialType.BEARER_TOKEN_CREDENTIALS;
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
+import java.net.URI;
import java.util.Properties;
import java.util.Set;
import org.jclouds.ContextBuilder;
import org.jclouds.azurecompute.arm.AzureComputeApi;
import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata;
+import org.jclouds.azurecompute.arm.AzureManagementApiMetadata;
+import org.jclouds.azurecompute.arm.config.AzureComputeHttpApiModule;
+import org.jclouds.azurecompute.arm.config.GraphRBAC;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.date.DateService;
+import org.jclouds.providers.ProviderMetadata;
import org.jclouds.rest.ApiContext;
+import org.jclouds.rest.ConfiguresHttpApi;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import com.google.common.base.Charsets;
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources;
import com.google.gson.JsonParser;
import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
@@ -48,8 +62,6 @@
private static final String MOCK_BEARER_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9";
private static final String DEFAULT_ENDPOINT = new AzureComputeProviderMetadata().getEndpoint();
- private final Set<Module> modules = ImmutableSet.<Module> of(new ExecutorServiceModule(sameThreadExecutor()));
-
protected MockWebServer server;
protected AzureComputeApi api;
protected ApiContext<AzureComputeApi> context;
@@ -62,25 +74,42 @@
public void start() throws IOException {
server = new MockWebServer();
server.play();
- AzureComputeProviderMetadata pm = AzureComputeProviderMetadata.builder().build();
- context = ContextBuilder.newBuilder(pm)
- .credentials("", MOCK_BEARER_TOKEN)
+
+ context = ContextBuilder.newBuilder(testProviderMetadata())
+ .credentials("mock", MOCK_BEARER_TOKEN)
.endpoint(server.getUrl("/").toString() + "subscriptions/SUBSCRIPTIONID")
- .modules(modules)
+ .modules(setupModules())
.overrides(setupProperties())
.build();
api = context.getApi();
dateService = context.utils().injector().getInstance(DateService.class);
}
+ protected ProviderMetadata testProviderMetadata() {
+ // Omit the default HTTP API modules to allow overriding
+ Set<Class<? extends Module>> defaultModules = newHashSet(filter(
+ new AzureManagementApiMetadata().getDefaultModules(),
+ not(Predicates.<Class<? extends Module>> equalTo(AzureComputeHttpApiModule.class))));
+ return AzureComputeProviderMetadata.builder()
+ .apiMetadata(AzureManagementApiMetadata.builder().defaultModules(defaultModules).build()).build();
+ }
+
protected Properties setupProperties() {
Properties properties = new Properties();
-
properties.put(CREDENTIAL_TYPE, BEARER_TOKEN_CREDENTIALS.toString());
properties.put("oauth.endpoint", "https://login.microsoftonline.com/tenant-id/oauth2/token");
return properties;
}
+ protected Set<Module> setupModules() {
+ ImmutableSet.Builder<Module> modules = ImmutableSet.builder();
+ modules.add(new ExecutorServiceModule(sameThreadExecutor()));
+ // Override the default HTTP module to accomodate custom bindings for the
+ // hardcoded endpoints such as the Graph RBAC API one.
+ modules.add(new TestAzureComputeHttpApiModule(server));
+ return modules.build();
+ }
+
@AfterMethod(alwaysRun = true)
public void stop() throws IOException {
server.shutdown();
@@ -107,21 +136,22 @@
return new MockResponse().setStatus("HTTP/1.1 202 Accepted");
}
-
protected MockResponse response204() {
return new MockResponse().setStatus("HTTP/1.1 204 No Content");
}
protected MockResponse response202WithHeader() {
return new MockResponse()
- .setStatus("HTTP/1.1 202 Accepted")
- .addHeader("Location", "https://management.azure.com/subscriptions/SUBSCRIPTIONID/operationresults/eyJqb2JJZCI6IlJFU09VUkNFR1JPVVBERUxFVElPTkpPQi1SVEVTVC1DRU5UUkFMVVMiLCJqb2JMb2NhdGlvbiI6ImNlbnRyYWx1cyJ9?api-version=2014-04-01");
+ .setStatus("HTTP/1.1 202 Accepted")
+ .addHeader(
+ "Location",
+ "https://management.azure.com/subscriptions/SUBSCRIPTIONID/operationresults/eyJqb2JJZCI6IlJFU09VUkNFR1JPVVBERUxFVElPTkpPQi1SVEVTVC1DRU5UUkFMVVMiLCJqb2JMb2NhdGlvbiI6ImNlbnRyYWx1cyJ9?api-version=2014-04-01");
}
protected String stringFromResource(String resourceName) {
try {
- return Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8)
- .replace(DEFAULT_ENDPOINT, url(""));
+ return Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8).replace(DEFAULT_ENDPOINT,
+ url(""));
} catch (IOException e) {
throw Throwables.propagate(e);
}
@@ -137,10 +167,27 @@
}
protected RecordedRequest assertSent(MockWebServer server, String method, String path, String json)
- throws InterruptedException {
+ throws InterruptedException {
RecordedRequest request = assertSent(server, method, path);
assertEquals(request.getHeader("Content-Type"), "application/json");
assertEquals(parser.parse(new String(request.getBody(), Charsets.UTF_8)), parser.parse(json));
return request;
}
+
+ @ConfiguresHttpApi
+ private static class TestAzureComputeHttpApiModule extends AzureComputeHttpApiModule {
+ private final MockWebServer server;
+
+ public TestAzureComputeHttpApiModule(MockWebServer server) {
+ this.server = server;
+ }
+
+ @Override
+ protected void bindServiceEndpoints() {
+ // Override the hardcoded service URIs to allow mocking service endpoints
+ bind(new TypeLiteral<Supplier<URI>>() {
+ }).annotatedWith(GraphRBAC.class).toInstance(
+ Suppliers.ofInstance(URI.create(server.getUrl("/graphrbac").toString() + "/tenant-id")));
+ }
+ }
}
diff --git a/azurecompute-arm/src/test/resources/serviceprincipals.json b/azurecompute-arm/src/test/resources/serviceprincipals.json
new file mode 100644
index 0000000..cdeefa6
--- /dev/null
+++ b/azurecompute-arm/src/test/resources/serviceprincipals.json
@@ -0,0 +1,53 @@
+{
+ "odata.metadata": "https://graph.windows.net/tenantId/$metadata#directoryObjects/Microsoft.DirectoryServices.ServicePrincipal",
+ "value": [
+ {
+ "odata.type": "Microsoft.DirectoryServices.ServicePrincipal",
+ "objectType": "ServicePrincipal",
+ "objectId": "objectId",
+ "deletionTimestamp": null,
+ "accountEnabled": true,
+ "addIns": [],
+ "alternativeNames": [],
+ "appDisplayName": "jclouds",
+ "appId": "applicationId",
+ "appOwnerTenantId": "tenantId",
+ "appRoleAssignmentRequired": false,
+ "appRoles": [],
+ "displayName": "jclouds",
+ "errorUrl": null,
+ "homepage": "https://jclouds.apache.org",
+ "keyCredentials": [],
+ "logoutUrl": null,
+ "oauth2Permissions": [
+ {
+ "adminConsentDescription": "Allow the application to access jclouds on behalf of the signed-in user.",
+ "adminConsentDisplayName": "Access jclouds",
+ "id": "id",
+ "isEnabled": true,
+ "type": "User",
+ "userConsentDescription": "Allow the application to access jclouds on your behalf.",
+ "userConsentDisplayName": "Access jclouds",
+ "value": "user_impersonation"
+ }
+ ],
+ "passwordCredentials": [],
+ "preferredTokenSigningKeyThumbprint": null,
+ "publisherName": "Default Directory",
+ "replyUrls": [
+ "https://jclouds.apache.org"
+ ],
+ "samlMetadataUrl": null,
+ "servicePrincipalNames": [
+ "https://jclouds.onmicrosoft.com/jcloudsid",
+ "applicationId"
+ ],
+ "servicePrincipalType": "Application",
+ "tags": [
+ "WindowsAzureActiveDirectoryIntegratedApp"
+ ],
+ "tokenEncryptionKeyId": null
+ }
+ ]
+}
+