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));
        }
Пример #4
0
        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);
        }
Пример #5
0
        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);
            }
        }
Пример #7
0
        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);
        }
Пример #8
0
        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);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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);
        }