Configure the Graph RBAC API and allow mocking service endpoints

This commit is contained in:
Ignasi Barrera 2017-12-04 10:06:21 +01:00
parent 6c759930d7
commit 3efce9a3a5
11 changed files with 302 additions and 90 deletions

View File

@ -24,6 +24,7 @@ import org.jclouds.azurecompute.arm.domain.ServicePrincipal;
import org.jclouds.azurecompute.arm.features.AvailabilitySetApi; import org.jclouds.azurecompute.arm.features.AvailabilitySetApi;
import org.jclouds.azurecompute.arm.features.DeploymentApi; import org.jclouds.azurecompute.arm.features.DeploymentApi;
import org.jclouds.azurecompute.arm.features.DiskApi; 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.ImageApi;
import org.jclouds.azurecompute.arm.features.JobApi; import org.jclouds.azurecompute.arm.features.JobApi;
import org.jclouds.azurecompute.arm.features.LoadBalancerApi; import org.jclouds.azurecompute.arm.features.LoadBalancerApi;
@ -242,6 +243,15 @@ public interface AzureComputeApi extends Closeable {
@Delegate @Delegate
MetricDefinitionsApi getMetricsDefinitionsApi(@PathParam("resourceid") String resourceid); 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. * Returns the information about the current service principal.
*/ */

View File

@ -37,11 +37,11 @@ import static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE;
import java.net.URI; import java.net.URI;
import java.util.Properties; import java.util.Properties;
import org.jclouds.azurecompute.arm.config.AzureComputeHttpApiModule.CurrentServicePrincipal;
import org.jclouds.azurecompute.arm.domain.Region; import org.jclouds.azurecompute.arm.domain.Region;
import org.jclouds.azurecompute.arm.features.AvailabilitySetApi; import org.jclouds.azurecompute.arm.features.AvailabilitySetApi;
import org.jclouds.azurecompute.arm.features.DeploymentApi; import org.jclouds.azurecompute.arm.features.DeploymentApi;
import org.jclouds.azurecompute.arm.features.DiskApi; 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.ImageApi;
import org.jclouds.azurecompute.arm.features.LoadBalancerApi; import org.jclouds.azurecompute.arm.features.LoadBalancerApi;
import org.jclouds.azurecompute.arm.features.LocationApi; import org.jclouds.azurecompute.arm.features.LocationApi;
@ -125,7 +125,7 @@ public class AzureComputeProviderMetadata extends BaseProviderMetadata {
properties.put(API_VERSION_PREFIX + MetricDefinitionsApi.class.getSimpleName(), "2017-05-01-preview"); 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 + MetricsApi.class.getSimpleName(), "2016-09-01");
properties.put(API_VERSION_PREFIX + VirtualMachineScaleSetApi.class.getSimpleName(), "2017-03-30"); 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; return properties;
} }

View File

@ -16,6 +16,8 @@
*/ */
package org.jclouds.azurecompute.arm; package org.jclouds.azurecompute.arm;
import static org.jclouds.reflect.Reflection2.typeToken;
import java.net.URI; import java.net.URI;
import java.util.Properties; import java.util.Properties;
@ -32,8 +34,6 @@ import org.jclouds.rest.internal.BaseHttpApiMetadata;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.inject.Module; import com.google.inject.Module;
import static org.jclouds.reflect.Reflection2.typeToken;
/** /**
* Implementation of {@link ApiMetadata} for Microsoft Azure Resource Manager REST API * Implementation of {@link ApiMetadata} for Microsoft Azure Resource Manager REST API
*/ */
@ -44,8 +44,12 @@ public class AzureManagementApiMetadata extends BaseHttpApiMetadata<AzureCompute
return new Builder().fromApiMetadata(this); return new Builder().fromApiMetadata(this);
} }
public static Builder builder() {
return new Builder();
}
public AzureManagementApiMetadata() { public AzureManagementApiMetadata() {
this(new Builder()); this(builder());
} }
protected AzureManagementApiMetadata(final Builder builder) { protected AzureManagementApiMetadata(final Builder builder) {

View File

@ -17,7 +17,6 @@
package org.jclouds.azurecompute.arm.config; package org.jclouds.azurecompute.arm.config;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
import java.net.URI; import java.net.URI;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -26,14 +25,10 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.inject.Singleton; 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.AzureComputeApi;
import org.jclouds.azurecompute.arm.config.GraphRBAC.GraphRBACForTenant;
import org.jclouds.azurecompute.arm.domain.ServicePrincipal; import org.jclouds.azurecompute.arm.domain.ServicePrincipal;
import org.jclouds.azurecompute.arm.filters.ApiVersionFilter;
import org.jclouds.azurecompute.arm.handlers.AzureComputeErrorHandler; import org.jclouds.azurecompute.arm.handlers.AzureComputeErrorHandler;
import org.jclouds.http.HttpErrorHandler; import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.annotation.ClientError; import org.jclouds.http.annotation.ClientError;
@ -43,28 +38,22 @@ import org.jclouds.location.suppliers.ImplicitLocationSupplier;
import org.jclouds.location.suppliers.implicit.FirstRegion; import org.jclouds.location.suppliers.implicit.FirstRegion;
import org.jclouds.oauth.v2.config.OAuthConfigFactory; import org.jclouds.oauth.v2.config.OAuthConfigFactory;
import org.jclouds.oauth.v2.config.OAuthScopes; import org.jclouds.oauth.v2.config.OAuthScopes;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ConfiguresHttpApi; 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.config.HttpApiModule;
import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier; import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.inject.Provides; import com.google.inject.Provides;
import com.google.inject.Scopes; import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named; import com.google.inject.name.Named;
@ConfiguresHttpApi @ConfiguresHttpApi
public class AzureComputeHttpApiModule extends HttpApiModule<AzureComputeApi> { public class AzureComputeHttpApiModule extends HttpApiModule<AzureComputeApi> {
private static final Pattern OAUTH_TENANT_PATTERN = Pattern private static final Pattern OAUTH_TENANT_PATTERN = Pattern
.compile("https://login.microsoftonline.com/([^/]+)/oauth2/token"); .compile("https://login.microsoft(?:online)?.com/([^/]+)/oauth2/token");
@Override @Override
protected void bindErrorHandlers() { protected void bindErrorHandlers() {
@ -82,15 +71,20 @@ public class AzureComputeHttpApiModule extends HttpApiModule<AzureComputeApi> {
@Override @Override
protected void configure() { protected void configure() {
super.configure(); super.configure();
bindHttpApi(binder(), CurrentServicePrincipal.class);
bind(OAuthScopes.class).toInstance(OAuthScopes.NoScopes.create()); bind(OAuthScopes.class).toInstance(OAuthScopes.NoScopes.create());
bind(OAuthConfigFactory.class).to(AzureOAuthConfigFactory.class).in(Scopes.SINGLETON); 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 @Provides
@Singleton @Singleton
@Tenant @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); Matcher m = OAUTH_TENANT_PATTERN.matcher(oauthEndpoint);
if (!m.matches()) { if (!m.matches()) {
throw new IllegalArgumentException("Could not parse tenantId from: " + oauthEndpoint); throw new IllegalArgumentException("Could not parse tenantId from: " + oauthEndpoint);
@ -100,37 +94,15 @@ public class AzureComputeHttpApiModule extends HttpApiModule<AzureComputeApi> {
@Provides @Provides
@Singleton @Singleton
@GraphRBAC protected final Supplier<ServicePrincipal> provideServicePrincipal(final AzureComputeApi api,
protected Supplier<URI> graphRBACEndpoint(@Tenant String tenantId) {
return Suppliers.ofInstance(URI.create(GraphRBAC.ENDPOINT + tenantId));
}
@Provides
@Singleton
protected Supplier<ServicePrincipal> provideServicePrincipal(final CurrentServicePrincipal currentServicePrincipal,
AtomicReference<AuthorizationException> authException, @Named(PROPERTY_SESSION_INTERVAL) long seconds) { AtomicReference<AuthorizationException> authException, @Named(PROPERTY_SESSION_INTERVAL) long seconds) {
// This supplier must be defensive against any auth exception. // This supplier must be defensive against any auth exception.
return MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(authException, return MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(authException,
new Supplier<ServicePrincipal>() { new Supplier<ServicePrincipal>() {
@Override @Override
public ServicePrincipal get() { public ServicePrincipal get() {
return currentServicePrincipal.get(); return api.getGraphRBACApi().getCurrentServicePrincipal();
} }
}, seconds, TimeUnit.SECONDS); }, 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();
}
} }

View File

@ -20,9 +20,13 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.net.URI;
import javax.inject.Inject;
import javax.inject.Qualifier; import javax.inject.Qualifier;
import com.google.common.base.Supplier;
/** /**
* Provides the Graph RBAC API endpoint for the current tenant. * Provides the Graph RBAC API endpoint for the current tenant.
*/ */
@ -32,4 +36,19 @@ import javax.inject.Qualifier;
public @interface GraphRBAC { public @interface GraphRBAC {
String ENDPOINT = "https://graph.windows.net/"; 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);
}
}
} }

View File

@ -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();
}

View File

@ -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);
}
}
}

View File

@ -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");
}
}

View File

@ -17,7 +17,6 @@
package org.jclouds.azurecompute.arm.features; package org.jclouds.azurecompute.arm.features;
import com.google.common.collect.ImmutableMap; 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.Extension;
import org.jclouds.azurecompute.arm.domain.ExtensionProfile; import org.jclouds.azurecompute.arm.domain.ExtensionProfile;
import org.jclouds.azurecompute.arm.domain.ExtensionProfileSettings; import org.jclouds.azurecompute.arm.domain.ExtensionProfileSettings;
@ -69,8 +68,6 @@ public class VirtualMachineScaleSetApiLiveTest extends BaseAzureComputeApiLiveTe
private String subscriptionid; private String subscriptionid;
private String vmssName; private String vmssName;
private VirtualMachineScaleSetSKU SKU;
private String nicName;
private String virtualNetworkName; private String virtualNetworkName;
private String subnetId; private String subnetId;
private Subnet subnet; private Subnet subnet;
@ -151,25 +148,6 @@ public class VirtualMachineScaleSetApiLiveTest extends BaseAzureComputeApiLiveTe
return VirtualMachineScaleSetUpgradePolicy.create("Manual"); 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() { private StorageProfile getLinuxStorageProfile_Default() {
return StorageProfile.create(getLinuxImageReference(), getLinuxOSDisk(), null); return StorageProfile.create(getLinuxImageReference(), getLinuxOSDisk(), null);
} }
@ -178,21 +156,11 @@ public class VirtualMachineScaleSetApiLiveTest extends BaseAzureComputeApiLiveTe
return ManagedDiskParameters.create(null, "Standard_LRS"); return ManagedDiskParameters.create(null, "Standard_LRS");
} }
private OSDisk getWindowsOSDisk() {
return OSDisk.create("Windows", null, null, null, "FromImage",
null, getManagedDiskParameters(), null);
}
private OSDisk getLinuxOSDisk() { private OSDisk getLinuxOSDisk() {
return OSDisk.create("Linux", null, null, null, "FromImage", return OSDisk.create("Linux", null, null, null, "FromImage",
null, getManagedDiskParameters(), null); null, getManagedDiskParameters(), null);
} }
private ImageReference getWindowsImageReference() {
return ImageReference.create(null, "Microsoft.Windows", "Windows2016",
"Enterprise", "latest");
}
private ImageReference getLinuxImageReference() { private ImageReference getLinuxImageReference() {
return ImageReference.create(null, "Canonical", "UbuntuServer", return ImageReference.create(null, "Canonical", "UbuntuServer",
"16.04-LTS", "latest"); "16.04-LTS", "latest");

View File

@ -15,30 +15,44 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jclouds.azurecompute.arm.internal; 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 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.CredentialType.BEARER_TOKEN_CREDENTIALS;
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE; import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import org.jclouds.ContextBuilder; import org.jclouds.ContextBuilder;
import org.jclouds.azurecompute.arm.AzureComputeApi; import org.jclouds.azurecompute.arm.AzureComputeApi;
import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata; 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.concurrent.config.ExecutorServiceModule;
import org.jclouds.date.DateService; import org.jclouds.date.DateService;
import org.jclouds.providers.ProviderMetadata;
import org.jclouds.rest.ApiContext; import org.jclouds.rest.ApiContext;
import org.jclouds.rest.ConfiguresHttpApi;
import org.testng.annotations.AfterMethod; import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeMethod;
import com.google.common.base.Charsets; 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.base.Throwables;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer; import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest; import com.squareup.okhttp.mockwebserver.RecordedRequest;
@ -48,8 +62,6 @@ public class BaseAzureComputeApiMockTest {
private static final String MOCK_BEARER_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9"; private static final String MOCK_BEARER_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9";
private static final String DEFAULT_ENDPOINT = new AzureComputeProviderMetadata().getEndpoint(); private static final String DEFAULT_ENDPOINT = new AzureComputeProviderMetadata().getEndpoint();
private final Set<Module> modules = ImmutableSet.<Module> of(new ExecutorServiceModule(sameThreadExecutor()));
protected MockWebServer server; protected MockWebServer server;
protected AzureComputeApi api; protected AzureComputeApi api;
protected ApiContext<AzureComputeApi> context; protected ApiContext<AzureComputeApi> context;
@ -62,25 +74,42 @@ public class BaseAzureComputeApiMockTest {
public void start() throws IOException { public void start() throws IOException {
server = new MockWebServer(); server = new MockWebServer();
server.play(); server.play();
AzureComputeProviderMetadata pm = AzureComputeProviderMetadata.builder().build();
context = ContextBuilder.newBuilder(pm) context = ContextBuilder.newBuilder(testProviderMetadata())
.credentials("", MOCK_BEARER_TOKEN) .credentials("mock", MOCK_BEARER_TOKEN)
.endpoint(server.getUrl("/").toString() + "subscriptions/SUBSCRIPTIONID") .endpoint(server.getUrl("/").toString() + "subscriptions/SUBSCRIPTIONID")
.modules(modules) .modules(setupModules())
.overrides(setupProperties()) .overrides(setupProperties())
.build(); .build();
api = context.getApi(); api = context.getApi();
dateService = context.utils().injector().getInstance(DateService.class); 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() { protected Properties setupProperties() {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put(CREDENTIAL_TYPE, BEARER_TOKEN_CREDENTIALS.toString()); properties.put(CREDENTIAL_TYPE, BEARER_TOKEN_CREDENTIALS.toString());
properties.put("oauth.endpoint", "https://login.microsoftonline.com/tenant-id/oauth2/token"); properties.put("oauth.endpoint", "https://login.microsoftonline.com/tenant-id/oauth2/token");
return properties; 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) @AfterMethod(alwaysRun = true)
public void stop() throws IOException { public void stop() throws IOException {
server.shutdown(); server.shutdown();
@ -107,7 +136,6 @@ public class BaseAzureComputeApiMockTest {
return new MockResponse().setStatus("HTTP/1.1 202 Accepted"); return new MockResponse().setStatus("HTTP/1.1 202 Accepted");
} }
protected MockResponse response204() { protected MockResponse response204() {
return new MockResponse().setStatus("HTTP/1.1 204 No Content"); return new MockResponse().setStatus("HTTP/1.1 204 No Content");
} }
@ -115,13 +143,15 @@ public class BaseAzureComputeApiMockTest {
protected MockResponse response202WithHeader() { protected MockResponse response202WithHeader() {
return new MockResponse() return new MockResponse()
.setStatus("HTTP/1.1 202 Accepted") .setStatus("HTTP/1.1 202 Accepted")
.addHeader("Location", "https://management.azure.com/subscriptions/SUBSCRIPTIONID/operationresults/eyJqb2JJZCI6IlJFU09VUkNFR1JPVVBERUxFVElPTkpPQi1SVEVTVC1DRU5UUkFMVVMiLCJqb2JMb2NhdGlvbiI6ImNlbnRyYWx1cyJ9?api-version=2014-04-01"); .addHeader(
"Location",
"https://management.azure.com/subscriptions/SUBSCRIPTIONID/operationresults/eyJqb2JJZCI6IlJFU09VUkNFR1JPVVBERUxFVElPTkpPQi1SVEVTVC1DRU5UUkFMVVMiLCJqb2JMb2NhdGlvbiI6ImNlbnRyYWx1cyJ9?api-version=2014-04-01");
} }
protected String stringFromResource(String resourceName) { protected String stringFromResource(String resourceName) {
try { try {
return Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8) return Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8).replace(DEFAULT_ENDPOINT,
.replace(DEFAULT_ENDPOINT, url("")); url(""));
} catch (IOException e) { } catch (IOException e) {
throw Throwables.propagate(e); throw Throwables.propagate(e);
} }
@ -143,4 +173,21 @@ public class BaseAzureComputeApiMockTest {
assertEquals(parser.parse(new String(request.getBody(), Charsets.UTF_8)), parser.parse(json)); assertEquals(parser.parse(new String(request.getBody(), Charsets.UTF_8)), parser.parse(json));
return request; 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")));
}
}
} }

View File

@ -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
}
]
}