public static async Task <PublishingProfile> GetPublishingProfile(TargetSite targetSite, ServicePrincipalAccount account) { string mgmtEndpoint = account.ResourceManagementEndpointBaseUri; var token = new TokenCredentials(await Auth.GetAuthTokenAsync(account)); var azureCredentials = new AzureCredentials( token, token, account.TenantId, new AzureKnownEnvironment(account.AzureEnvironment).AsAzureSDKEnvironment()) .WithDefaultSubscription(account.SubscriptionNumber); var restClient = RestClient .Configure() .WithBaseUri(mgmtEndpoint) .WithEnvironment(new AzureKnownEnvironment(account.AzureEnvironment).AsAzureSDKEnvironment()) .WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic) .WithCredentials(azureCredentials) .Build(); var webAppClient = new WebSiteManagementClient(restClient) { SubscriptionId = account.SubscriptionNumber }; var options = new CsmPublishingProfileOptions { Format = PublishingProfileFormat.WebDeploy }; await webAppClient.WebApps.GetWithHttpMessagesAsync(targetSite.ResourceGroupName, targetSite.Site); using var publishProfileStream = targetSite.HasSlot ? await webAppClient.WebApps.ListPublishingProfileXmlWithSecretsSlotAsync(targetSite.ResourceGroupName, targetSite.Site, options, targetSite.Slot) : await webAppClient.WebApps.ListPublishingProfileXmlWithSecretsAsync(targetSite.ResourceGroupName, targetSite.Site, options); using var streamReader = new StreamReader(publishProfileStream); var document = XDocument.Parse(await streamReader.ReadToEndAsync()); var profile = (from el in document.Descendants("publishProfile") where string.Compare(el.Attribute("publishMethod")?.Value, "MSDeploy", StringComparison.OrdinalIgnoreCase) == 0 select new PublishingProfile { PublishUrl = $"https://{el.Attribute("publishUrl")?.Value}", Username = el.Attribute("userName")?.Value, Password = el.Attribute("userPWD")?.Value, Site = el.Attribute("msdeploySite")?.Value }).FirstOrDefault(); if (profile == null) { throw new Exception("Failed to retrieve publishing profile."); } return(profile); }
public async Task Execute(RunningDeployment context) { // Read/Validate variables Log.Verbose("Starting App Settings Deploy"); var variables = context.Variables; var principalAccount = ServicePrincipalAccount.CreateFromKnownVariables(variables); var webAppName = variables.Get(SpecialVariables.Action.Azure.WebAppName); var slotName = variables.Get(SpecialVariables.Action.Azure.WebAppSlot); if (webAppName == null) { throw new Exception("Web App Name must be specified"); } var resourceGroupName = variables.Get(SpecialVariables.Action.Azure.ResourceGroupName); if (resourceGroupName == null) { throw new Exception("resource group name must be specified"); } var targetSite = AzureWebAppHelper.GetAzureTargetSite(webAppName, slotName, resourceGroupName); string token = await Auth.GetAuthTokenAsync(principalAccount); var webAppClient = new WebSiteManagementClient(new Uri(principalAccount.ResourceManagementEndpointBaseUri), new TokenCredentials(token)) { SubscriptionId = principalAccount.SubscriptionNumber, HttpClient = { BaseAddress = new Uri(principalAccount.ResourceManagementEndpointBaseUri) } }; // If app settings are specified if (variables.GetNames().Contains(SpecialVariables.Action.Azure.AppSettings) && !string.IsNullOrWhiteSpace(variables[SpecialVariables.Action.Azure.AppSettings])) { var appSettingsJson = variables.Get(SpecialVariables.Action.Azure.AppSettings, ""); Log.Verbose($"Updating application settings:\n{appSettingsJson}"); var appSettings = JsonConvert.DeserializeObject <AppSetting[]>(appSettingsJson); await PublishAppSettings(webAppClient, targetSite, appSettings, token); Log.Info("Updated application settings"); } // If connection strings are specified if (variables.GetNames().Contains(SpecialVariables.Action.Azure.ConnectionStrings) && !string.IsNullOrWhiteSpace(variables[SpecialVariables.Action.Azure.ConnectionStrings])) { var connectionStringsJson = variables.Get(SpecialVariables.Action.Azure.ConnectionStrings, ""); Log.Verbose($"Updating connection strings:\n{connectionStringsJson}"); var connectionStrings = JsonConvert.DeserializeObject <ConnectionStringSetting[]>(connectionStringsJson); await PublishConnectionStrings(webAppClient, targetSite, connectionStrings); Log.Info("Updated connection strings"); } }
public Task Execute(RunningDeployment context) { var account = ServicePrincipalAccount.CreateFromKnownVariables(context.Variables); var resourceGroupName = context.Variables.Get(SpecialVariables.Action.Azure.ResourceGroupName); var webAppName = context.Variables.Get(SpecialVariables.Action.Azure.WebAppName); return(ConfirmWebAppExists(account, resourceGroupName, webAppName)); }
public static async Task <string> GetAuthTokenAsync(ServicePrincipalAccount principalAccount) { string authContext = GetContextUri(principalAccount.ActiveDirectoryEndpointBaseUri, principalAccount.TenantId); var context = new AuthenticationContext(authContext); var result = await context.AcquireTokenAsync(principalAccount.ResourceManagementEndpointBaseUri, new ClientCredential(principalAccount.ClientId, principalAccount.Password)); return(result.AccessToken); }
public static async Task <string> GetBasicAuthCreds(ServicePrincipalAccount principalAccount, TargetSite targetSite) { var publishingProfile = await PublishingProfile.GetPublishingProfile(targetSite, principalAccount); string credential = Convert.ToBase64String( Encoding.ASCII.GetBytes($"{publishingProfile.Username}:{publishingProfile.Password}")); return(credential); }
private async Task ConfirmWebAppExists(ServicePrincipalAccount servicePrincipal, string resourceGroupName, string siteAndSlotName) { 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}", rfe); } }
public async Task Execute(RunningDeployment context) { var variables = context.Variables; var principalAccount = ServicePrincipalAccount.CreateFromKnownVariables(variables); var webAppName = variables.Get(SpecialVariables.Action.Azure.WebAppName); var slotName = variables.Get(SpecialVariables.Action.Azure.WebAppSlot); var rgName = variables.Get(SpecialVariables.Action.Azure.ResourceGroupName); var targetSite = AzureWebAppHelper.GetAzureTargetSite(webAppName, slotName, rgName); var image = variables.Get(SpecialVariables.Action.Package.Image); var registryHost = variables.Get(SpecialVariables.Action.Package.Registry); var regUsername = variables.Get(SpecialVariables.Action.Package.Feed.Username); var regPwd = variables.Get(SpecialVariables.Action.Package.Feed.Password); var token = await Auth.GetAuthTokenAsync(principalAccount); var webAppClient = new WebSiteManagementClient(new Uri(principalAccount.ResourceManagementEndpointBaseUri), new TokenCredentials(token)) { SubscriptionId = principalAccount.SubscriptionNumber }; Log.Info($"Updating web app to use image {image} from registry {registryHost}"); Log.Verbose("Retrieving config (this is required to update image)"); var config = await webAppClient.WebApps.GetConfigurationAsync(targetSite); config.LinuxFxVersion = $@"DOCKER|{image}"; Log.Verbose("Retrieving app settings"); var appSettings = await webAppClient.WebApps.ListApplicationSettingsAsync(targetSite); appSettings.Properties["DOCKER_REGISTRY_SERVER_URL"] = "https://" + registryHost; appSettings.Properties["DOCKER_REGISTRY_SERVER_USERNAME"] = regUsername; appSettings.Properties["DOCKER_REGISTRY_SERVER_PASSWORD"] = regPwd; Log.Info("Updating app settings with container registry"); await webAppClient.WebApps.UpdateApplicationSettingsAsync(targetSite, appSettings); Log.Info("Updating configuration with container image"); await webAppClient.WebApps.UpdateConfigurationAsync(targetSite, config); }
public async Task Execute(RunningDeployment context) { var variables = context.Variables; var webAppName = variables.Get(SpecialVariables.Action.Azure.WebAppName); var slotName = variables.Get(SpecialVariables.Action.Azure.WebAppSlot); var resourceGroupName = variables.Get(SpecialVariables.Action.Azure.ResourceGroupName); var targetSite = AzureWebAppHelper.GetAzureTargetSite(webAppName, slotName, resourceGroupName); var principalAccount = ServicePrincipalAccount.CreateFromKnownVariables(variables); var token = await Auth.GetAuthTokenAsync(principalAccount); var webAppClient = new WebSiteManagementClient(new Uri(principalAccount.ResourceManagementEndpointBaseUri), new TokenCredentials(token)) { SubscriptionId = principalAccount.SubscriptionNumber }; Log.Info("Performing soft restart of web app"); await webAppClient.WebApps.RestartAsync(targetSite, true); }
public void DiscoverKubernetesClusterWithAzureServicePrincipalAccount() { var serviceMessageCollectorLog = new ServiceMessageCollectorLog(); Log = serviceMessageCollectorLog; var scope = new TargetDiscoveryScope("TestSpace", "Staging", "testProject", null, new[] { "discovery-role" }, "WorkerPool-1"); var account = new ServicePrincipalAccount( ExternalVariables.Get(ExternalVariable.AzureSubscriptionId), ExternalVariables.Get(ExternalVariable.AzureSubscriptionClientId), ExternalVariables.Get(ExternalVariable.AzureSubscriptionTenantId), ExternalVariables.Get(ExternalVariable.AzureSubscriptionPassword), null, null, null); var authenticationDetails = new AccountAuthenticationDetails <ServicePrincipalAccount>( "Azure", "Accounts-1", account); var targetDiscoveryContext = new TargetDiscoveryContext <AccountAuthenticationDetails <ServicePrincipalAccount> >(scope, authenticationDetails); var result = ExecuteDiscoveryCommand(targetDiscoveryContext, new[] { "Calamari.Azure" } ); result.AssertSuccess(); var targetName = $"aks/{azureSubscriptionId}/{azurermResourceGroup}/{aksClusterName}"; var expectedServiceMessage = new ServiceMessage( KubernetesDiscoveryCommand.CreateKubernetesTargetServiceMessageName, new Dictionary <string, string> { { "name", targetName }, { "clusterName", aksClusterName }, { "clusterResourceGroup", azurermResourceGroup }, { "skipTlsVerification", bool.TrueString }, { "octopusDefaultWorkerPoolIdOrName", scope.WorkerPoolId }, { "octopusAccountIdOrName", "Accounts-1" }, { "octopusRoles", "discovery-role" }, { "updateIfExisting", bool.TrueString }, { "isDynamic", bool.TrueString }, { "awsUseWorkerCredentials", bool.FalseString }, { "awsAssumeRole", bool.FalseString }, }); serviceMessageCollectorLog.ServiceMessages.Should() .ContainSingle(s => s.Properties["name"] == targetName) .Which.Should() .BeEquivalentTo(expectedServiceMessage); }
public async Task Execute(RunningDeployment context) { var variables = context.Variables; var servicePrincipal = ServicePrincipalAccount.CreateFromKnownVariables(variables); string?webAppName = variables.Get(SpecialVariables.Action.Azure.WebAppName); if (webAppName == null) { throw new Exception("Web App Name must be specified"); } string?resourceGroupName = variables.Get(SpecialVariables.Action.Azure.ResourceGroupName); if (resourceGroupName == null) { throw new Exception("resource group name must be specified"); } string?slotName = variables.Get(SpecialVariables.Action.Azure.WebAppSlot); var packageFileInfo = new FileInfo(variables.Get(TentacleVariables.CurrentDeployment.PackageFilePath) !); switch (packageFileInfo.Extension) { case ".zip": Archive = new ZipPackageProvider(); break; case ".nupkg": Archive = new NugetPackageProvider(); break; case ".war": Archive = new WarPackageProvider(Log, variables, context); break; default: throw new Exception("Unsupported archive type"); } var azureClient = servicePrincipal.CreateAzureClient(); var webApp = await azureClient.WebApps.GetByResourceGroupAsync(resourceGroupName, webAppName); var targetSite = AzureWebAppHelper.GetAzureTargetSite(webAppName, slotName, resourceGroupName); // Lets process our archive while the slot is spun up. we will await it later before we try to upload to it. var slotCreateTask = new Task(() => { }); if (targetSite.HasSlot) { slotCreateTask = FindOrCreateSlot(webApp, targetSite); } string[]? substitutionFeatures = { KnownVariables.Features.ConfigurationTransforms, KnownVariables.Features.StructuredConfigurationVariables, KnownVariables.Features.SubstituteInFiles }; /* * Calamari default behaviors * https://github.com/OctopusDeploy/Calamari/tree/master/source/Calamari.Common/Features/Behaviours */ var uploadPath = string.Empty; if (substitutionFeatures.Any(featureName => context.Variables.IsFeatureEnabled(featureName))) { uploadPath = (await Archive.PackageArchive(context.StagingDirectory, context.CurrentDirectory)) .FullName; } else { uploadPath = (await Archive.ConvertToAzureSupportedFile(packageFileInfo)).FullName; } if (uploadPath == null) { throw new Exception("Package File Path must be specified"); } // need to ensure slot is created as slot creds may be used if (targetSite.HasSlot) { await slotCreateTask; } var publishingProfile = await PublishingProfile.GetPublishingProfile(targetSite, servicePrincipal); string?credential = await Auth.GetBasicAuthCreds(servicePrincipal, targetSite); string token = await Auth.GetAuthTokenAsync(servicePrincipal); var webAppClient = new WebSiteManagementClient(new Uri(servicePrincipal.ResourceManagementEndpointBaseUri), new TokenCredentials(token)) { SubscriptionId = servicePrincipal.SubscriptionNumber }; var httpClient = webAppClient.HttpClient; httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credential); Log.Info($"Uploading package to {targetSite.SiteAndSlot}"); await UploadZipAsync(publishingProfile, httpClient, uploadPath, targetSite.ScmSiteAndSlot); }