示例#1
0
        public async Task <bool> DeployStepAsync(
            Settings settings,
            DryRunLevel?dryRun,
            string moduleReference,
            string instanceName,
            bool allowDataLoos,
            bool protectStack,
            Dictionary <string, string> parameters,
            bool forceDeploy,
            bool promptAllParameters,
            XRayTracingLevel xRayTracingLevel,
            bool deployOnlyIfExists,
            bool allowDependencyUpgrades
            )
        {
            try {
                if (!await PopulateDeploymentTierSettingsAsync(settings))
                {
                    return(false);
                }
                if (HasErrors)
                {
                    return(false);
                }

                // reading module parameters
                return(await new DeployStep(settings, moduleReference).DoAsync(
                           dryRun,
                           moduleReference,
                           instanceName,
                           allowDataLoos,
                           protectStack,
                           parameters,
                           forceDeploy,
                           promptAllParameters,
                           xRayTracingLevel,
                           deployOnlyIfExists,
                           allowDependencyUpgrades
                           ));
            } catch (Exception e) {
                LogError(e);
                return(false);
            }
        }
示例#2
0
        //--- Methods ---
        public async Task <bool> DoAsync(
            DryRunLevel?dryRun,
            string moduleReference,
            string instanceName,
            bool allowDataLoos,
            bool protectStack,
            Dictionary <string, string> parameters,
            bool forceDeploy,
            bool promptAllParameters,
            XRayTracingLevel xRayTracingLevel,
            bool deployOnlyIfExists,
            bool allowDependencyUpgrades
            )
        {
            Console.WriteLine($"Resolving module reference: {moduleReference}");

            // determine location of cloudformation template from module key
            if (!ModuleInfo.TryParse(moduleReference, out var moduleInfo))
            {
                LogError($"invalid module reference: {moduleReference}");
                return(false);
            }
            var foundModuleLocation = await _loader.ResolveInfoToLocationAsync(moduleInfo, moduleInfo.Origin, ModuleManifestDependencyType.Root, allowImport : Settings.AllowImport, showError : !deployOnlyIfExists);

            if (foundModuleLocation == null)
            {
                // nothing to do; loader already emitted an error
                return(deployOnlyIfExists);
            }

            // download module manifest
            var(manifest, manifestErrorReason) = await _loader.LoadManifestFromLocationAsync(foundModuleLocation);

            if (manifest == null)
            {
                LogError(manifestErrorReason);
                return(false);
            }

            // deploy module
            if (dryRun == null)
            {
                var stackName = Settings.GetStackName(manifest.GetFullName(), instanceName);

                // check version of previously deployed module
                if (!deployOnlyIfExists)
                {
                    Console.WriteLine("=> Validating module for deployment tier");
                }
                var updateValidation = await IsValidModuleUpdateAsync(stackName, manifest, showError : !forceDeploy && !deployOnlyIfExists);

                if (!forceDeploy && !updateValidation.Success)
                {
                    return(false);
                }

                // check if a previous deployment was found
                if (deployOnlyIfExists && (updateValidation.ExistingStack == null))
                {
                    // nothing to do
                    return(true);
                }
                var existing = updateValidation.ExistingStack;

                // check if existing stack checksum matches template checksum
                if (!forceDeploy && !parameters.Any())
                {
                    var existingChecksum = existing?.Outputs.FirstOrDefault(output => output.OutputKey == "ModuleChecksum");
                    if (existingChecksum?.OutputValue == manifest.TemplateChecksum)
                    {
                        Console.WriteLine($"{Settings.LowContrastColor}=> No changes found to deploy{Settings.ResetColor}");
                        return(true);
                    }
                }

                // prompt for missing parameters
                var deployParameters = PromptModuleParameters(manifest, existing, parameters, promptAllParameters);
                if (HasErrors)
                {
                    return(false);
                }

                // check if module supports AWS X-Ray for tracing
                if (
                    manifest.GetAllParameters().Any(p => p.Name == "XRayTracing") &&
                    !deployParameters.Any(p => p.ParameterKey == "XRayTracing")
                    )
                {
                    deployParameters.Add(new CloudFormationParameter {
                        ParameterKey   = "XRayTracing",
                        ParameterValue = xRayTracingLevel.ToString()
                    });
                }

                // discover shared module dependencies and prompt for missing parameters
                var dependencies = (await _loader.DiscoverAllDependenciesAsync(manifest, checkExisting: true, allowImport: Settings.AllowImport, allowDependencyUpgrades: allowDependencyUpgrades))
                                   .Where(dependency => dependency.Type == ModuleManifestDependencyType.Shared)
                                   .ToList();
                if (HasErrors)
                {
                    return(false);
                }
                var dependenciesParameters = dependencies
                                             .Select(dependency => new {
                    ModuleFullName = dependency.Manifest.GetFullName(),
                    Parameters     = PromptModuleParameters(
                        dependency.Manifest,
                        promptAll: promptAllParameters
                        )
                })
                                             .ToDictionary(t => t.ModuleFullName, t => t.Parameters);
                if (HasErrors)
                {
                    return(false);
                }

                // deploy module dependencies
                foreach (var dependency in dependencies)
                {
                    var dependencyLocation = new ModuleLocation(Settings.DeploymentBucketName, dependency.ModuleLocation.ModuleInfo, dependency.ModuleLocation.Hash);
                    if (!await new ModelUpdater(Settings, SourceFilename).DeployChangeSetAsync(
                            dependency.Manifest,
                            await _loader.GetNameMappingsFromLocationAsync(dependencyLocation),
                            dependencyLocation,
                            Settings.GetStackName(dependency.Manifest.GetFullName()),
                            allowDataLoos,
                            protectStack,
                            dependenciesParameters[dependency.Manifest.GetFullName()]
                            ))
                    {
                        return(false);
                    }
                }

                // deploy module
                var moduleLocation = new ModuleLocation(Settings.DeploymentBucketName, manifest.ModuleInfo, manifest.TemplateChecksum);
                return(await new ModelUpdater(Settings, moduleReference).DeployChangeSetAsync(
                           manifest,
                           await _loader.GetNameMappingsFromLocationAsync(moduleLocation),
                           moduleLocation,
                           stackName,
                           allowDataLoos,
                           protectStack,
                           deployParameters
                           ));
            }
            return(true);
        }
        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);
            }
        }