public async Task <bool> Init(
            Settings settings,
            bool allowDataLoos,
            bool protectStack,
            bool forceDeploy,
            VersionInfo version,
            string lambdaSharpPath,
            string parametersFilename,
            bool forcePublish,
            bool promptAllParameters,
            XRayTracingLevel xRayTracingLevel,
            CoreServices coreServices,
            string existingS3BucketName,
            bool allowUpgrade
            )
        {
            // NOTE (2019-08-15, bjorg): the deployment tier initialization must support the following scenarios:
            //  1. New deployment tier
            //  2. Updating an existing tier with any configuration changes
            //  3. Upgrading an existing tier to enable LambdaSharp.Core services
            //  4. Downgrading an existing tier to disable LambdaSharp.Core services

            // read the current deployment tier settings if possible
            await PopulateDeploymentTierSettingsAsync(
                settings,

                // bucket name and core services settings may be missing for deployment tier v0.6 or earlier
                requireBucketName : false,
                requireCoreServices : false,

                // version is more explicitly checked below
                requireVersionCheck : false,

                // deployment tier may not exist yet
                optional : true
                );

            if (HasErrors)
            {
                return(false);
            }

            // check if a new installation is required
            var createNewTier      = (settings.TierVersion == null);
            var updateExistingTier = false;

            if (!createNewTier)
            {
                // if core services state is not requested, inherit current state
                if (coreServices == CoreServices.Undefined)
                {
                    coreServices = settings.CoreServices;
                }

                // determine if the deployment tier needs to be updated
                var tierToToolVersionComparison = settings.TierVersion.CompareToVersion(settings.CoreServicesVersion);
                if (tierToToolVersionComparison == 0)
                {
                    // versions are identical; nothing to do, unless we're forced to update
                    updateExistingTier = forceDeploy

                                         // it's a pre-release, which always needs to be updated
                                         || settings.CoreServicesVersion.IsPreRelease

                                         // deployment tier is running core services state is different from requested state
                                         || (settings.CoreServices != coreServices);
                }
                else if (tierToToolVersionComparison > 0)
                {
                    // tier is newer; tool needs to get updated
                    LogError($"LambdaSharp tool is out of date (tool: {settings.CoreServicesVersion}, tier: {settings.TierVersion})", new LambdaSharpToolOutOfDateException(settings.TierVersion));
                    return(false);
                }
                else if (tierToToolVersionComparison < 0)
                {
                    // tier is older; let's only upgrade it if requested
                    updateExistingTier = true;

                    // tool version is more recent; if it's a minor update, proceed without prompting, otherwise ask user to confirm upgrade
                    if (!settings.TierVersion.IsCoreServicesCompatible(settings.CoreServicesVersion) && !allowUpgrade)
                    {
                        Console.WriteLine($"LambdaSharp Tier is out of date");
                        updateExistingTier = settings.PromptYesNo($"Do you want to upgrade LambdaSharp Tier '{settings.TierName}' from v{settings.TierVersion} to v{settings.CoreServicesVersion}?", defaultAnswer: false);
                    }
                    if (!updateExistingTier)
                    {
                        return(false);
                    }
                }
                else if (!forceDeploy)
                {
                    LogError($"Could not determine if LambdaSharp tool is compatible (tool: {settings.CoreServicesVersion}, tier: {settings.TierVersion}); use --force-deploy to proceed anyway");
                    return(false);
                }
                else
                {
                    // force deploy it is!
                    updateExistingTier = true;
                }
            }

            // check if deployment tier with disabled core services needs to be created/updated
            Dictionary <string, string> parameters = null;
            var tierCommand = new CliTierCommand();
            var updated     = false;

            if (
                createNewTier ||
                (updateExistingTier && (

                     // deployment tier doesn't have core services (pre-0.7); so the bootstrap stack needs to be installed first
                     (settings.CoreServices == CoreServices.Undefined)

                     // deployment tier core services need to be disabled
                     || (coreServices == CoreServices.Disabled)
                     ))
                )
            {
                // deploy bootstrap stack with disabled core services
                if (!await DeployCoreServicesDisabledTemplate())
                {
                    return(false);
                }
                updated = true;
            }

            // check if API Gateway role needs to be set or updated
            await CheckApiGatewayRole(settings);

            if (HasErrors)
            {
                return(false);
            }

            // standard modules
            var standardModules = new[] {
                "LambdaSharp.Core",
                "LambdaSharp.S3.IO",
                "LambdaSharp.S3.Subscriber",
                "LambdaSharp.Twitter.Query"
            };

            // check if the module must be built and published first (only applicable when running lash in contributor mode)
            var buildPublishDeployCommand = new CliBuildPublishDeployCommand();

            if (lambdaSharpPath != null)
            {
                Console.WriteLine($"Building LambdaSharp modules");

                // attempt to parse the tool version from environment variables
                if (!VersionInfo.TryParse(Environment.GetEnvironmentVariable("LAMBDASHARP_VERSION"), out var moduleVersion))
                {
                    LogError("unable to parse module version from LAMBDASHARP_VERSION");
                    return(false);
                }
                foreach (var module in standardModules)
                {
                    var moduleSource = Path.Combine(lambdaSharpPath, "Modules", module, "Module.yml");
                    settings.WorkingDirectory = Path.GetDirectoryName(moduleSource);
                    settings.OutputDirectory  = Path.Combine(settings.WorkingDirectory, "bin");

                    // build local module
                    if (!await buildPublishDeployCommand.BuildStepAsync(
                            settings,
                            Path.Combine(settings.OutputDirectory, "cloudformation.json"),
                            noAssemblyValidation: true,
                            noPackageBuild: false,
                            gitSha: GetGitShaValue(settings.WorkingDirectory, showWarningOnFailure: false),
                            gitBranch: GetGitBranch(settings.WorkingDirectory, showWarningOnFailure: false),
                            buildConfiguration: "Release",
                            selector: null,
                            moduleSource: moduleSource,
                            moduleVersion: moduleVersion
                            ))
                    {
                        return(false);
                    }

                    // publish module
                    var moduleReference = await buildPublishDeployCommand.PublishStepAsync(settings, forcePublish, moduleOrigin : "lambdasharp");

                    if (moduleReference == null)
                    {
                        return(false);
                    }
                }
            }
            else
            {
                // explicitly import the LambdaSharp.Core module (if it wasn't built locally)
                if (!await buildPublishDeployCommand.ImportStepAsync(
                        settings,
                        ModuleInfo.Parse($"LambdaSharp.Core:{version}@lambdasharp"),
                        forcePublish: false
                        ))
                {
                    return(false);
                }
            }

            // check if core services do not need to be updated further
            if (coreServices == CoreServices.Disabled)
            {
                if (!updated)
                {
                    Console.WriteLine();
                    Console.WriteLine("No update required");
                }
                return(true);
            }

            // check if operating services need to be installed/updated
            if (createNewTier)
            {
                Console.WriteLine();
                Console.WriteLine($"Creating new deployment tier '{settings.TierName}'");
            }
            else if (updateExistingTier)
            {
                Console.WriteLine();
                Console.WriteLine($"Updating deployment tier '{settings.TierName}'");
            }
            else
            {
                if (!updated)
                {
                    Console.WriteLine();
                    Console.WriteLine("No update required");
                }
                return(true);
            }

            // read parameters if they haven't been read yet
            if (parameters == null)
            {
                parameters = (parametersFilename != null)
                    ? CliBuildPublishDeployCommand.ReadInputParametersFiles(settings, parametersFilename)
                    : new Dictionary <string, string>();
                if (HasErrors)
                {
                    return(false);
                }
            }

            // deploy LambdaSharp module
            foreach (var module in standardModules)
            {
                var isLambdaSharpCoreModule = (module == "LambdaSharp.Core");
                if (!await buildPublishDeployCommand.DeployStepAsync(
                        settings,
                        dryRun: null,
                        moduleReference: $"{module}:{version}@lambdasharp",
                        instanceName: null,
                        allowDataLoos: allowDataLoos,
                        protectStack: protectStack,
                        parameters: parameters,
                        forceDeploy: forceDeploy,
                        promptAllParameters: promptAllParameters,
                        xRayTracingLevel: xRayTracingLevel,
                        deployOnlyIfExists: !isLambdaSharpCoreModule
                        ))
                {
                    return(false);
                }

                // reset tier version if core module was deployed; this will force the tier settings to be refetched
                if (isLambdaSharpCoreModule)
                {
                    await PopulateDeploymentTierSettingsAsync(settings, force : true);
                }
            }

            // check if core services need to be enabled for deployed modules
            if (settings.CoreServices == CoreServices.Enabled)
            {
                await tierCommand.UpdateCoreServicesAsync(settings, enabled : true, showModules : false);
            }
            return(!HasErrors);

            // local function
            async Task <bool> DeployCoreServicesDisabledTemplate()
            {
                // initialize stack with seed CloudFormation template
                var template = ReadResource("LambdaSharpCore.yml", new Dictionary <string, string> {
                    ["CORE-VERSION"] = settings.CoreServicesVersion.ToString(),
                    ["TOOL-VERSION"] = settings.ToolVersion.ToString(),
                    ["CHECKSUM"]     = settings.ToolVersion.ToString().ToMD5Hash()
                });

                // check if bootstrap template is being updated or installed
                if (createNewTier)
                {
                    Console.WriteLine($"Creating LambdaSharp tier");
                }
                else
                {
                    Console.WriteLine($"Updating LambdaSharp tier");
                }

                // create lambdasharp CLI bootstrap stack
                var stackName = $"{settings.TierPrefix}LambdaSharp-Core";

                parameters = (parametersFilename != null)
                    ? CliBuildPublishDeployCommand.ReadInputParametersFiles(settings, parametersFilename)
                    : new Dictionary <string, string>();
                if (HasErrors)
                {
                    return(false);
                }
                var bootstrapParameters = new Dictionary <string, string>(parameters)
                {
                    ["TierName"] = settings.Tier
                };

                // check if command line options were provided to set template parameters
                if ((coreServices == CoreServices.Enabled) || (coreServices == CoreServices.Disabled))
                {
                    bootstrapParameters["CoreServices"] = coreServices.ToString();
                }
                if (existingS3BucketName != null)
                {
                    bootstrapParameters["ExistingDeploymentBucket"] = existingS3BucketName;
                }

                // prompt for missing parameters
                var templateParameters = await PromptMissingTemplateParameters(
                    settings,
                    stackName,
                    bootstrapParameters,
                    template
                    );

                if (coreServices == CoreServices.Undefined)
                {
                    // determine wanted core services state from template parameters
                    var coreServicesValue = templateParameters.First(parameter => parameter.ParameterKey == "CoreServices")?.ParameterValue;
                    if (!Enum.TryParse <CoreServices>(coreServicesValue, ignoreCase: true, out coreServices))
                    {
                        LogError($"unable to parse CoreServices value from template parameters (found: '{coreServicesValue}')");
                        return(false);
                    }
                }
                if (HasErrors)
                {
                    return(false);
                }

                // disable core services in all deployed modules
                if (!createNewTier)
                {
                    await tierCommand.UpdateCoreServicesAsync(settings, enabled : false, showModules : false);

                    if (HasErrors)
                    {
                        return(false);
                    }
                }

                // create/update cloudformation stack
                if (createNewTier)
                {
                    Console.WriteLine($"=> Stack creation initiated for {stackName}");
                    var response = await settings.CfnClient.CreateStackAsync(new CreateStackRequest {
                        StackName    = stackName,
                        Capabilities = new List <string> {
                        },
                        OnFailure    = OnFailure.DELETE,
                        Parameters   = templateParameters,
                        EnableTerminationProtection = protectStack,
                        TemplateBody = template,
                        Tags         = settings.GetCloudFormationStackTags("LambdaSharp.Core", stackName)
                    });

                    var created = await settings.CfnClient.TrackStackUpdateAsync(stackName, response.StackId, mostRecentStackEventId : null, logError : LogError);

                    if (created.Success)
                    {
                        Console.WriteLine("=> Stack creation finished");
                    }
                    else
                    {
                        Console.WriteLine("=> Stack creation FAILED");
                        return(false);
                    }
                }
                else
                {
                    Console.WriteLine($"=> Stack update initiated for {stackName}");
                    try {
                        var mostRecentStackEventId = await settings.CfnClient.GetMostRecentStackEventIdAsync(stackName);

                        var response = await settings.CfnClient.UpdateStackAsync(new UpdateStackRequest {
                            StackName    = stackName,
                            Capabilities = new List <string> {
                            },
                            Parameters   = templateParameters,
                            TemplateBody = template,
                            Tags         = settings.GetCloudFormationStackTags("LambdaSharp.Core", stackName)
                        });

                        var created = await settings.CfnClient.TrackStackUpdateAsync(stackName, response.StackId, mostRecentStackEventId, logError : LogError);

                        if (created.Success)
                        {
                            Console.WriteLine("=> Stack update finished");
                        }
                        else
                        {
                            Console.WriteLine("=> Stack update FAILED");
                            return(false);
                        }
                    } catch (AmazonCloudFormationException e) when(e.Message == "No updates are to be performed.")
                    {
                        // this error is thrown when no required updates where found
                        Console.WriteLine("=> No stack update required");
                    }
                }
                await PopulateDeploymentTierSettingsAsync(settings, force : true);

                return(!HasErrors);
            }
        }
Пример #2
0
        public async Task <bool> Init(
            Settings settings,
            bool allowDataLoos,
            bool protectStack,
            bool forceDeploy,
            VersionInfo version,
            string lambdaSharpPath,
            string parametersFilename,
            bool forcePublish,
            bool promptAllParameters,
            bool promptsAsErrors
            )
        {
            var command = new CliBuildPublishDeployCommand();

            Console.WriteLine($"Creating new deployment tier '{settings.Tier}'");

            // standard modules
            var standardModules = new[] {
                "LambdaSharp.Core",
                "LambdaSharp.S3.IO",
                "LambdaSharp.S3.Subscriber"
            };

            // check if the module must be built and published first
            if (lambdaSharpPath != null)
            {
                // attempt to parse the tool version from environment variables
                if (!VersionInfo.TryParse(Environment.GetEnvironmentVariable("LAMBDASHARP_VERSION"), out var moduleVersion))
                {
                    LogError("unable to parse module version from LAMBDASHARP_VERSION");
                    return(false);
                }

                foreach (var module in standardModules)
                {
                    var moduleSource = Path.Combine(lambdaSharpPath, "Modules", module, "Module.yml");
                    settings.WorkingDirectory = Path.GetDirectoryName(moduleSource);
                    settings.OutputDirectory  = Path.Combine(settings.WorkingDirectory, "bin");

                    // build local module
                    if (!await command.BuildStepAsync(
                            settings,
                            Path.Combine(settings.OutputDirectory, "cloudformation.json"),
                            noAssemblyValidation: true,
                            noPackageBuild: false,
                            gitSha: GetGitShaValue(settings.WorkingDirectory, showWarningOnFailure: false),
                            gitBranch: GetGitBranch(settings.WorkingDirectory, showWarningOnFailure: false),
                            buildConfiguration: "Release",
                            selector: null,
                            moduleSource: moduleSource,
                            moduleVersion: moduleVersion
                            ))
                    {
                        return(false);
                    }

                    // publish module
                    var moduleReference = await command.PublishStepAsync(settings, forcePublish);

                    if (moduleReference == null)
                    {
                        return(false);
                    }
                }
            }

            // deploy LambdaSharp module
            foreach (var module in standardModules)
            {
                if (!await command.DeployStepAsync(
                        settings,
                        dryRun: null,
                        moduleReference: $"{module}:{version}",
                        instanceName: null,
                        allowDataLoos: allowDataLoos,
                        protectStack: protectStack,
                        parametersFilename: parametersFilename,
                        forceDeploy: forceDeploy,
                        promptAllParameters: promptAllParameters,
                        promptsAsErrors: promptsAsErrors,
                        enableXRayTracing: false,
                        deployOnlyIfExists: (module != "LambdaSharp.Core")
                        ))
                {
                    return(false);
                }
            }
            return(true);
        }