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 { } } } }
//--- 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); }