Merge branch 'master' of github.com:projectkudu/ARMClient

This commit is contained in:
Ahmed ElSayed 2015-01-22 17:34:30 -08:00
Родитель 890267be20 8aa6567157
Коммит b218224afe
11 изменённых файлов: 138 добавлений и 50 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -15,6 +15,7 @@ build/
bld/
[Bb]in/
[Oo]bj/
[Aa]rtifacts/
# MSTest test Results
[Tt]est[Rr]esult*/

Просмотреть файл

@ -43,7 +43,6 @@ namespace ARMClient.Authentication.AADAuthentication
var tenantCache = await GetTokenForTenants(tokenCache, authResult);
this.TokenStorage.SaveRecentToken(authResult);
this.TokenStorage.SaveCache(tokenCache);
this.TenantStorage.SaveCache(tenantCache);
}
@ -76,7 +75,7 @@ namespace ARMClient.Authentication.AADAuthentication
}
var tokenCache = this.TokenStorage.GetCache();
var authResults = tokenCache.Where(p => p.Key.TenantId == tenantId)
var authResults = tokenCache.Where(p => p.Key.TenantId == tenantId && String.Equals(p.Key.Resource, Constants.CSMResource, StringComparison.OrdinalIgnoreCase))
.Select(p => AuthenticationResult.Deserialize(Encoding.UTF8.GetString(Convert.FromBase64String(p.Value)))).ToArray();
if (authResults.Length <= 0)
{
@ -123,7 +122,7 @@ namespace ARMClient.Authentication.AADAuthentication
{
var tokenCache = new Dictionary<TokenCacheKey, string>();
var authResult = GetAuthorizationResult(tokenCache, tenantId, appId, appKey);
var authResult = GetAuthorizationResultByAppKey(tokenCache, tenantId, appId, appKey);
var tenantCache = new Dictionary<string, TenantCacheInfo>();
var info = new TenantCacheInfo
@ -131,6 +130,19 @@ namespace ARMClient.Authentication.AADAuthentication
tenantId = tenantId
};
//try
//{
// var aadToken = GetAuthorizationResultByAppKey(tokenCache, tenantId, appId, appKey, Constants.AADGraphUrls[(int)AzureEnvironments]);
// var details = await GetTenantDetail(aadToken, tenantId);
// info.displayName = details.displayName;
// info.domain = details.verifiedDomains.First(d => d.@default).name;
// Trace.WriteLine(String.Format("App: {0}, Tenant: {1} ({2})", appId, tenantId, details.verifiedDomains.First(d => d.@default).name));
//}
//catch (Exception ex)
//{
// Trace.WriteLine(String.Format("App: {0}, Tenant: {1} {2}", appId, tenantId, ex.Message));
//}
Trace.WriteLine(String.Format("App: {0}, Tenant: {1}", appId, tenantId));
var subscriptions = await GetSubscriptions(authResult);
@ -185,7 +197,7 @@ namespace ARMClient.Authentication.AADAuthentication
if (!String.IsNullOrEmpty(tenantId) && !String.IsNullOrEmpty(appId) && !String.IsNullOrEmpty(appKey))
{
tokenCache.Clear();
authResult = GetAuthorizationResult(tokenCache, tenantId, appId, appKey);
authResult = GetAuthorizationResultByAppKey(tokenCache, tenantId, appId, appKey);
}
}
@ -231,7 +243,7 @@ namespace ARMClient.Authentication.AADAuthentication
var tenantCache = this.TenantStorage.GetCache();
if (tokenCache.Count > 0)
{
foreach (var item in tokenCache)
foreach (var item in tokenCache.Where(k => String.Equals(k.Key.Resource, Constants.CSMResource, StringComparison.OrdinalIgnoreCase)))
{
var key = item.Key;
var value = item.Value;
@ -247,8 +259,7 @@ namespace ARMClient.Authentication.AADAuthentication
if (authResult.UserInfo != null)
{
var user = authResult.UserInfo.UserId;
yield return string.Format("User: {0}, Tenant: {1}", user, tenantId);
//yield return string.Format("User: {0}, Tenant: {1} {2} ({3})", user, tenantId, details.displayName, details.domain);
yield return string.Format("User: {0}, Tenant: {1} ({2})", user, tenantId, details.domain);
}
else
{
@ -268,7 +279,7 @@ namespace ARMClient.Authentication.AADAuthentication
}
}
protected Task<AuthenticationResult> GetAuthorizationResult(Dictionary<TokenCacheKey, string> tokenCache, string tenantId, string user = null)
protected Task<AuthenticationResult> GetAuthorizationResult(Dictionary<TokenCacheKey, string> tokenCache, string tenantId, string user = null, string resource = Constants.CSMResource)
{
var tcs = new TaskCompletionSource<AuthenticationResult>();
var thread = new Thread(() =>
@ -286,7 +297,7 @@ namespace ARMClient.Authentication.AADAuthentication
if (!string.IsNullOrEmpty(user))
{
result = context.AcquireToken(
resource: "https://management.core.windows.net/",
resource: resource,
clientId: Constants.AADClientId,
redirectUri: new Uri(Constants.AADRedirectUri),
userId: null);
@ -294,7 +305,7 @@ namespace ARMClient.Authentication.AADAuthentication
else
{
result = context.AcquireToken(
resource: "https://management.core.windows.net/",
resource: resource,
clientId: Constants.AADClientId,
redirectUri: new Uri(Constants.AADRedirectUri),
promptBehavior: PromptBehavior.Always);
@ -315,7 +326,7 @@ namespace ARMClient.Authentication.AADAuthentication
return tcs.Task;
}
protected AuthenticationResult GetAuthorizationResult(Dictionary<TokenCacheKey, string> tokenCache, string tenantId, string appId, string appKey)
protected AuthenticationResult GetAuthorizationResultByAppKey(Dictionary<TokenCacheKey, string> tokenCache, string tenantId, string appId, string appKey, string resource = Constants.CSMResource)
{
var azureEnvironment = this.AzureEnvironments;
var authority = String.Format("{0}/{1}", Constants.AADLoginUrls[(int)azureEnvironment], tenantId);
@ -324,10 +335,10 @@ namespace ARMClient.Authentication.AADAuthentication
validateAuthority: true,
tokenCacheStore: tokenCache);
var credential = new ClientCredential(appId, appKey);
var authResult = context.AcquireToken("https://management.core.windows.net/", credential);
var authResult = context.AcquireToken(resource, credential);
// this will only get us one token, we save AppId and AppKey info in TokenCacheKey
var key = tokenCache.Keys.First();
var key = tokenCache.Keys.First(k => String.Equals(k.Resource, resource, StringComparison.OrdinalIgnoreCase));
SaveApplicationInfo(key, tenantId, appId, appKey);
return authResult;
@ -360,20 +371,18 @@ namespace ARMClient.Authentication.AADAuthentication
continue;
}
// blocked on Graph API failure
//try
//{
// var details = await GetTenantDetail(result, tenantId);
// info.displayName = details.displayName;
// info.domain = details.verifiedDomains.First(d => d.@default).name;
// Trace.WriteLine(string.Format("User: {0}, Tenant: {1} {2} ({3})", result.UserInfo.UserId, tenantId, details.displayName, details.verifiedDomains.First(d => d.@default).name));
//}
//catch (Exception ex)
//{
// Trace.WriteLine(string.Format("User: {0}, Tenant: {1} {2}", result.UserInfo.UserId, tenantId, ex.Message));
//}
Trace.WriteLine(string.Format("User: {0}, Tenant: {1}", result.UserInfo.UserId, tenantId));
try
{
var aadToken = await GetAuthorizationResult(tokenCache, tenantId: tenantId, user: authResult.UserInfo.UserId, resource: Constants.AADGraphUrls[(int)AzureEnvironments]);
var details = await GetTenantDetail(aadToken, tenantId);
info.displayName = details.displayName;
info.domain = details.verifiedDomains.First(d => d.@default).name;
Trace.WriteLine(string.Format("User: {0}, Tenant: {1} ({2})", result.UserInfo.UserId, tenantId, details.verifiedDomains.First(d => d.@default).name));
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("User: {0}, Tenant: {1} {2}", result.UserInfo.UserId, tenantId, ex.Message));
}
try
{
@ -386,6 +395,11 @@ namespace ARMClient.Authentication.AADAuthentication
displayName = subscription.displayName
}).ToArray();
if (info.subscriptions.Length > 0)
{
this.TokenStorage.SaveRecentToken(result);
}
foreach (var subscription in subscriptions)
{
Trace.WriteLine(string.Format("\tSubscription {0} ({1})", subscription.subscriptionId, subscription.displayName));
@ -407,6 +421,7 @@ namespace ARMClient.Authentication.AADAuthentication
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", authResult.CreateAuthorizationHeader());
client.DefaultRequestHeaders.Add("User-Agent", Constants.UserAgent.Value);
var azureEnvironment = this.AzureEnvironments;
var url = string.Format("{0}/tenants?api-version={1}", Constants.CSMUrls[(int)azureEnvironment], Constants.CSMApiVersion);
@ -445,6 +460,7 @@ namespace ARMClient.Authentication.AADAuthentication
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", authResult.CreateAuthorizationHeader());
client.DefaultRequestHeaders.Add("User-Agent", Constants.UserAgent.Value);
var azureEnvironment = this.AzureEnvironments;
var url = string.Format("{0}/{1}/tenantDetails?api-version={2}", Constants.AADGraphUrls[(int)azureEnvironment], tenantId, Constants.AADGraphApiVersion);
@ -476,6 +492,7 @@ namespace ARMClient.Authentication.AADAuthentication
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", authResult.CreateAuthorizationHeader());
client.DefaultRequestHeaders.Add("User-Agent", Constants.UserAgent.Value);
var azureEnvironment = this.AzureEnvironments;
var url = string.Format("{0}/subscriptions?api-version={1}", Constants.CSMUrls[(int)azureEnvironment], Constants.CSMApiVersion);

Просмотреть файл

@ -1,4 +1,7 @@

using System;
using System.Diagnostics;
using System.Reflection;
namespace ARMClient.Authentication
{
public static class Constants
@ -49,10 +52,19 @@ namespace ARMClient.Authentication
"f8cdef31-a31e-4b4a-93e4-5f571e91255a"
};
public static Lazy<string> UserAgent = new Lazy<string>(() =>
{
Assembly assembly = Assembly.GetExecutingAssembly();
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
return "ARMClient/" + fvi.FileVersion;
});
public const string AADTenantId = "common";
public const string AADClientId = "1950a258-227b-4e31-a9cf-717495945fc2";
public const string AADRedirectUri = "urn:ietf:wg:oauth:2.0:oob";
public const string CSMResource = "https://management.core.windows.net/";
public const string CSMApiVersion = "2014-01-01";
public const string AADGraphApiVersion = "1.5";
public const string JsonContentType = "application/json";
}
}

Просмотреть файл

@ -65,6 +65,7 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="ARMClient.nuspec" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>ARMClient</id>
<title>ARMClient</title>
<version>0.9.1</version>
<authors>Project Kudu contributors</authors>
<summary>A simple command line tool to invoke the Azure Resource Manager API</summary>
<description>A simple command line tool to invoke the Azure Resource Manager API</description>
<projectUrl>https://github.com/projectkudu/ARMClient</projectUrl>
<tags>azure rest API</tags>
<licenseUrl>http://www.apache.org/licenses/LICENSE-2.0.html</licenseUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<releaseNotes></releaseNotes>
</metadata>
<files>
<file src="bin\debug\*.exe" target="tools" />
<file src="bin\debug\*.dll" target="tools" />
</files>
</package>

Просмотреть файл

@ -4,6 +4,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using ARMClient.Authentication;
using Newtonsoft.Json.Linq;
namespace ARMClient
@ -119,7 +120,7 @@ namespace ARMClient
}
var result = await content.ReadAsStringAsync();
if (_verbose && content.Headers.ContentType.MediaType.Contains("application/json"))
if (content.Headers.ContentType.MediaType.Contains(Constants.JsonContentType))
{
if (result.StartsWith("["))
{

Просмотреть файл

@ -151,7 +151,7 @@ namespace ARMClient
}
catch (Exception ex)
{
Console.WriteLine(ex.GetBaseException().Message);
DumpException(ex);
return -1;
}
}
@ -189,10 +189,25 @@ namespace ARMClient
Console.WriteLine();
}
static void DumpException(Exception ex)
{
if (ex.InnerException != null)
{
DumpException(ex.InnerException);
}
// Aggregate exceptions themselves don't have interesting messages
if (!(ex is AggregateException))
{
Console.WriteLine(ex.Message);
}
}
static void PrintUsage()
{
Console.WriteLine("ARMClient supports getting token and simple Http ARM resources.");
Console.WriteLine("Source codes are available at https://github.com/projectkudu/ARMClient.");
Console.WriteLine(@"ARMClient version {0}", typeof(Program).Assembly.GetName().Version);
Console.WriteLine("A simple tool to invoke the Azure Resource Manager API");
Console.WriteLine("Source code is available on https://github.com/projectkudu/ARMClient.");
Console.WriteLine();
Console.WriteLine("Login and get tokens");
@ -200,7 +215,7 @@ namespace ARMClient
Console.WriteLine();
Console.WriteLine("Call ARM api");
Console.WriteLine(" ARMClient.exe [get|post|put|delete] [url] (-data <@file|json>) (-verbose)");
Console.WriteLine(" ARMClient.exe [get|post|put|delete] [url] (<@file|content>) (-verbose)");
Console.WriteLine();
Console.WriteLine("Copy token to clipboard");
@ -221,14 +236,21 @@ namespace ARMClient
static HttpContent ParseHttpContent(string verb, CommandLineParameters parameters)
{
if (String.Equals(verb, "post", StringComparison.OrdinalIgnoreCase)
|| String.Equals(verb, "put", StringComparison.OrdinalIgnoreCase)
|| String.Equals(verb, "patch", StringComparison.OrdinalIgnoreCase))
bool requiresData = String.Equals(verb, "put", StringComparison.OrdinalIgnoreCase)
|| String.Equals(verb, "patch", StringComparison.OrdinalIgnoreCase);
bool inputRedirected = Console.IsInputRedirected;
if (requiresData || String.Equals(verb, "post", StringComparison.OrdinalIgnoreCase))
{
string data = parameters.Get("-data", requires: false);
string data = parameters.Get("2", "content", requires: requiresData && !inputRedirected);
if (data == null)
{
return new StringContent(String.Empty, Encoding.UTF8, "application/json");
if (inputRedirected)
{
return new StringContent(Console.In.ReadToEnd(), Encoding.UTF8, Constants.JsonContentType);
}
return new StringContent(String.Empty, Encoding.UTF8, Constants.JsonContentType);
}
if (data.StartsWith("@"))
@ -236,7 +258,7 @@ namespace ARMClient
data = File.ReadAllText(data.Substring(1));
}
return new StringContent(data, Encoding.UTF8, "application/json");
return new StringContent(data, Encoding.UTF8, Constants.JsonContentType);
}
return null;
}
@ -246,8 +268,8 @@ namespace ARMClient
using (var client = new HttpClient(new HttpLoggingHandler(new HttpClientHandler(), verbose)))
{
client.DefaultRequestHeaders.Add("Authorization", authResult.CreateAuthorizationHeader());
client.DefaultRequestHeaders.Add("User-Agent", "ARMClient-" + Environment.MachineName);
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("User-Agent", Constants.UserAgent.Value);
client.DefaultRequestHeaders.Add("Accept", Constants.JsonContentType);
if (IsRdfe(uri))
{

Просмотреть файл

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("0.9.1.0")]
[assembly: AssemblyFileVersion("0.9.1.0")]

Просмотреть файл

@ -187,8 +187,8 @@ namespace ARMClient.Library
using (var client = new HttpClient(new HttpClientHandler()))
{
client.DefaultRequestHeaders.Add("Authorization", this._authorizationHeader);
client.DefaultRequestHeaders.Add("User-Agent", "ARMClient-" + Environment.MachineName);
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("User-Agent", Constants.UserAgent.Value);
client.DefaultRequestHeaders.Add("Accept", Constants.JsonContentType);
HttpResponseMessage response = null;
if (String.Equals(verb, "get", StringComparison.OrdinalIgnoreCase))
@ -201,11 +201,11 @@ namespace ARMClient.Library
}
else if (String.Equals(verb, "post", StringComparison.OrdinalIgnoreCase))
{
response = await client.PostAsync(uri, new StringContent(payload ?? String.Empty, Encoding.UTF8, "application/json")).ConfigureAwait(false);
response = await client.PostAsync(uri, new StringContent(payload ?? String.Empty, Encoding.UTF8, Constants.JsonContentType)).ConfigureAwait(false);
}
else if (String.Equals(verb, "put", StringComparison.OrdinalIgnoreCase))
{
response = await client.PutAsync(uri, new StringContent(payload ?? String.Empty, Encoding.UTF8, "application/json")).ConfigureAwait(false);
response = await client.PutAsync(uri, new StringContent(payload ?? String.Empty, Encoding.UTF8, Constants.JsonContentType)).ConfigureAwait(false);
}
else
{

7
BuildPackage.cmd Normal file
Просмотреть файл

@ -0,0 +1,7 @@
@echo off
nuget restore
msbuild
md artifacts
nuget pack -NoPackageAnalysis -OutputDirectory artifacts ARMClient.Console\ARMClient.nuspec

Просмотреть файл

@ -1,7 +1,11 @@
ARMClient
=========
ARMClient facilitates getting token and ARM resource access. The simplest use case is to acquire token `ARMClient.exe login` and http GET CSM resource such as `ARMClient.exe get https://management.azure.com/subscriptions?api-version=2014-04-01`
ARMClient is a simple command line tool to invoke the Azure Resource Manager API. You can install it from [Chocolatey](https://chocolatey.org/) by running:
choco install armclient
This [blog post](http://blog.davidebbo.com/2015/01/azure-resource-manager-client.html) introduces the tool and is a good place to start.
Check out [wiki](https://github.com/projectkudu/ARMClient/wiki) for more details.
@ -9,7 +13,7 @@ Check out [wiki](https://github.com/projectkudu/ARMClient/wiki) for more details
ARMClient.exe login
Call ARM api
ARMClient.exe [get|post|put|delete] [url] (-data <@file|json>) (-verbose)
ARMClient.exe [get|post|put|delete] [url] [@file|json] (-verbose)
Copy token to clipboard
ARMClient.exe token [tenant|subscription]
@ -68,9 +72,12 @@ public class Site
```
The make up of the call is similar to the way CSM Urls are constructed. For example if the Url looks like this
The make up of the call is similar to the way ARM Urls are constructed. For example if the Url looks like this
`https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{webSiteName}/slots/{slotName}/config/web`
Note that you can omit the hostname, and simply have:
`/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{webSiteName}/slots/{slotName}/config/web`
then the `ARMClient` call will be `.Subscriptions["{subscriptionId}"].ResourceGroups["{resourceGroupName}"].Providers["Microsoft.Web"].Sites["{webSiteName}"].Slots["{slotName}"].Config["web"]`
Note: Capitalization is optional `.Subscriptions[""]` == `.subscription[""]` also the distinction between `[]` and `.` is also optional `.Config["web"]` == `.Config.Web`.