From e1d0e67c527e195b0b3aacfa9906f00dccc2ca96 Mon Sep 17 00:00:00 2001 From: Matt Hilton Date: Tue, 14 Jun 2022 16:48:55 +1000 Subject: [PATCH] Ensure HealthCheck respects configured proxy settings Fixes OctopusDeploy/Issues#7544 Updates the HealthCheck to use the new version of the Azure SDKs. It's in beta, so not touching the actual deployment code at this stage. --- source/Calamari/Azure/AzureClient.cs | 22 ++++++++++++++++++ .../Calamari/Azure/AzureKnownEnvironment.cs | 18 ++++++++++++--- source/Calamari/Calamari.csproj | 2 ++ source/Calamari/HealthCheckCommand.cs | 23 +++++++++++++------ 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/source/Calamari/Azure/AzureClient.cs b/source/Calamari/Azure/AzureClient.cs index 75e58216..3d208bfc 100644 --- a/source/Calamari/Azure/AzureClient.cs +++ b/source/Calamari/Azure/AzureClient.cs @@ -1,5 +1,8 @@ using System.Net; using System.Net.Http; +using Azure.Core.Pipeline; +using Azure.Identity; +using Azure.ResourceManager; using Microsoft.Azure.Management.Fluent; using Microsoft.Azure.Management.ResourceManager.Fluent; @@ -23,5 +26,24 @@ public static IAzure CreateAzureClient(this ServicePrincipalAccount servicePrinc .Authenticate(credentials) .WithSubscription(servicePrincipal.SubscriptionNumber); } + + /// + /// Creates an ArmClient for the new Azure SDK, which replaces the older fluent libraries. + /// We should migrate to this SDK once it stabilises. + /// + /// Service Principal Account to use when connecting to Azure + /// + public static ArmClient CreateArmClient(this ServicePrincipalAccount servicePrincipal) + { + var environment = new AzureKnownEnvironment(servicePrincipal.AzureEnvironment).AsAzureArmEnvironment(); + + var httpClientTransport = new HttpClientTransport(new HttpClientHandler { Proxy = WebRequest.DefaultWebProxy }); + + var tokenCredentialOptions = new TokenCredentialOptions { Transport = httpClientTransport }; + var credential = new ClientSecretCredential(servicePrincipal.TenantId, servicePrincipal.ClientId, servicePrincipal.Password, tokenCredentialOptions); + + var armClientOptions = new ArmClientOptions() { Transport = httpClientTransport, Environment = environment }; + return new ArmClient(credential, defaultSubscriptionId: servicePrincipal.SubscriptionNumber, armClientOptions); + } } } diff --git a/source/Calamari/Azure/AzureKnownEnvironment.cs b/source/Calamari/Azure/AzureKnownEnvironment.cs index 39cdac89..5fdd1212 100644 --- a/source/Calamari/Azure/AzureKnownEnvironment.cs +++ b/source/Calamari/Azure/AzureKnownEnvironment.cs @@ -1,4 +1,5 @@ using System; +using Azure.ResourceManager; using Microsoft.Azure.Management.ResourceManager.Fluent; namespace Calamari.Azure @@ -15,11 +16,11 @@ public AzureKnownEnvironment(string environment) if (string.IsNullOrEmpty(environment) || environment == "AzureCloud") // This environment name is defined in Sashimi.Azure.Accounts.AzureEnvironmentsListAction Value = Global.Value; // We interpret it as the normal Azure environment for historical reasons) - azureEnvironment = AzureEnvironment.FromName(Value) ?? + azureSdkEnvironment = AzureEnvironment.FromName(Value) ?? throw new InvalidOperationException($"Unknown environment name {Value}"); } - private readonly AzureEnvironment azureEnvironment; + private readonly AzureEnvironment azureSdkEnvironment; public string Value { get; } public static readonly AzureKnownEnvironment Global = new AzureKnownEnvironment("AzureGlobalCloud"); @@ -29,7 +30,18 @@ public AzureKnownEnvironment(string environment) public AzureEnvironment AsAzureSDKEnvironment() { - return azureEnvironment; + return azureSdkEnvironment; } + + public ArmEnvironment AsAzureArmEnvironment() => ToArmEnvironment(Value); + + private static ArmEnvironment ToArmEnvironment(string name) => name switch + { + "AzureGlobalCloud" => ArmEnvironment.AzurePublicCloud, + "AzureChinaCloud" => ArmEnvironment.AzureChina, + "AzureGermanCloud" => ArmEnvironment.AzureGermany, + "AzureUSGovernment" => ArmEnvironment.AzureGovernment, + _ => throw new InvalidOperationException($"ARM Environment {name} is not a known Azure Environment name.") + }; } } diff --git a/source/Calamari/Calamari.csproj b/source/Calamari/Calamari.csproj index eaa6d44a..4671f9c7 100644 --- a/source/Calamari/Calamari.csproj +++ b/source/Calamari/Calamari.csproj @@ -16,6 +16,8 @@ net5.0 + + diff --git a/source/Calamari/HealthCheckCommand.cs b/source/Calamari/HealthCheckCommand.cs index 7826298a..557a5d7c 100644 --- a/source/Calamari/HealthCheckCommand.cs +++ b/source/Calamari/HealthCheckCommand.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Azure; +using Azure.ResourceManager.AppService; +using Azure.ResourceManager.Resources; using Calamari.Azure; using Calamari.Common.Commands; using Calamari.Common.Plumbing.Pipeline; -using Microsoft.Azure.Management.ResourceManager.Fluent; namespace Calamari.AzureAppService { @@ -34,14 +36,21 @@ public Task Execute(RunningDeployment context) return ConfirmWebAppExists(account, resourceGroupName, webAppName); } - async Task ConfirmWebAppExists(ServicePrincipalAccount servicePrincipal, string resourceGroupName, string siteAndSlotName) + private async Task ConfirmWebAppExists(ServicePrincipalAccount servicePrincipal, string resourceGroupName, string siteAndSlotName) { - var azureClient = servicePrincipal.CreateAzureClient(); - var webApp = await azureClient.WebApps.GetByResourceGroupAsync(resourceGroupName, siteAndSlotName); - if (webApp == null) + var client = servicePrincipal.CreateArmClient(); + var subscription = await client.GetDefaultSubscriptionAsync(); + var resourceGroups = subscription.GetResourceGroups(); + + try + { + ResourceGroupResource resourceGroup = await resourceGroups.GetAsync(resourceGroupName); + _ = await resourceGroup.GetWebSiteAsync(siteAndSlotName); + } + catch (RequestFailedException rfe) when (rfe.Status == 404) { - throw new Exception($"Could not find site {siteAndSlotName} in resource group {resourceGroupName}, using Service Principal with subscription {servicePrincipal.SubscriptionNumber}"); + throw new Exception($"Could not find site {siteAndSlotName} in resource group {resourceGroupName}, using Service Principal with subscription {servicePrincipal.SubscriptionNumber}", rfe); } } } -} \ No newline at end of file +}