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