<commit_before>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SetupStepFactory.cs" company="Naos">
//    Copyright (c) Naos 2017. All Rights Reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Naos.Deployment.Core
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Security.Cryptography.X509Certificates;
    using System.Threading.Tasks;

    using Its.Log.Instrumentation;

    using Naos.Deployment.Domain;
    using Naos.Logging.Domain;
    using Naos.Packaging.Domain;

    using Spritely.Recipes;

    using static System.FormattableString;

    /// <summary>
    /// Factory to create a list of setup steps from various situations (abstraction to actual machine setup).
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Like it this way.")]
    internal partial class SetupStepFactory
    {
        private readonly IGetCertificates certificateRetriever;

        private readonly IGetPackages packageManager;

        private readonly IManageConfigFiles configFileManager;

        private readonly string environmentCertificateName;

        private readonly string workingDirectory;

        private readonly Action<string> debugAnnouncer;

        /// <summary>
        /// Initializes a new instance of the <see cref="SetupStepFactory"/> class.
        /// </summary>
        /// <param name="settings">Settings for the factory.</param>
        /// <param name="certificateRetriever">Certificate retriever to get certificates for steps.</param>
        /// <param name="packageManager">Package manager to use for getting package files contents.</param>
        /// <param name="configFileManager">Config file manager to use when creating or manipulating config files.</param>
        /// <param name="environmentCertificateName">Optional name of the environment certificate to be found in the CertificateManager provided.</param>
        /// <param name="workingDirectory">Working directory to create scratch files.</param>
        /// <param name="debugAnnouncer">Announcer for events.</param>
        public SetupStepFactory(SetupStepFactorySettings settings, IGetCertificates certificateRetriever, IGetPackages packageManager, IManageConfigFiles configFileManager, string environmentCertificateName, string workingDirectory, Action<string> debugAnnouncer)
        {
            this.Settings = settings;
            this.packageManager = packageManager;
            this.certificateRetriever = certificateRetriever;
            this.configFileManager = configFileManager;
            this.environmentCertificateName = environmentCertificateName;
            this.workingDirectory = workingDirectory;
            this.debugAnnouncer = debugAnnouncer;
        }

        /// <summary>
        /// Gets the administrator account name.
        /// </summary>
        public string AdministratorAccount => this.Settings.AdministratorAccount;

        /// <summary>
        /// Gets the initialization strategy types that require the package bytes to be copied up to the target server.
        /// </summary>
        public IReadOnlyCollection<Type> InitializationStrategyTypesThatNeedPackageBytes => this.Settings.InitializationStrategyTypesThatNeedPackageBytes;

        /// <summary>
        /// Gets the initialization strategy types that require the package bytes to be copied up to the target server.
        /// </summary>
        public IReadOnlyCollection<Type> InitializationStrategyTypesThatNeedEnvironmentCertificate => this.Settings.InitializationStrategyTypesThatNeedEnvironmentCertificate;

        /// <summary>
        /// Gets the max number of times to execute a setup step before throwing.
        /// </summary>
        public int MaxSetupStepAttempts => this.Settings.MaxSetupStepAttempts;

        /// <summary>
        /// Gets a value indicating whether or not to throw if the max attempts are not successful on a setup step.
        /// </summary>
        public bool ThrowOnFailedSetupStep => this.Settings.ThrowOnFailedSetupStep;

        /// <summary>
        /// Gets the list of directories we've found people add to packages and contain assemblies that fail to load correctly in reflection and are not be necessary for normal function.
        /// </summary>
        public IReadOnlyCollection<string> RootPackageDirectoriesToPrune => this.Settings.RootPackageDirectoriesToPrune;

        /// <summary>
        /// Gets the underlying settings used to create the factory.
        /// </summary>
        public SetupStepFactorySettings Settings { get; private set; }

        /// <summary>
        /// Get the appropriate setup steps for the packaged config.
        /// </summary>
        /// <param name="packagedConfig">Config to base setup steps from.</param>
        /// <param name="environment">Environment that is being deployed.</param>
        /// <param name="adminPassword">Administrator password for the machine in case an application needs to be run as that user (which is discouraged!).</param>
        /// <param name="funcToCreateNewDnsWithTokensReplaced">Function to apply any token replacements to a DNS entry.</param>
        /// <returns>Collection of setup steps that will leave the machine properly configured.</returns>
        public async Task<IReadOnlyCollection<SetupStepBatch>> GetSetupStepsAsync(PackagedDeploymentConfiguration packagedConfig, string environment, string adminPassword, Func<string, string> funcToCreateNewDnsWithTokensReplaced)
        {
            ThrowIfMultipleMongoStrategiesAreInvalidCombination(packagedConfig.GetInitializationStrategiesOf<InitializationStrategyMongo>());

            ThrowIfCertificatesNotProperlyConfigured(packagedConfig.GetInitializationStrategiesOf<InitializationStrategyCertificateToInstall>(), packagedConfig.GetInitializationStrategiesOf<InitializationStrategySelfHost>(), packagedConfig.GetInitializationStrategiesOf<InitializationStrategyIis>());

            ThrowIfMissingNecessaryVolumes(packagedConfig.DeploymentConfiguration.Volumes, packagedConfig.GetInitializationStrategiesOf<InitializationStrategySqlServer>(), packagedConfig.GetInitializationStrategiesOf<InitializationStrategyMongo>());

            var ret = new List<SetupStepBatch>();

            var distinctInitializationStrategyTypes = packagedConfig.InitializationStrategies.Select(_ => _.GetType()).Distinct().ToList();

            // only copy the package byes if there are initialization strategies on the package that require it...
            if (distinctInitializationStrategyTypes.Any(_ => this.InitializationStrategyTypesThatNeedPackageBytes.Contains(_)))
            {
                var deployUnzippedFileStep = this.GetCopyAndUnzipPackageStep(packagedConfig);
                var batch = new SetupStepBatch
                                {
                                    ExecutionOrder = ExecutionOrder.CopyPackages,
                                    Steps = new[] { deployUnzippedFileStep },
                                };
                ret.Add(batch);
            }

            if (distinctInitializationStrategyTypes.OfType<InitializationStrategyIis>().Any())
            {
                var installIisSteps = this.GetIisInstallSteps();
                var batch = new SetupStepBatch { ExecutionOrder = ExecutionOrder.InstallIis, Steps = installIisSteps, };
                ret.Add(batch);
            }

            if (distinctInitializationStrategyTypes.OfType<InitializationStrategyMongo>().Any())
            {
                var installMongoSteps = this.GetInstallMongoSteps();
                var batch = new SetupStepBatch { ExecutionOrder = ExecutionOrder.InstallMongo, Steps = installMongoSteps, };
                ret.Add(batch);
            }

            foreach (var initializationStrategy in packagedConfig.InitializationStrategies)
            {
                var initSteps = await this.GetStrategySpecificSetupStepBatchesAsync(initializationStrategy, packagedConfig, environment, adminPassword, funcToCreateNewDnsWithTokensReplaced);
                ret.AddRange(initSteps);
            }

            return ret;
        }

        private async Task<IReadOnlyCollection<SetupStepBatch>> GetStrategySpecificSetupStepBatchesAsync(InitializationStrategyBase strategy, PackagedDeploymentConfiguration packagedConfig, string environment, string adminPassword, Func<string, string> funcToCreateNewDnsWithTokensReplaced)
        {
            IReadOnlyCollection<SetupStepBatch> ret = null;
            var package = packagedConfig.PackageWithBundleIdentifier.Package;
            var packageId = package.PackageDescription.Id;
            var packageDirectoryPath = this.GetPackageDirectoryPath(packagedConfig);
            var defaultLogProcessorSettings = this.GetDefaultLogProcessorSettings(packagedConfig);

            if (strategy is InitializationStrategyReplaceTokenInFiles replaceTokenStrategy)
            {
                var tokenSteps = this.GetReplaceTokenInFilesSpecificSteps(replaceTokenStrategy, packageId, packageDirectoryPath, funcToCreateNewDnsWithTokensReplaced);

                ret = new[]
                          {
                              new SetupStepBatch
                                  {<commit_msg>Updated GetStrategySpecificSetupStepxxxx to default to an empty array.<commit_after>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SetupStepFactory.cs" company="Naos">
//    Copyright (c) Naos 2017. All Rights Reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Naos.Deployment.Core
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Security.Cryptography.X509Certificates;
    using System.Threading.Tasks;

    using Its.Log.Instrumentation;

    using Naos.Deployment.Domain;
    using Naos.Logging.Domain;
    using Naos.Packaging.Domain;

    using Spritely.Recipes;

    using static System.FormattableString;

    /// <summary>
    /// Factory to create a list of setup steps from various situations (abstraction to actual machine setup).
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Like it this way.")]
    internal partial class SetupStepFactory
    {
        private readonly IGetCertificates certificateRetriever;

        private readonly IGetPackages packageManager;

        private readonly IManageConfigFiles configFileManager;

        private readonly string environmentCertificateName;

        private readonly string workingDirectory;

        private readonly Action<string> debugAnnouncer;

        /// <summary>
        /// Initializes a new instance of the <see cref="SetupStepFactory"/> class.
        /// </summary>
        /// <param name="settings">Settings for the factory.</param>
        /// <param name="certificateRetriever">Certificate retriever to get certificates for steps.</param>
        /// <param name="packageManager">Package manager to use for getting package files contents.</param>
        /// <param name="configFileManager">Config file manager to use when creating or manipulating config files.</param>
        /// <param name="environmentCertificateName">Optional name of the environment certificate to be found in the CertificateManager provided.</param>
        /// <param name="workingDirectory">Working directory to create scratch files.</param>
        /// <param name="debugAnnouncer">Announcer for events.</param>
        public SetupStepFactory(SetupStepFactorySettings settings, IGetCertificates certificateRetriever, IGetPackages packageManager, IManageConfigFiles configFileManager, string environmentCertificateName, string workingDirectory, Action<string> debugAnnouncer)
        {
            this.Settings = settings;
            this.packageManager = packageManager;
            this.certificateRetriever = certificateRetriever;
            this.configFileManager = configFileManager;
            this.environmentCertificateName = environmentCertificateName;
            this.workingDirectory = workingDirectory;
            this.debugAnnouncer = debugAnnouncer;
        }

        /// <summary>
        /// Gets the administrator account name.
        /// </summary>
        public string AdministratorAccount => this.Settings.AdministratorAccount;

        /// <summary>
        /// Gets the initialization strategy types that require the package bytes to be copied up to the target server.
        /// </summary>
        public IReadOnlyCollection<Type> InitializationStrategyTypesThatNeedPackageBytes => this.Settings.InitializationStrategyTypesThatNeedPackageBytes;

        /// <summary>
        /// Gets the initialization strategy types that require the package bytes to be copied up to the target server.
        /// </summary>
        public IReadOnlyCollection<Type> InitializationStrategyTypesThatNeedEnvironmentCertificate => this.Settings.InitializationStrategyTypesThatNeedEnvironmentCertificate;

        /// <summary>
        /// Gets the max number of times to execute a setup step before throwing.
        /// </summary>
        public int MaxSetupStepAttempts => this.Settings.MaxSetupStepAttempts;

        /// <summary>
        /// Gets a value indicating whether or not to throw if the max attempts are not successful on a setup step.
        /// </summary>
        public bool ThrowOnFailedSetupStep => this.Settings.ThrowOnFailedSetupStep;

        /// <summary>
        /// Gets the list of directories we've found people add to packages and contain assemblies that fail to load correctly in reflection and are not be necessary for normal function.
        /// </summary>
        public IReadOnlyCollection<string> RootPackageDirectoriesToPrune => this.Settings.RootPackageDirectoriesToPrune;

        /// <summary>
        /// Gets the underlying settings used to create the factory.
        /// </summary>
        public SetupStepFactorySettings Settings { get; private set; }

        /// <summary>
        /// Get the appropriate setup steps for the packaged config.
        /// </summary>
        /// <param name="packagedConfig">Config to base setup steps from.</param>
        /// <param name="environment">Environment that is being deployed.</param>
        /// <param name="adminPassword">Administrator password for the machine in case an application needs to be run as that user (which is discouraged!).</param>
        /// <param name="funcToCreateNewDnsWithTokensReplaced">Function to apply any token replacements to a DNS entry.</param>
        /// <returns>Collection of setup steps that will leave the machine properly configured.</returns>
        public async Task<IReadOnlyCollection<SetupStepBatch>> GetSetupStepsAsync(PackagedDeploymentConfiguration packagedConfig, string environment, string adminPassword, Func<string, string> funcToCreateNewDnsWithTokensReplaced)
        {
            ThrowIfMultipleMongoStrategiesAreInvalidCombination(packagedConfig.GetInitializationStrategiesOf<InitializationStrategyMongo>());

            ThrowIfCertificatesNotProperlyConfigured(packagedConfig.GetInitializationStrategiesOf<InitializationStrategyCertificateToInstall>(), packagedConfig.GetInitializationStrategiesOf<InitializationStrategySelfHost>(), packagedConfig.GetInitializationStrategiesOf<InitializationStrategyIis>());

            ThrowIfMissingNecessaryVolumes(packagedConfig.DeploymentConfiguration.Volumes, packagedConfig.GetInitializationStrategiesOf<InitializationStrategySqlServer>(), packagedConfig.GetInitializationStrategiesOf<InitializationStrategyMongo>());

            var ret = new List<SetupStepBatch>();

            var distinctInitializationStrategyTypes = packagedConfig.InitializationStrategies.Select(_ => _.GetType()).Distinct().ToList();

            // only copy the package byes if there are initialization strategies on the package that require it...
            if (distinctInitializationStrategyTypes.Any(_ => this.InitializationStrategyTypesThatNeedPackageBytes.Contains(_)))
            {
                var deployUnzippedFileStep = this.GetCopyAndUnzipPackageStep(packagedConfig);
                var batch = new SetupStepBatch
                                {
                                    ExecutionOrder = ExecutionOrder.CopyPackages,
                                    Steps = new[] { deployUnzippedFileStep },
                                };
                ret.Add(batch);
            }

            if (distinctInitializationStrategyTypes.OfType<InitializationStrategyIis>().Any())
            {
                var installIisSteps = this.GetIisInstallSteps();
                var batch = new SetupStepBatch { ExecutionOrder = ExecutionOrder.InstallIis, Steps = installIisSteps, };
                ret.Add(batch);
            }

            if (distinctInitializationStrategyTypes.OfType<InitializationStrategyMongo>().Any())
            {
                var installMongoSteps = this.GetInstallMongoSteps();
                var batch = new SetupStepBatch { ExecutionOrder = ExecutionOrder.InstallMongo, Steps = installMongoSteps, };
                ret.Add(batch);
            }

            foreach (var initializationStrategy in packagedConfig.InitializationStrategies)
            {
                var initSteps = await this.GetStrategySpecificSetupStepBatchesAsync(initializationStrategy, packagedConfig, environment, adminPassword, funcToCreateNewDnsWithTokensReplaced);
                ret.AddRange(initSteps);
            }

            return ret;
        }

        private async Task<IReadOnlyCollection<SetupStepBatch>> GetStrategySpecificSetupStepBatchesAsync(InitializationStrategyBase strategy, PackagedDeploymentConfiguration packagedConfig, string environment, string adminPassword, Func<string, string> funcToCreateNewDnsWithTokensReplaced)
        {
            IReadOnlyCollection<SetupStepBatch> ret = new SetupStepBatch[0];
            var package = packagedConfig.PackageWithBundleIdentifier.Package;
            var packageId = package.PackageDescription.Id;
            var packageDirectoryPath = this.GetPackageDirectoryPath(packagedConfig);
            var defaultLogProcessorSettings = this.GetDefaultLogProcessorSettings(packagedConfig);

            if (strategy is InitializationStrategyReplaceTokenInFiles replaceTokenStrategy)
            {
                var tokenSteps = this.GetReplaceTokenInFilesSpecificSteps(replaceTokenStrategy, packageId, packageDirectoryPath, funcToCreateNewDnsWithTokensReplaced);

                ret = new[]
                          {
                              new SetupStepBatch
                                  {
{'fraction_non_alphanumeric': 0.09954114089060381, 'fraction_numerical': 0.0011992908541036604, 'mean_word_length': 2.684018440261237, 'pattern_counts': {'":': 0, '<': 139, '<?xml version=': 0, '>': 155, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 7, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '820717', 'n_tokens_mistral': 4382, 'n_tokens_neox': 4204, 'n_words': 1130}