You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2017/12/05 08:09:40 UTC

[1/2] jclouds-labs git commit: Injectable current service principal

Repository: jclouds-labs
Updated Branches:
  refs/heads/master 13a0440d9 -> 6472341ad


Injectable current service principal


Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/86fc88f1
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/86fc88f1
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/86fc88f1

Branch: refs/heads/master
Commit: 86fc88f1b07c9d964d554ab32eaf7c07228a127f
Parents: 13a0440
Author: Ignasi Barrera <na...@apache.org>
Authored: Fri Dec 1 11:54:58 2017 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Fri Dec 1 14:21:22 2017 +0100

----------------------------------------------------------------------
 .../azurecompute/arm/AzureComputeApi.java       | 40 ++++++----
 .../arm/AzureComputeProviderMetadata.java       | 20 ++---
 .../arm/config/AzureComputeHttpApiModule.java   | 83 +++++++++++++++++++-
 .../arm/config/AzureOAuthConfigFactory.java     | 60 ++++++++++++++
 .../azurecompute/arm/config/GraphRBAC.java      | 35 +++++++++
 .../azurecompute/arm/config/OAuthResource.java  | 35 +++++++++
 .../jclouds/azurecompute/arm/config/Tenant.java | 34 ++++++++
 .../arm/domain/ServicePrincipal.java            | 66 ++++++++++++++++
 ...achineScaleSetIpConfigurationProperties.java | 38 +++------
 .../domain/VirtualMachineScaleSetOSProfile.java |  8 +-
 .../CurrentServicePrincipalApiLiveTest.java     | 34 ++++++++
 11 files changed, 396 insertions(+), 57 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeApi.java
----------------------------------------------------------------------
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 8623580..afc8413 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
@@ -20,30 +20,34 @@ import java.io.Closeable;
 
 import javax.ws.rs.PathParam;
 
+import org.jclouds.azurecompute.arm.domain.ServicePrincipal;
+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.ImageApi;
 import org.jclouds.azurecompute.arm.features.JobApi;
+import org.jclouds.azurecompute.arm.features.LoadBalancerApi;
 import org.jclouds.azurecompute.arm.features.LocationApi;
+import org.jclouds.azurecompute.arm.features.MetricDefinitionsApi;
+import org.jclouds.azurecompute.arm.features.MetricsApi;
+import org.jclouds.azurecompute.arm.features.NetworkInterfaceCardApi;
+import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi;
+import org.jclouds.azurecompute.arm.features.NetworkSecurityRuleApi;
+import org.jclouds.azurecompute.arm.features.OSImageApi;
+import org.jclouds.azurecompute.arm.features.PublicIPAddressApi;
 import org.jclouds.azurecompute.arm.features.ResourceGroupApi;
+import org.jclouds.azurecompute.arm.features.ResourceProviderApi;
 import org.jclouds.azurecompute.arm.features.StorageAccountApi;
 import org.jclouds.azurecompute.arm.features.SubnetApi;
-import org.jclouds.azurecompute.arm.features.VirtualNetworkApi;
-import org.jclouds.azurecompute.arm.features.NetworkInterfaceCardApi;
-import org.jclouds.azurecompute.arm.features.PublicIPAddressApi;
+import org.jclouds.azurecompute.arm.features.VMSizeApi;
 import org.jclouds.azurecompute.arm.features.VirtualMachineApi;
 import org.jclouds.azurecompute.arm.features.VirtualMachineScaleSetApi;
-import org.jclouds.azurecompute.arm.features.VMSizeApi;
-import org.jclouds.azurecompute.arm.features.OSImageApi;
-import org.jclouds.azurecompute.arm.features.DeploymentApi;
-import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi;
-import org.jclouds.azurecompute.arm.features.NetworkSecurityRuleApi;
-import org.jclouds.azurecompute.arm.features.LoadBalancerApi;
-import org.jclouds.azurecompute.arm.features.AvailabilitySetApi;
-import org.jclouds.azurecompute.arm.features.ResourceProviderApi;
-import org.jclouds.azurecompute.arm.features.DiskApi;
-import org.jclouds.azurecompute.arm.features.ImageApi;
-import org.jclouds.azurecompute.arm.features.MetricsApi;
-import org.jclouds.azurecompute.arm.features.MetricDefinitionsApi;
+import org.jclouds.azurecompute.arm.features.VirtualNetworkApi;
 import org.jclouds.rest.annotations.Delegate;
 
+import com.google.common.base.Supplier;
+import com.google.inject.Provides;
+
 /**
  * The Azure Resource Manager API is a REST API for managing your services and deployments.
  * <p>
@@ -237,4 +241,10 @@ public interface AzureComputeApi extends Closeable {
     */
    @Delegate
    MetricDefinitionsApi getMetricsDefinitionsApi(@PathParam("resourceid") String resourceid);
+   
+   /**
+    * Returns the information about the current service principal.
+    */
+   @Provides
+   Supplier<ServicePrincipal> getServicePrincipal();
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java
----------------------------------------------------------------------
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 8849a1f..6848167 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,28 +37,29 @@ import static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE;
 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.ImageApi;
+import org.jclouds.azurecompute.arm.features.LoadBalancerApi;
 import org.jclouds.azurecompute.arm.features.LocationApi;
+import org.jclouds.azurecompute.arm.features.MetricDefinitionsApi;
+import org.jclouds.azurecompute.arm.features.MetricsApi;
 import org.jclouds.azurecompute.arm.features.NetworkInterfaceCardApi;
 import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi;
 import org.jclouds.azurecompute.arm.features.NetworkSecurityRuleApi;
 import org.jclouds.azurecompute.arm.features.OSImageApi;
-import org.jclouds.azurecompute.arm.features.ResourceGroupApi;
 import org.jclouds.azurecompute.arm.features.PublicIPAddressApi;
+import org.jclouds.azurecompute.arm.features.ResourceGroupApi;
 import org.jclouds.azurecompute.arm.features.ResourceProviderApi;
 import org.jclouds.azurecompute.arm.features.StorageAccountApi;
 import org.jclouds.azurecompute.arm.features.SubnetApi;
-import org.jclouds.azurecompute.arm.features.VirtualNetworkApi;
 import org.jclouds.azurecompute.arm.features.VMSizeApi;
 import org.jclouds.azurecompute.arm.features.VirtualMachineApi;
-import org.jclouds.azurecompute.arm.features.LoadBalancerApi;
-import org.jclouds.azurecompute.arm.features.AvailabilitySetApi;
-import org.jclouds.azurecompute.arm.features.DiskApi;
-import org.jclouds.azurecompute.arm.features.ImageApi;
-import org.jclouds.azurecompute.arm.features.MetricDefinitionsApi;
-import org.jclouds.azurecompute.arm.features.MetricsApi;
 import org.jclouds.azurecompute.arm.features.VirtualMachineScaleSetApi;
+import org.jclouds.azurecompute.arm.features.VirtualNetworkApi;
 import org.jclouds.providers.ProviderMetadata;
 import org.jclouds.providers.internal.BaseProviderMetadata;
 
@@ -124,7 +125,8 @@ public class AzureComputeProviderMetadata extends BaseProviderMetadata {
       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");
+      
       return properties;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeHttpApiModule.java
----------------------------------------------------------------------
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 991c738..8d417ca 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
@@ -16,7 +16,24 @@
  */
 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;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+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.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;
@@ -24,15 +41,31 @@ import org.jclouds.http.annotation.Redirection;
 import org.jclouds.http.annotation.ServerError;
 import org.jclouds.location.suppliers.ImplicitLocationSupplier;
 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.name.Named;
 
 @ConfiguresHttpApi
 public class AzureComputeHttpApiModule extends HttpApiModule<AzureComputeApi> {
 
+   private static final Pattern OAUTH_TENANT_PATTERN = Pattern
+         .compile("https://login.microsoftonline.com/([^/]+)/oauth2/token");
+
    @Override
    protected void bindErrorHandlers() {
       bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(AzureComputeErrorHandler.class);
@@ -46,10 +79,58 @@ public class AzureComputeHttpApiModule extends HttpApiModule<AzureComputeApi> {
       bind(ImplicitLocationSupplier.class).to(FirstRegion.class).in(Scopes.SINGLETON);
    }
 
-
    @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);
+   }
+
+   @Provides
+   @Singleton
+   @Tenant
+   protected 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);
+      }
+      return m.group(1);
+   }
+
+   @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,
+         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();
+               }
+            }, 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();
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureOAuthConfigFactory.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureOAuthConfigFactory.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureOAuthConfigFactory.java
new file mode 100644
index 0000000..9128b59
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureOAuthConfigFactory.java
@@ -0,0 +1,60 @@
+/*
+ * 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.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
+import static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.oauth.v2.config.OAuthConfigFactory;
+import org.jclouds.oauth.v2.config.OAuthScopes;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+public class AzureOAuthConfigFactory implements OAuthConfigFactory {
+   private final OAuthScopes scopes;
+   
+   @Named(AUDIENCE)
+   @Inject(optional = true)
+   private String audience;
+   
+   @Named(RESOURCE)
+   @Inject(optional = true)
+   private String resource;
+
+   @Inject
+   AzureOAuthConfigFactory(OAuthScopes scopes) {
+      this.scopes = scopes;
+   }
+
+   @Override
+   public OAuthConfig forRequest(HttpRequest input) {
+      OAuthResource customResource = null;
+      if (input instanceof GeneratedHttpRequest) {
+         GeneratedHttpRequest request = (GeneratedHttpRequest) input;
+         customResource = request.getInvocation().getInvokable().getAnnotation(OAuthResource.class);
+         if (customResource == null) {
+            customResource = request.getInvocation().getInvokable().getDeclaringClass()
+                  .getAnnotation(OAuthResource.class);
+         }
+      }
+      String oauthResource = customResource != null ? customResource.value() : resource;
+      return OAuthConfig.create(scopes.forRequest(input), audience, oauthResource);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/GraphRBAC.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..6853782
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/GraphRBAC.java
@@ -0,0 +1,35 @@
+/*
+ * 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/**
+ * Provides the Graph RBAC API endpoint for the current tenant.
+ */
+@Retention(value = RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
+@Qualifier
+public @interface GraphRBAC {
+
+   String ENDPOINT = "https://graph.windows.net/";
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/OAuthResource.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/OAuthResource.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/OAuthResource.java
new file mode 100644
index 0000000..6e5a2df
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/OAuthResource.java
@@ -0,0 +1,35 @@
+/*
+ * 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/**
+ * Configures a custom OAuth resource for certain APIs and methods.
+ */
+@Retention(value = RetentionPolicy.RUNTIME)
+@Target(value = { ElementType.TYPE, ElementType.METHOD })
+@Qualifier
+public @interface OAuthResource {
+   
+   String value();
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/Tenant.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/Tenant.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/Tenant.java
new file mode 100644
index 0000000..5524361
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/Tenant.java
@@ -0,0 +1,34 @@
+/*
+ * 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/**
+ * Qualifies an object that describes the current tenant.
+ */
+@Retention(value = RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
+@Qualifier
+public @interface Tenant {
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/ServicePrincipal.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/ServicePrincipal.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/ServicePrincipal.java
new file mode 100644
index 0000000..2faba3f
--- /dev/null
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/ServicePrincipal.java
@@ -0,0 +1,66 @@
+/*
+ * 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.domain;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+@AutoValue
+public abstract class ServicePrincipal {
+   
+   @Nullable public abstract String appId();
+   @Nullable public abstract Date deletionTimestamp();
+   @Nullable public abstract String displayName();
+   public abstract String objectId();
+   public abstract String objectType();
+   public abstract List<String> servicePrincipalNames();
+
+   @SerializedNames({ "appId", "deletionTimestamp", "displayName", "objectId", "objectType", "servicePrincipalNames" })
+   public static ServicePrincipal create(String appId, Date deletionTimestamp, String displayName, String objectId,
+         String objectType, List<String> servicePrincipalNames) {
+      List<String> servicePrincipals = servicePrincipalNames != null ? ImmutableList.copyOf(servicePrincipalNames)
+            : ImmutableList.<String> of();
+      return builder().appId(appId).deletionTimestamp(deletionTimestamp).displayName(displayName).objectId(objectId)
+            .objectType(objectType).servicePrincipalNames(servicePrincipals).build();
+   }
+
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_ServicePrincipal.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+      
+      public abstract Builder appId(String appId);
+      public abstract Builder deletionTimestamp(Date deletionTimestamp);
+      public abstract Builder displayName(String displayName);
+      public abstract Builder objectId(String objectId);
+      public abstract Builder objectType(String objectType);
+      public abstract Builder servicePrincipalNames(List<String> servicePrincipalNames);
+
+      public abstract ServicePrincipal build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VirtualMachineScaleSetIpConfigurationProperties.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VirtualMachineScaleSetIpConfigurationProperties.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VirtualMachineScaleSetIpConfigurationProperties.java
index ee23152..739dadf 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VirtualMachineScaleSetIpConfigurationProperties.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VirtualMachineScaleSetIpConfigurationProperties.java
@@ -70,49 +70,35 @@ public abstract class VirtualMachineScaleSetIpConfigurationProperties {
       final String applicationGatewayBackendAddressPools)
 
    {
-
       return builder()
-         .publicIPAddressConfiguration(publicIPAddressConfiguration)
-         .subnet(subnet)
-         .privateIPAddressVersion(privateIPAddressVersion)
-         .lbBackendAddressPools(loadBalancerBackendAddressPools)
-         .lbInboundNatPools(loadBalancerInboundNatPools)
-         .applicationGatewayBackendAddressPools(applicationGatewayBackendAddressPools)
-         .build();
+            .publicIPAddressConfiguration(publicIPAddressConfiguration)
+            .subnet(subnet)
+            .privateIPAddressVersion(privateIPAddressVersion)
+            .loadBalancerBackendAddressPools(
+                  loadBalancerBackendAddressPools != null ? ImmutableList.copyOf(loadBalancerBackendAddressPools)
+                        : ImmutableList.<IdReference> of())
+            .loadBalancerInboundNatPools(
+                  loadBalancerInboundNatPools != null ? ImmutableList.copyOf(loadBalancerInboundNatPools)
+                        : ImmutableList.<IdReference> of())
+            .applicationGatewayBackendAddressPools(applicationGatewayBackendAddressPools).build();
    }
 
    public abstract Builder toBuilder();
 
    public static Builder builder() {
-      return new AutoValue_VirtualMachineScaleSetIpConfigurationProperties.Builder()
-              .lbBackendAddressPools(null)
-              .lbInboundNatPools(null);
+      return new AutoValue_VirtualMachineScaleSetIpConfigurationProperties.Builder();
    }
 
    @AutoValue.Builder
    public abstract static class Builder {
       public abstract Builder publicIPAddressConfiguration(VirtualMachineScaleSetPublicIPAddressConfiguration publicIPAddressConfiguration);
-
       public abstract Builder subnet(Subnet subnet);
-
       public abstract Builder loadBalancerBackendAddressPools(List<IdReference> loadBalancerBackendAddressPools);
-
       public abstract Builder loadBalancerInboundNatPools(List<IdReference> loadBalancerInboundNatPools);
-
       public abstract Builder privateIPAddressVersion(String privateIPAddressVersion);
-
-      public Builder lbBackendAddressPools(List<IdReference> loadBalancerBackendAddressPools) {
-         return loadBalancerBackendAddressPools(loadBalancerBackendAddressPools != null ? ImmutableList
-                 .copyOf(loadBalancerBackendAddressPools) : ImmutableList.<IdReference>of());
-      }
-      public Builder lbInboundNatPools(List<IdReference> loadBalancerInboundNatPools) {
-         return loadBalancerInboundNatPools(loadBalancerInboundNatPools != null ? ImmutableList
-         .copyOf(loadBalancerInboundNatPools) : ImmutableList.<IdReference>of());
-      }
-
       public abstract Builder applicationGatewayBackendAddressPools(String applicationGatewayBackendAddressPools);
+      
       public abstract VirtualMachineScaleSetIpConfigurationProperties build();
-
    }
 }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VirtualMachineScaleSetOSProfile.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VirtualMachineScaleSetOSProfile.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VirtualMachineScaleSetOSProfile.java
index 5593457..e417d5a 100644
--- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VirtualMachineScaleSetOSProfile.java
+++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/VirtualMachineScaleSetOSProfile.java
@@ -249,14 +249,14 @@ public abstract class VirtualMachineScaleSetOSProfile {
          .adminPassword(adminPassword)
          .linuxConfiguration(linuxConfiguration)
          .windowsConfiguration(windowsConfiguration)
-         ._secrets(secrets)
+         .secrets(secrets != null ? ImmutableList.copyOf(secrets) : ImmutableList.<Secrets> of())
          .build();
    }
 
    public abstract Builder toBuilder();
 
    public static Builder builder() {
-      return new AutoValue_VirtualMachineScaleSetOSProfile.Builder()._secrets(null);
+      return new AutoValue_VirtualMachineScaleSetOSProfile.Builder();
    }
 
    @AutoValue.Builder
@@ -268,10 +268,6 @@ public abstract class VirtualMachineScaleSetOSProfile {
       public abstract Builder windowsConfiguration(WindowsConfiguration windowsConfiguration);
       public abstract Builder secrets(List<Secrets> secrets);
 
-      public Builder _secrets(List<Secrets> secrets) {
-         return secrets(secrets != null ? ImmutableList.copyOf(secrets) : ImmutableList.<Secrets>of());
-      }
-
       public abstract VirtualMachineScaleSetOSProfile build();
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/86fc88f1/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/CurrentServicePrincipalApiLiveTest.java
----------------------------------------------------------------------
diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/CurrentServicePrincipalApiLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/CurrentServicePrincipalApiLiveTest.java
new file mode 100644
index 0000000..50a1e11
--- /dev/null
+++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/CurrentServicePrincipalApiLiveTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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 org.jclouds.azurecompute.arm.domain.ServicePrincipal;
+import org.jclouds.azurecompute.arm.internal.BaseAzureComputeApiLiveTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "live", testName = "CurrentServicePrincipalApiLiveTest", singleThreaded = true)
+public class CurrentServicePrincipalApiLiveTest extends BaseAzureComputeApiLiveTest {
+
+   @Test
+   public void testGetCurrentServicePrincipal() {
+      ServicePrincipal currentUser = api.getServicePrincipal().get();
+      assertEquals(currentUser.appId(), identity);
+   }
+
+}


[2/2] jclouds-labs git commit: Configure the Graph RBAC API and allow mocking service endpoints

Posted by na...@apache.org.
Configure the Graph RBAC API and allow mocking service endpoints


Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/6472341a
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/6472341a
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/6472341a

Branch: refs/heads/master
Commit: 6472341adcb1a3df0acb0bfc5923dfad80be4dff
Parents: 86fc88f
Author: Ignasi Barrera <na...@apache.org>
Authored: Mon Dec 4 10:06:21 2017 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Mon Dec 4 10:12:16 2017 +0100

----------------------------------------------------------------------
 .../azurecompute/arm/AzureComputeApi.java       | 10 +++
 .../arm/AzureComputeProviderMetadata.java       |  4 +-
 .../arm/AzureManagementApiMetadata.java         | 10 ++-
 .../arm/config/AzureComputeHttpApiModule.java   | 52 ++++----------
 .../azurecompute/arm/config/GraphRBAC.java      | 19 +++++
 .../azurecompute/arm/features/GraphRBACApi.java | 50 ++++++++++++++
 .../arm/config/ParseTenantIdTest.java           | 50 ++++++++++++++
 .../arm/features/GraphRBACApiMockTest.java      | 39 +++++++++++
 .../VirtualMachineScaleSetApiLiveTest.java      | 32 ---------
 .../internal/BaseAzureComputeApiMockTest.java   | 73 ++++++++++++++++----
 .../src/test/resources/serviceprincipals.json   | 53 ++++++++++++++
 11 files changed, 302 insertions(+), 90 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeApi.java
----------------------------------------------------------------------
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.domain.ServicePrincipal;
 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 @@ public interface AzureComputeApi extends Closeable {
    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

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureComputeProviderMetadata.java
----------------------------------------------------------------------
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 static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE;
 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 @@ public class AzureComputeProviderMetadata extends BaseProviderMetadata {
       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;
    }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/AzureManagementApiMetadata.java
----------------------------------------------------------------------
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 org.jclouds.rest.internal.BaseHttpApiMetadata;
 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 class AzureManagementApiMetadata extends BaseHttpApiMetadata<AzureCompute
    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) {

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/AzureComputeHttpApiModule.java
----------------------------------------------------------------------
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.Matcher;
 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.ImplicitLocationSupplier;
 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 @@ public class AzureComputeHttpApiModule extends HttpApiModule<AzureComputeApi> {
    @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 @@ public class AzureComputeHttpApiModule extends HttpApiModule<AzureComputeApi> {
 
    @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();
-   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/config/GraphRBAC.java
----------------------------------------------------------------------
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.ElementType;
 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 @@ import javax.inject.Qualifier;
 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);
+      }
+
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/features/GraphRBACApi.java
----------------------------------------------------------------------
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();
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/config/ParseTenantIdTest.java
----------------------------------------------------------------------
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);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/GraphRBACApiMockTest.java
----------------------------------------------------------------------
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");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VirtualMachineScaleSetApiLiveTest.java
----------------------------------------------------------------------
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 @@ public class VirtualMachineScaleSetApiLiveTest extends BaseAzureComputeApiLiveTe
 
    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 @@ public class VirtualMachineScaleSetApiLiveTest extends BaseAzureComputeApiLiveTe
       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 @@ public class VirtualMachineScaleSetApiLiveTest extends BaseAzureComputeApiLiveTe
       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");

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiMockTest.java
----------------------------------------------------------------------
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 @@ public class BaseAzureComputeApiMockTest {
    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 class BaseAzureComputeApiMockTest {
    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 @@ public class BaseAzureComputeApiMockTest {
       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 @@ public class BaseAzureComputeApiMockTest {
    }
 
    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")));
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/6472341a/azurecompute-arm/src/test/resources/serviceprincipals.json
----------------------------------------------------------------------
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
+    }
+  ]
+}
+