Exemple #1
0
        private void ShowStackResult(CloudFormationStack stack)
        {
            var outputs = stack.Outputs;

            if (outputs.Any())
            {
                Console.WriteLine("Stack output values:");
                foreach (var output in outputs.OrderBy(output => output.OutputKey))
                {
                    var line = $"=> {output.OutputKey}";
                    if (!string.IsNullOrEmpty(output.Description))
                    {
                        line += $": {output.Description}";
                    }
                    line += $" = {output.OutputValue}";
                    Console.WriteLine(line);
                }
            }
        }
        private void ShowStackResult(CloudFormationStack stack)
        {
            var outputs = stack.Outputs.Where(output => {
                // show everything when verbose level is set to 'Detailed'
                if (Settings.VerboseLevel >= VerboseLevel.Detailed)
                {
                    return(true);
                }

                // omit known outputs
                switch (output.OutputKey)
                {
                case "LambdaSharpTier":
                case "LambdaSharpTool":
                case "Module":
                case "ModuleInfo":
                case "ModuleChecksum":

                    // skip expected outputs
                    return(false);

                default:
                    return(true);
                }
            }).ToList();

            if (outputs.Any())
            {
                Console.WriteLine("Stack output values:");
                foreach (var output in outputs.OrderBy(output => output.OutputKey))
                {
                    var line = $"=> {Settings.OutputColor}{output.OutputKey}";
                    if (!string.IsNullOrEmpty(output.Description))
                    {
                        line += $": {output.Description}";
                    }
                    line += $" = {Settings.InfoColor}{output.OutputValue}{Settings.ResetColor}";
                    Console.WriteLine(line);
                }
            }
        }
        private List <CloudFormationParameter> PromptModuleParameters(
            ModuleManifest manifest,
            CloudFormationStack existing           = null,
            Dictionary <string, string> parameters = null,
            bool promptAll = false
            )
        {
            var stackParameters = new Dictionary <string, CloudFormationParameter>();

            // tentatively indicate to reuse previous parameter values
            if (existing != null)
            {
                foreach (var parameter in manifest.ParameterSections
                         .SelectMany(section => section.Parameters)
                         .Where(moduleParameter => existing.Parameters.Any(existingParameter => existingParameter.ParameterKey == moduleParameter.Name))
                         )
                {
                    stackParameters[parameter.Name] = new CloudFormationParameter {
                        ParameterKey     = parameter.Name,
                        UsePreviousValue = true
                    };
                }
            }

            // deployment bucket must always be set to match deployment tier in case it changed because LambdaSharp.Core was recreated
            stackParameters["DeploymentBucketName"] = new CloudFormationParameter {
                ParameterKey   = "DeploymentBucketName",
                ParameterValue = Settings.DeploymentBucketName
            };

            // add all provided parameters
            if (parameters != null)
            {
                foreach (var parameter in parameters)
                {
                    stackParameters[parameter.Key] = new CloudFormationParameter {
                        ParameterKey   = parameter.Key,
                        ParameterValue = parameter.Value
                    };
                }
            }

            // check if module requires any prompts
            if (manifest.GetAllParameters().Any(RequiresPrompt))
            {
                Console.WriteLine();
                Console.WriteLine($"Configuration for {manifest.GetFullName()} (v{manifest.GetVersion()})");

                // only list parameter sections that contain a parameter that requires a prompt
                foreach (var parameterGroup in manifest.ParameterSections.Where(group => group.Parameters.Any(RequiresPrompt)))
                {
                    Console.WriteLine();
                    Settings.PromptLabel(parameterGroup.Title.ToUpper());

                    // only prompt for required parameters
                    foreach (var parameter in parameterGroup.Parameters.Where(RequiresPrompt))
                    {
                        // check if parameter is multiple choice
                        string enteredValue;
                        if (parameter.AllowedValues?.Any() ?? false)
                        {
                            var message = parameter.Name;
                            if (parameter.Label != null)
                            {
                                message += $": {parameter.Label}";
                            }
                            enteredValue = Settings.PromptChoice(message, parameter.AllowedValues);
                        }
                        else
                        {
                            var constraints = new StringBuilder();
                            if ((parameter.MinValue != null) || (parameter.MaxValue != null))
                            {
                                // append value constraints
                                constraints.Append(" (");
                                if ((parameter.MinValue != null) && (parameter.MaxValue != null))
                                {
                                    constraints.Append($"Range: {parameter.MinValue.Value}..{parameter.MaxValue.Value}");
                                }
                                else if (parameter.MinValue != null)
                                {
                                    constraints.Append($"Mininum: {parameter.MinValue.Value}");
                                }
                                else if (parameter.MaxValue != null)
                                {
                                    constraints.Append($"Maximum: {parameter.MaxValue.Value}");
                                }
                                constraints.Append(")");
                            }
                            else if ((parameter.MinLength != null) || (parameter.MaxLength != null))
                            {
                                // append length constraints
                                constraints.Append(" (");
                                if ((parameter.MinLength != null) || (parameter.MaxLength != null))
                                {
                                    constraints.Append($"Length: {parameter.MinValue.Value}..{parameter.MaxValue.Value}");
                                }
                                else if (parameter.MinLength != null)
                                {
                                    constraints.Append($"Mininum Length: {parameter.MinLength.Value}");
                                }
                                else if (parameter.MaxLength != null)
                                {
                                    constraints.Append($"Maximum Length: {parameter.MaxLength.Value}");
                                }
                                constraints.Append(")");
                            }
                            var message = $"{parameter.Name} [{parameter.Type}]{constraints}";
                            if (parameter.Label != null)
                            {
                                message += $": {parameter.Label}";
                            }
                            enteredValue = Settings.PromptString(message, parameter.Default, parameter.AllowedPattern, parameter.ConstraintDescription) ?? "";
                        }
                        stackParameters[parameter.Name] = new CloudFormationParameter {
                            ParameterKey   = parameter.Name,
                            ParameterValue = enteredValue
                        };
                    }
                }
            }

            // check if LambdaSharp.Core services should be enabled by default
            if (
                (Settings.CoreServices == CoreServices.Enabled) &&
                manifest.GetAllParameters().Any(p => p.Name == "LambdaSharpCoreServices") &&
                !stackParameters.Any(p => p.Value.ParameterKey == "LambdaSharpCoreServices")
                )
            {
                stackParameters.Add("LambdaSharpCoreServices", new CloudFormationParameter {
                    ParameterKey   = "LambdaSharpCoreServices",
                    ParameterValue = Settings.CoreServices.ToString()
                });
            }
            return(stackParameters.Values.ToList());

            // local functions
            bool RequiresPrompt(ModuleManifestParameter parameter)
            {
                if (parameters?.ContainsKey(parameter.Name) == true)
                {
                    // no prompt since parameter is provided explicitly
                    return(false);
                }
                if (existing?.Parameters.Any(p => p.ParameterKey == parameter.Name) == true)
                {
                    // no prompt since we can reuse the previous parameter value
                    return(false);
                }
                if (!promptAll && (parameter.Default != null))
                {
                    // no prompt since parameter has a default value
                    return(false);
                }
                if (Settings.PromptsAsErrors)
                {
                    LogError($"{manifest.GetFullName()} requires value for parameter '{parameter.Name}'");
                    return(false);
                }
                return(true);
            }
        }
        //--- Methods ---
        public async Task <bool> DoAsync(
            DryRunLevel?dryRun,
            string moduleReference,
            string instanceName,
            bool allowDataLoos,
            bool protectStack,
            Dictionary <string, string> parameters,
            bool forceDeploy,
            bool promptAllParameters,
            bool promptsAsErrors,
            bool enableXRayTracing,
            bool deployOnlyIfExists
            )
        {
            Console.WriteLine($"Resolving module reference: {moduleReference}");

            // determine location of cloudformation template from module key
            var location = await _loader.LocateAsync(moduleReference);

            if (location == null)
            {
                LogError($"unable to resolve: {moduleReference}");
                return(false);
            }

            // download module manifest
            var manifest = await _loader.LoadFromS3Async(location.ModuleBucketName, location.TemplatePath);

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

            // check that the LambdaSharp Core & CLI versions match
            if (!forceDeploy)
            {
                if (Settings.TierVersion == null)
                {
                    // core module doesn't expect a deployment tier to exist
                    if (manifest.RuntimeCheck)
                    {
                        LogError("could not determine the LambdaSharp tier version; use --force-deploy to proceed anyway", new LambdaSharpDeploymentTierSetupException(Settings.Tier));
                        return(false);
                    }
                }
                else if (manifest.GetFullName() == "LambdaSharp.Core")
                {
                    // core module has special rules for updates
                    if (Settings.ToolVersion < Settings.TierVersion)
                    {
                        // tool version is older than tier; most likely a user error
                        LogError($"LambdaSharp CLI (v{Settings.ToolVersion}) has older version than Tier (v{Settings.TierVersion}); use --force-deploy to proceed anyway");
                        return(false);
                    }
                    else if (Settings.ToolVersion > Settings.TierVersion)
                    {
                        // allow upgrading Tier when tool version is more recent
                        Console.WriteLine($"LambdaSharp Tier appears to be out of date");
                        var upgrade = Prompt.GetYesNo($"|=> Do you want to upgrade LambdaSharp Tier '{Settings.Tier}' from v{Settings.TierVersion} to v{Settings.ToolVersion}?", false);
                        if (!upgrade)
                        {
                            return(false);
                        }
                    }
                    else
                    {
                        // nothing to do
                        return(true);
                    }
                }
                else if (!Settings.ToolVersion.IsCompatibleWith(Settings.TierVersion))
                {
                    LogError($"LambdaSharp CLI (v{Settings.ToolVersion}) and Tier (v{Settings.TierVersion}) versions do not match; use --force-deploy to proceed anyway");
                    return(false);
                }
            }

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

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

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

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

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

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

                // discover and deploy module dependencies
                var dependencies = await DiscoverDependenciesAsync(manifest);

                if (HasErrors)
                {
                    return(false);
                }
                var dependenciesParameters = dependencies
                                             .Select(dependency => new {
                    ModuleFullName = dependency.Manifest.GetFullName(),
                    Parameters     = PromptModuleParameters(
                        dependency.Manifest,
                        promptAll: promptAllParameters,
                        promptsAsErrors: promptsAsErrors
                        )
                })
                                             .ToDictionary(t => t.ModuleFullName, t => t.Parameters);
                if (HasErrors)
                {
                    return(false);
                }
                foreach (var dependency in dependencies)
                {
                    if (!await new ModelUpdater(Settings, dependency.Location.ToModuleReference()).DeployChangeSetAsync(
                            dependency.Manifest,
                            dependency.Location,
                            ToStackName(dependency.Manifest.GetFullName()),
                            allowDataLoos,
                            protectStack,
                            dependenciesParameters[dependency.Manifest.GetFullName()]
                            ))
                    {
                        return(false);
                    }
                }

                // deploy module
                return(await new ModelUpdater(Settings, moduleReference).DeployChangeSetAsync(
                           manifest,
                           location,
                           stackName,
                           allowDataLoos,
                           protectStack,
                           deployParameters
                           ));
            }
            return(true);
        }
        private List <CloudFormationParameter> PromptModuleParameters(
            ModuleManifest manifest,
            CloudFormationStack existing           = null,
            Dictionary <string, string> parameters = null,
            bool promptAll       = false,
            bool promptsAsErrors = false
            )
        {
            var stackParameters = new Dictionary <string, CloudFormationParameter>();

            // tentatively indicate to reuse previous parameter values
            if (existing != null)
            {
                foreach (var parameter in manifest.ParameterSections
                         .SelectMany(section => section.Parameters)
                         .Where(moduleParameter => existing.Parameters.Any(existingParameter => existingParameter.ParameterKey == moduleParameter.Name))
                         )
                {
                    stackParameters[parameter.Name] = new CloudFormationParameter {
                        ParameterKey     = parameter.Name,
                        UsePreviousValue = true
                    };
                }
            }

            // add all provided parameters
            if (parameters != null)
            {
                foreach (var parameter in parameters)
                {
                    stackParameters[parameter.Key] = new CloudFormationParameter {
                        ParameterKey   = parameter.Key,
                        ParameterValue = parameter.Value
                    };
                }
            }

            // check if module requires any prompts
            if (manifest.GetAllParameters().Any(RequiresPrompt))
            {
                Console.WriteLine();
                Console.WriteLine($"Configuration for {manifest.GetFullName()} (v{manifest.GetVersion()})");

                // only list parameter sections that contain a parameter that requires a prompt
                foreach (var parameterGroup in manifest.ParameterSections.Where(group => group.Parameters.Any(RequiresPrompt)))
                {
                    Console.WriteLine();
                    Console.WriteLine($"*** {parameterGroup.Title.ToUpper()} ***");

                    // only prompt for required parameters
                    foreach (var parameter in parameterGroup.Parameters.Where(RequiresPrompt))
                    {
                        var enteredValue = PromptString(parameter, parameter.Default) ?? "";
                        stackParameters[parameter.Name] = new CloudFormationParameter {
                            ParameterKey   = parameter.Name,
                            ParameterValue = enteredValue
                        };
                    }
                }
            }
            return(stackParameters.Values.ToList());

            // local functions
            bool RequiresPrompt(ModuleManifestParameter parameter)
            {
                if (parameters?.ContainsKey(parameter.Name) == true)
                {
                    // no prompt since parameter is provided explicitly
                    return(false);
                }
                if (existing?.Parameters.Any(p => p.ParameterKey == parameter.Name) == true)
                {
                    // no prompt since we can reuse the previous parameter value
                    return(false);
                }
                if (!promptAll && (parameter.Default != null))
                {
                    // no prompt since parameter has a default value
                    return(false);
                }
                if (promptsAsErrors)
                {
                    LogError($"{manifest.GetFullName()} requires value for parameter '{parameter.Name}'");
                    return(false);
                }
                return(true);
            }

            string PromptString(ModuleManifestParameter parameter, string defaultValue = null)
            {
                var prompt = $"|=> {parameter.Name} [{parameter.Type}]:";

                if (parameter.Label != null)
                {
                    prompt += $" {parameter.Label}:";
                }
                if (!string.IsNullOrEmpty(defaultValue))
                {
                    prompt = $"{prompt} [{defaultValue}]";
                }
                Console.Write(prompt);
                Console.Write(' ');
                SetCursorVisible(true);
                var resp = Console.ReadLine();

                SetCursorVisible(false);
                if (!string.IsNullOrEmpty(resp))
                {
                    return(resp);
                }
                return(defaultValue);

                // local functions
                void SetCursorVisible(bool visible)
                {
                    try {
                        Console.CursorVisible = visible;
                    } catch { }
                }
            }
        }
Exemple #6
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
            )
        {
            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, ModuleManifestDependencyType.Root, allowImport : false, showError : !deployOnlyIfExists);

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

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

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

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

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

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

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

                // 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 shard module dependencies and prompt for missing parameters
                var dependencies = (await _loader.DiscoverAllDependenciesAsync(manifest, checkExisting: true, allowImport: false))
                                   .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);
        }