public void NewCSharpFunction( Settings settings, string functionName, string rootNamespace, string framework, string workingDirectory, string moduleFile, int functionMemory, int functionTimeout, string projectDirectory, FunctionType functionType ) { if (functionName == "Finalizer") { // always of type finalizer functionType = FunctionType.Finalizer; functionTimeout = 900; } else if (functionType == FunctionType.Unknown) { // prompt for function type functionType = Enum.Parse <FunctionType>(settings.PromptChoice("Select function type", _functionTypes), ignoreCase: true); } // create function project var isNetCore31OrLater = VersionInfoCompatibility.IsNetCore3OrLater(framework); var projectFile = Path.Combine(projectDirectory, functionName + ".csproj"); var substitutions = new Dictionary <string, string> { ["FRAMEWORK"] = framework, ["ROOTNAMESPACE"] = rootNamespace, ["LAMBDASHARP_VERSION"] = VersionInfoCompatibility.GetLambdaSharpAssemblyWildcardVersion(settings.ToolVersion, framework) }; try { var projectContents = ReadResource($"NewCSharpFunction-{functionType}.xml", substitutions); File.WriteAllText(projectFile, projectContents); Console.WriteLine($"Created project file: {Path.GetRelativePath(Directory.GetCurrentDirectory(), projectFile)}"); } catch (Exception e) { LogError($"unable to create project file '{projectFile}'", e); return; } // create function source code var functionFile = Path.Combine(projectDirectory, "Function.cs"); var functionContents = ReadResource($"NewCSharpFunction-{functionType}.txt", substitutions); try { File.WriteAllText(functionFile, functionContents); Console.WriteLine($"Created function file: {Path.GetRelativePath(Directory.GetCurrentDirectory(), functionFile)}"); } catch (Exception e) { LogError($"unable to create function file '{functionFile}'", e); return; } }
public bool ValidateLambdaSharpPackageReferences(VersionInfo toolVersion, Action <string>?logWarn, Action <string>?logError) { var success = true; var includes = PackageReferences.Where(elem => elem.Attribute("Include")?.Value.StartsWith("LambdaSharp", StringComparison.Ordinal) ?? false); foreach (var include in includes) { var expectedVersion = VersionInfoCompatibility.GetLambdaSharpAssemblyWildcardVersion(toolVersion, TargetFramework); var library = include.Attribute("Include").Value; var libraryVersionText = include.Attribute("Version")?.Value; if (libraryVersionText == null) { success = false; logError?.Invoke($"csproj file is missing a version attribute in its assembly reference for {library} (expected version: '{expectedVersion}')"); } else { try { if (!VersionInfoCompatibility.IsValidLambdaSharpAssemblyReferenceForToolVersion(toolVersion, TargetFramework, libraryVersionText, out var outdated)) { // check if we're compiling a conditional package reference in contributor mode if ((include.Attribute("Condition")?.Value != null) && (Environment.GetEnvironmentVariable("LAMBDASHARP") != null)) { // show error as warning instead since this package reference will not be used anyway logWarn?.Invoke($"csproj file contains a mismatched assembly reference for {library} (expected version: '{expectedVersion}', found: '{libraryVersionText}')"); } else { success = false; logError?.Invoke($"csproj file contains a mismatched assembly reference for {library} (expected version: '{expectedVersion}', found: '{libraryVersionText}')"); } } else if (outdated) { // show warning to updated dependency logWarn?.Invoke($"csproj file contains outdated assembly reference for {library} (expected version: '{expectedVersion}', found: '{libraryVersionText}')"); } } catch (VersionInfoCompatibilityUnsupportedFrameworkException) { success = false; logError?.Invoke($"csproj file targets unsupported framework '{TargetFramework}'"); } catch { success = false; logError?.Invoke($"csproj file contains an invalid wildcard version in its assembly reference for {library} (expected version: '{expectedVersion}', found: '{libraryVersionText}')"); } } } return(success); }
//--- Methods --- public void Build( IFunction function, bool noCompile, bool noAssemblyValidation, string gitSha, string gitBranch, string buildConfiguration, bool forceBuild ) { // collect sources with invoke methods var mappings = ExtractMappings(function); if (mappings == null) { // nothing to log since error was already logged return; } // check if a function package already exists if (!forceBuild) { var functionPackage = Provider.ExistingPackages.FirstOrDefault(p => Path.GetFileName(p).StartsWith($"function_{Provider.ModuleFullName}_{function.LogicalId}_", StringComparison.Ordinal) && p.EndsWith(".zip", StringComparison.Ordinal) ); // to skip the build, we both need the function package and the function schema when mappings are present var schemaFile = Path.Combine(Provider.OutputDirectory, $"functionschema_{Provider.ModuleFullName}_{function.LogicalId}.json"); if ((functionPackage != null) && (!mappings.Any() || File.Exists(schemaFile))) { LogInfoVerbose($"=> Analyzing function {function.FullName} dependencies"); // find all files used to create the function package var files = new HashSet <string>(); CSharpProjectFile.DiscoverDependencies( files, function.Project, filePath => LogInfoVerbose($"... analyzing {filePath}"), (message, exception) => LogError(message, exception) ); // check if any of the files has been modified more recently than the function package var functionPackageDate = File.GetLastWriteTime(functionPackage); var file = files.FirstOrDefault(f => File.GetLastWriteTime(f) > functionPackageDate); if (file == null) { var success = true; if (mappings.Any()) { // apply function schema to generate REST API and WebSocket models try { if (!ApplyInvocationSchemas(function, mappings, schemaFile, silent: true)) { success = false; // reset the mappings as the call to ApplyInvocationSchemas() may have modified them mappings = ExtractMappings(function); if (mappings == null) { // nothing to log since error was already logged return; } } } catch (Exception e) { LogError("unable to read create-invoke-methods-schema output", e); return; } // check if the mappings have changed by comparing the new data-structure to the one inside the zip file if (success) { var newMappingsJson = JsonSerializer.Serialize(new ApiGatewayInvocationMappings { Mappings = mappings }, _jsonOptions); using (var zipArchive = ZipFile.Open(functionPackage, ZipArchiveMode.Read)) { var entry = zipArchive.Entries.FirstOrDefault(entry => entry.FullName == API_MAPPINGS); if (entry != null) { using (var stream = entry.Open()) using (var reader = new StreamReader(stream)) { if (newMappingsJson != reader.ReadToEnd()) { // module mappings have change success = false; LogInfoVerbose($"... api mappings updated"); } } } else { // we now have mappings and we didn't use to success = false; LogInfoVerbose($"... api mappings updated"); } } } } // only skip compilation if we were able to apply the invocation schemas (or didn't have to) if (success) { Provider.WriteLine($"=> Skipping function {Provider.InfoColor}{function.FullName}{Provider.ResetColor} (no changes found)"); // keep the existing package Provider.ExistingPackages.Remove(functionPackage); // set the module variable to the final package name Provider.AddArtifact($"{function.FullName}::PackageName", functionPackage); return; } } else { LogInfoVerbose($"... change detected in {file}"); } } } else { LogInfoVerbose($"=> Analyzing function {function.FullName} dependencies"); // find all files used to create the function package var files = new HashSet <string>(); CSharpProjectFile.DiscoverDependencies( files, function.Project, filePath => LogInfoVerbose($"... analyzing {filePath}"), (message, exception) => LogError(message, exception) ); // loop over all project folders new CleanBuildFolders(BuildEventsConfig).Do(files); } // read settings from project file var projectFile = new CSharpProjectFile(function.Project); // compile function project var isReadyToRunSupported = VersionInfoCompatibility.IsReadyToRunSupported(projectFile.TargetFramework); var isAmazonLinux2 = Provider.IsAmazonLinux2(); var isReadyToRun = isReadyToRunSupported && isAmazonLinux2; var isSelfContained = (projectFile.OutputType == "Exe") && (projectFile.AssemblyName == "bootstrap"); var isTopLevelMain = !isSelfContained && (projectFile.OutputType == "Exe"); var readyToRunText = isReadyToRun ? ", ReadyToRun" : ""; var selfContained = isSelfContained ? ", SelfContained" : ""; Provider.WriteLine($"=> Building function {Provider.InfoColor}{function.FullName}{Provider.ResetColor} [{projectFile.TargetFramework}, {buildConfiguration}{readyToRunText}{selfContained}]"); var projectDirectory = Path.Combine(Provider.WorkingDirectory, Path.GetFileNameWithoutExtension(function.Project)); // check if the project contains an obsolete AWS Lambda Tools extension: <DotNetCliToolReference Include="Amazon.Lambda.Tools"/> if (projectFile.RemoveAmazonLambdaToolsReference()) { LogWarn($"removing obsolete AWS Lambda Tools extension from {Path.GetRelativePath(Provider.WorkingDirectory, function.Project)}"); projectFile.Save(function.Project); } // validate the project is using the most recent lambdasharp assembly references if ( !noAssemblyValidation && function.HasAssemblyValidation && !projectFile.ValidateLambdaSharpPackageReferences(Provider.ToolVersion, LogWarn, LogError) ) { return; } if (noCompile) { return; } // build project with AWS dotnet CLI lambda tool if (!DotNetPublish(projectFile.TargetFramework, buildConfiguration, projectDirectory, forceBuild, isReadyToRunSupported, isAmazonLinux2, isReadyToRun, isSelfContained, out var publishFolder)) { // nothing to do; error was already reported return; } // building a function with top-level statements also creates an ELF file we don't need if (isTopLevelMain) { var elfBinary = Path.Combine(publishFolder, Path.GetFileNameWithoutExtension(function.Project)); try { File.Delete(elfBinary); } catch (Exception e) { // no harm in leaving the file; report error as a warning LogWarn($"Unable to delete unnecessary ELF binary at '{elfBinary}' (Error: {e})"); } } // check if the assembly entry-point needs to be validated if (function.HasHandlerValidation) { if (isSelfContained || isTopLevelMain) { // nothing to do } else { // verify the function handler can be found in the compiled assembly if (function.Handler != null) { if (!ValidateEntryPoint( publishFolder, function.Handler )) { return; } } } } // add api mappings JSON file(s) if (mappings.Any()) { // self-contained assemblies cannot be inspected if (isSelfContained) { LogError("API Gateway mappings are not supported for self-contained Lambda functions"); return; } // create request/response schemas for invocation methods if (!LambdaSharpCreateInvocationSchemas( function, publishFolder, projectFile.RootNamespace, function.Handler, mappings )) { LogError($"'{Provider.Lash} util create-invoke-methods-schema' command failed"); return; } // write api-mappings.json file to publish folder File.WriteAllText(Path.Combine(publishFolder, API_MAPPINGS), JsonSerializer.Serialize(new ApiGatewayInvocationMappings { Mappings = mappings }, _jsonOptions)); } // compute hash o publish folder string hash; using (var md5 = MD5.Create()) using (var hashStream = new CryptoStream(Stream.Null, md5, CryptoStreamMode.Write)) { foreach (var publishedFile in Directory.GetFiles(publishFolder, "*", SearchOption.AllDirectories).OrderBy(filePath => filePath)) { // hash file path var filePathBytes = Encoding.UTF8.GetBytes(Path.GetRelativePath(publishFolder, publishedFile).Replace('\\', '/')); hashStream.Write(filePathBytes, 0, filePathBytes.Length); // hash file contents using (var stream = File.OpenRead(publishedFile)) { stream.CopyTo(hashStream); } } hashStream.FlushFinalBlock(); hash = md5.Hash !.ToHexString(); } // genereate function package with hash var package = Path.Combine(Provider.OutputDirectory, $"function_{Provider.ModuleFullName}_{function.LogicalId}_{hash}.zip"); if (Provider.ExistingPackages.Remove(package)) { // remove old, existing package so we can create the new package in the same location (which also preserves the more recent build timestamp) File.Delete(package); } // write git-info.json file to publish folder File.WriteAllText(Path.Combine(publishFolder, GIT_INFO_FILE), JsonSerializer.Serialize(new ModuleManifestGitInfo { SHA = gitSha, Branch = gitBranch }, _jsonOptions)); // zip files in publishing folder new ZipTool(BuildEventsConfig).ZipFolderWithExecutable(package, publishFolder); // set the module variable to the final package name Provider.AddArtifact($"{function.FullName}::PackageName", package); }
public async Task <ModuleLocation> ResolveInfoToLocationAsync( ModuleInfo moduleInfo, string originBucketName, ModuleManifestDependencyType dependencyType, bool allowImport, bool showError, bool allowCaching = false ) { if (originBucketName == null) { throw new ArgumentNullException(nameof(originBucketName)); } LogInfoVerbose($"... resolving module {moduleInfo}"); var stopwatch = Stopwatch.StartNew(); var cached = false; try { // check if a cached manifest matches var cachedDirectory = Path.Combine(Settings.GetOriginCacheDirectory(moduleInfo)); if (allowCaching && Settings.AllowCaching && Directory.Exists(cachedDirectory)) { var foundCached = Directory.GetFiles(cachedDirectory) .Select(found => VersionInfo.Parse(Path.GetFileName(found))) .Where(version => (moduleInfo.Version == null) || version.IsGreaterOrEqualThanVersion(moduleInfo.Version, strict: true)); // NOTE (2019-08-12, bjorg): unless the module is shared, we filter the list of found versions to // only contain versions that meet the module version constraint; for shared modules, we want to // keep the latest version that is compatible with the tool and is equal-or-greater than the // module version constraint. if ((dependencyType != ModuleManifestDependencyType.Shared) && (moduleInfo.Version != null)) { foundCached = foundCached.Where(version => version.MatchesConstraint(moduleInfo.Version)).ToList(); } // attempt to identify the newest module version compatible with the tool ModuleManifest manifest = null; var match = VersionInfo.FindLatestMatchingVersion(foundCached, moduleInfo.Version, candidate => { var candidateManifestText = File.ReadAllText(Path.Combine(Settings.GetOriginCacheDirectory(moduleInfo), candidate.ToString())); manifest = JsonConvert.DeserializeObject <ModuleManifest>(candidateManifestText); // check if module is compatible with this tool return(VersionInfoCompatibility.IsModuleCoreVersionCompatibleWithToolVersion(manifest.CoreServicesVersion, Settings.ToolVersion)); }); if (manifest != null) { cached = true; // TODO (2019-10-08, bjorg): what source bucket name should be used for cached manifests? return(MakeModuleLocation(Settings.DeploymentBucketName, manifest)); } } // check if module can be found in the deployment bucket var result = await FindNewestModuleVersionAsync(Settings.DeploymentBucketName); // check if the origin bucket needs to be checked if ( allowImport && (Settings.DeploymentBucketName != originBucketName) && ( // no version has been found (result.Version == null) // no module version constraint was given; the ultimate floating version || (moduleInfo.Version == null) // the module version constraint is for a pre-release; we always prefer the origin version then || moduleInfo.Version.IsPreRelease() // the module version constraint is floating; we need to check if origin has a newer version || !moduleInfo.Version.Minor.HasValue || !moduleInfo.Version.Patch.HasValue ) ) { var originResult = await FindNewestModuleVersionAsync(originBucketName); // check if module found at origin should be kept instead if ( (originResult.Version != null) && ( (result.Version == null) || (moduleInfo.Version?.IsPreRelease() ?? false) || originResult.Version.IsGreaterThanVersion(result.Version) ) ) { result = originResult; } } // check if a module was found if (result.Version == null) { // could not find a matching version var versionConstraint = (moduleInfo.Version != null) ? $"v{moduleInfo.Version} or later" : "any released version"; if (showError) { if (allowImport) { LogError($"could not find module '{moduleInfo}' ({versionConstraint})"); } else { LogError($"missing module dependency must be imported explicitly '{moduleInfo}' ({versionConstraint})"); } } return(null); } LogInfoVerbose($"... selected module {moduleInfo.WithVersion(result.Version)} from {result.Origin}"); // cache found version Directory.CreateDirectory(cachedDirectory); await File.WriteAllTextAsync(Path.Combine(cachedDirectory, result.Version.ToString()), JsonConvert.SerializeObject(result.Manifest)); return(MakeModuleLocation(result.Origin, result.Manifest)); } finally { LogInfoPerformance($"ResolveInfoToLocationAsync() for {moduleInfo}", stopwatch.Elapsed, cached); } async Task <(string Origin, VersionInfo Version, ModuleManifest Manifest)> FindNewestModuleVersionAsync(string bucketName) { // enumerate versions in bucket var found = await FindModuleVersionsAsync(bucketName); if (!found.Any()) { return(Origin : bucketName, Version : null, Manifest : null); } // NOTE (2019-08-12, bjorg): if the module is nested, we filter the list of found versions to // only contain versions that meet the module version constraint; for shared modules, we want to // keep the latest version that is compatible with the tool and is equal-or-greater than the // module version constraint. if ((dependencyType == ModuleManifestDependencyType.Nested) && (moduleInfo.Version != null)) { found = found.Where(version => { if (!version.MatchesConstraint(moduleInfo.Version)) { LogInfoVerbose($"... rejected v{version}: does not match version constraint {moduleInfo.Version}"); return(false); } return(true); }).ToList(); } // attempt to identify the newest module version compatible with the tool ModuleManifest manifest = null; var match = VersionInfo.FindLatestMatchingVersion(found, moduleInfo.Version, candidateVersion => { var candidateModuleInfo = new ModuleInfo(moduleInfo.Namespace, moduleInfo.Name, candidateVersion, moduleInfo.Origin); // check if the module version is allowed by the build policy if (!(Settings.BuildPolicy?.Modules?.Allow?.Contains(candidateModuleInfo.ToString()) ?? true)) { LogInfoVerbose($"... rejected v{candidateVersion}: not allowed by build policy"); return(false); } // check if module is compatible with this tool var candidateManifestText = GetS3ObjectContentsAsync(bucketName, candidateModuleInfo.VersionPath).GetAwaiter().GetResult(); manifest = JsonConvert.DeserializeObject <ModuleManifest>(candidateManifestText); if (!VersionInfoCompatibility.IsModuleCoreVersionCompatibleWithToolVersion(manifest.CoreServicesVersion, Settings.ToolVersion)) { LogInfoVerbose($"... rejected v{candidateVersion}: not compatible with tool version {Settings.ToolVersion}"); return(false); } return(true); }); return(Origin : bucketName, Version : match, Manifest : manifest); } async Task <IEnumerable <VersionInfo> > FindModuleVersionsAsync(string bucketName) { // get bucket region specific S3 client var s3Client = await GetS3ClientByBucketNameAsync(bucketName); if (s3Client == null) { // nothing to do; GetS3ClientByBucketName already emitted an error return(new List <VersionInfo>()); } // enumerate versions in bucket var versions = new List <VersionInfo>(); var request = new ListObjectsV2Request { BucketName = bucketName, Prefix = $"{moduleInfo.Origin ?? Settings.DeploymentBucketName}/{moduleInfo.Namespace}/{moduleInfo.Name}/", Delimiter = "/", MaxKeys = 100, RequestPayer = RequestPayer.Requester }; do { try { var response = await s3Client.ListObjectsV2Async(request); versions.AddRange(response.S3Objects .Select(s3Object => s3Object.Key.Substring(request.Prefix.Length)) .Select(found => VersionInfo.Parse(found)) .Where(version => (moduleInfo.Version == null) || version.IsGreaterOrEqualThanVersion(moduleInfo.Version, strict: true)) ); request.ContinuationToken = response.NextContinuationToken; } catch (AmazonS3Exception e) when(e.Message == "Access Denied") { // show message that access was denied for this location LogInfoVerbose($"... access denied to {bucketName} [{s3Client.Config.RegionEndpoint.SystemName}]"); return(versions); } } while(request.ContinuationToken != null); LogInfoVerbose($"... found {versions.Count} version{((versions.Count == 1) ? "" : "s")} in {bucketName} [{s3Client.Config.RegionEndpoint.SystemName}]"); return(versions); } ModuleLocation MakeModuleLocation(string sourceBucketName, ModuleManifest manifest) => new ModuleLocation(sourceBucketName, manifest.ModuleInfo, manifest.TemplateChecksum); }
public async Task <ModuleLocation> ResolveInfoToLocationAsync( ModuleInfo moduleInfo, string bucketName, ModuleManifestDependencyType dependencyType, bool allowImport, bool showError ) { if (bucketName == null) { throw new ArgumentNullException(nameof(bucketName)); } LogInfoVerbose($"... resolving module {moduleInfo}"); StartLogPerformance($"ResolveInfoToLocationAsync() for {moduleInfo}"); var cached = false; try { // check if module can be found in the deployment bucket var result = await FindNewestModuleVersionInBucketAsync(Settings.DeploymentBucketName); // check if the origin bucket needs to be checked if ( allowImport && (Settings.DeploymentBucketName != bucketName) && ( // no version has been found (result.Version == null) // no module version constraint was given; the ultimate floating version || (moduleInfo.Version == null) // the module version constraint is for a pre-release; we always prefer the origin version then || moduleInfo.Version.IsPreRelease() // the module version constraint is floating; we need to check if origin has a newer version || !moduleInfo.Version.Minor.HasValue || !moduleInfo.Version.Patch.HasValue ) ) { var originResult = await FindNewestModuleVersionInBucketAsync(bucketName); // check if module found at origin should be kept instead if ( (originResult.Version != null) && ( (result.Version == null) || (moduleInfo.Version?.IsPreRelease() ?? false) || originResult.Version.IsGreaterThanVersion(result.Version) ) ) { result = originResult; } } // check if a module was found if (result.Version == null) { // could not find a matching version var versionConstraint = (moduleInfo.Version != null) ? $"v{moduleInfo.Version} or later" : "any released version"; if (showError) { if (allowImport) { LogError($"could not find module '{moduleInfo}' ({versionConstraint})"); } else { LogError($"missing module dependency must be imported explicitly '{moduleInfo}' ({versionConstraint})"); } } return(null); } LogInfoVerbose($"... selected module {moduleInfo.WithVersion(result.Version)} from {result.Origin}"); return(MakeModuleLocation(result.Origin, result.Manifest)); } finally { StopLogPerformance(cached); } async Task <(string Origin, VersionInfo Version, ModuleManifest Manifest)> FindNewestModuleVersionInBucketAsync(string bucketName) { StartLogPerformance($"FindNewestModuleVersionInBucketAsync() for s3://{bucketName}"); try { // enumerate versions in bucket var found = await FindModuleVersionsInBucketAsync(bucketName); if (!found.Any()) { return(Origin : bucketName, Version : null, Manifest : null); } // NOTE (2019-08-12, bjorg): if the module is nested, we filter the list of found versions to // only contain versions that meet the module version constraint; for shared modules, we want to // keep the latest version that is compatible with the tool and is equal-or-greater than the // module version constraint. if ((dependencyType == ModuleManifestDependencyType.Nested) && (moduleInfo.Version != null)) { found = found.Where(version => { if (!version.MatchesConstraint(moduleInfo.Version)) { LogInfoVerbose($"... rejected v{version}: does not match version constraint {moduleInfo.Version}"); return(false); } return(true); }).ToList(); } // attempt to identify the newest module version compatible with the tool ModuleManifest manifest = null; var match = VersionInfo.FindLatestMatchingVersion(found, moduleInfo.Version, candidateVersion => { var candidateModuleInfo = new ModuleInfo(moduleInfo.Namespace, moduleInfo.Name, candidateVersion, moduleInfo.Origin); // check if the module version is allowed by the build policy if (!(Settings.BuildPolicy?.Modules?.Allow?.Contains(candidateModuleInfo.ToString()) ?? true)) { LogInfoVerbose($"... rejected v{candidateVersion}: not allowed by build policy"); return(false); } // load module manifest var(candidateManifest, candidateManifestErrorReason) = LoadManifestFromLocationAsync(new ModuleLocation(bucketName, candidateModuleInfo, "<MISSING>")).GetAwaiter().GetResult(); if (candidateManifest == null) { LogInfoVerbose($"... rejected v{candidateVersion}: {candidateManifestErrorReason}"); return(false); } // check if module is compatible with this tool if (!VersionInfoCompatibility.IsModuleCoreVersionCompatibleWithToolVersion(candidateManifest.CoreServicesVersion, Settings.ToolVersion)) { LogInfoVerbose($"... rejected v{candidateVersion}: not compatible with tool version {Settings.ToolVersion}"); return(false); } // keep this manifest manifest = candidateManifest; return(true); }); return(Origin : bucketName, Version : match, Manifest : manifest); } finally { StopLogPerformance(); } } async Task <IEnumerable <VersionInfo> > FindModuleVersionsInBucketAsync(string bucketName) { StartLogPerformance($"FindModuleVersionsInBucketAsync() for s3://{bucketName}"); var cached = false; try { var moduleOrigin = moduleInfo.Origin ?? Settings.DeploymentBucketName; List <VersionInfo> versions = null; string region = null; // check if a cached version exists string cachedManifestVersionsFilePath = null; if (!Settings.ForceRefresh) { var cachedManifestFolder = GetCachedManifestDirectory(bucketName, moduleOrigin, moduleInfo.Namespace, moduleInfo.Name); if (cachedManifestFolder != null) { cachedManifestVersionsFilePath = Path.Combine(cachedManifestFolder, "versions.json"); if ( File.Exists(cachedManifestVersionsFilePath) && (File.GetLastWriteTimeUtc(cachedManifestVersionsFilePath).Add(Settings.CachedManifestListingExpiration) > DateTime.UtcNow) ) { cached = true; var cachedManifestVersions = JsonSerializer.Deserialize <ModuleManifestVersions>(File.ReadAllText(cachedManifestVersionsFilePath), Settings.JsonSerializerOptions); region = cachedManifestVersions.Region; versions = cachedManifestVersions.Versions; } } } // check if data needs to be fetched from S3 bucket if (versions == null) { // get bucket region specific S3 client var s3Client = await GetS3ClientByBucketNameAsync(bucketName); if (s3Client == null) { // nothing to do; GetS3ClientByBucketName already emitted an error return(new List <VersionInfo>()); } // enumerate versions in bucket versions = new List <VersionInfo>(); region = s3Client.Config.RegionEndpoint.SystemName; var request = new ListObjectsV2Request { BucketName = bucketName, Prefix = $"{moduleOrigin}/{moduleInfo.Namespace}/{moduleInfo.Name}/", Delimiter = "/", MaxKeys = 100, RequestPayer = RequestPayer.Requester }; do { try { var response = await s3Client.ListObjectsV2Async(request); versions.AddRange(response.S3Objects .Select(s3Object => s3Object.Key.Substring(request.Prefix.Length)) .Select(found => VersionInfo.Parse(found)) ); request.ContinuationToken = response.NextContinuationToken; } catch (AmazonS3Exception e) when(e.Message == "Access Denied") { // show message that access was denied for this location LogInfoVerbose($"... access denied to {bucketName} [{s3Client.Config.RegionEndpoint.SystemName}]"); return(Enumerable.Empty <VersionInfo>()); } } while(request.ContinuationToken != null); // cache module versions listing if (cachedManifestVersionsFilePath != null) { try { File.WriteAllText(cachedManifestVersionsFilePath, JsonSerializer.Serialize(new ModuleManifestVersions { Region = region, Versions = versions }, Settings.JsonSerializerOptions)); } catch { // nothing to do } } } // filter list down to matching versions versions = versions.Where(version => (moduleInfo.Version == null) || version.IsGreaterOrEqualThanVersion(moduleInfo.Version, strict: true)).ToList(); LogInfoVerbose($"... found {versions.Count} version{((versions.Count == 1) ? "" : "s")} in {bucketName} [{region}]"); return(versions); } finally { StopLogPerformance(cached); } } ModuleLocation MakeModuleLocation(string sourceBucketName, ModuleManifest manifest) => new ModuleLocation(sourceBucketName, manifest.ModuleInfo, manifest.TemplateChecksum); }
public void NewCSharpFunction( Settings settings, string functionName, string rootNamespace, string framework, string workingDirectory, string moduleFile, int functionMemory, int functionTimeout, string projectDirectory, FunctionType functionType ) { if (functionName == "Finalizer") { // always of type finalizer functionType = FunctionType.Finalizer; functionTimeout = 900; } else if (functionType == FunctionType.Unknown) { // prompt for function type functionType = Enum.Parse <FunctionType>(settings.PromptChoice("Select function type", _functionTypes), ignoreCase: true); } // fetch resource names for this function type var frameworkFolder = framework.Replace(".", ""); var sourceResourceNamesPrefix = $"{frameworkFolder}.NewCSharpFunction-{functionType}."; var sourceResourceNames = GetResourceNames(sourceResourceNamesPrefix); if (sourceResourceNames.Length == 0) { LogError("function type is not supported for selected framework"); return; } // create files for the project var substitutions = new Dictionary <string, string> { ["FRAMEWORK"] = framework, ["ROOTNAMESPACE"] = rootNamespace, ["LAMBDASHARP_VERSION"] = VersionInfoCompatibility.GetLambdaSharpAssemblyWildcardVersion(settings.ToolVersion, framework) }; var projectSourceResourceName = $"{sourceResourceNamesPrefix}xml"; foreach (var sourceResourceName in sourceResourceNames) { var sourceContents = ReadResource(sourceResourceName, substitutions); if (sourceResourceName == projectSourceResourceName) { // create function project var projectFile = Path.Combine(projectDirectory, functionName + ".csproj"); try { File.WriteAllText(projectFile, sourceContents); Console.WriteLine($"Created project file: {Path.GetRelativePath(Directory.GetCurrentDirectory(), projectFile)}"); } catch (Exception e) { LogError($"unable to create project file '{projectFile}'", e); return; } } else { // create source file var otherFile = Path.Combine(projectDirectory, Path.GetFileNameWithoutExtension(sourceResourceName.Substring(sourceResourceNamesPrefix.Length)).Replace("_", ".")); try { File.WriteAllText(otherFile, sourceContents); Console.WriteLine($"Created file: {Path.GetRelativePath(Directory.GetCurrentDirectory(), otherFile)}"); } catch (Exception e) { LogError($"unable to create file '{otherFile}'", e); return; } } } }
protected async Task <bool> PopulateDeploymentTierSettingsAsync( Settings settings, bool requireBucketName = true, bool requireCoreServices = true, bool requireVersionCheck = true, bool optional = false, bool force = false, bool allowCaching = false ) { var stopwatch = System.Diagnostics.Stopwatch.StartNew(); var cached = false; try { if ( (settings.DeploymentBucketName == null) || (settings.TierVersion == null) || force ) { var cachedDeploymentTierSettings = Path.Combine(Settings.AwsProfileCacheDirectory, $"{settings.TierPrefix}tier.json"); if (!force && allowCaching && Settings.AllowCaching && File.Exists(cachedDeploymentTierSettings)) { var cachedInfo = JsonConvert.DeserializeObject <CachedDeploymentTierSettingsInfo>(await File.ReadAllTextAsync(cachedDeploymentTierSettings)); // initialize settings settings.DeploymentBucketName = cachedInfo.DeploymentBucketName; settings.LoggingBucketName = cachedInfo.LoggingBucketName; settings.TierVersion = cachedInfo.TierVersion; settings.CoreServices = cachedInfo.CoreServices; cached = true; return(true); } // attempt to find an existing core module var stackName = $"{settings.TierPrefix}LambdaSharp-Core"; var existing = await settings.CfnClient.GetStackAsync(stackName, LogError); if (existing.Stack == null) { if (!optional) { LogError($"LambdaSharp tier {settings.TierName} does not exist", new LambdaSharpDeploymentTierSetupException(settings.TierName)); } return(false); } // validate module information var result = true; var tierModuleInfoText = existing.Stack?.GetModuleVersionText(); if (tierModuleInfoText == null) { if (!optional && result) { LogError($"Could not find LambdaSharp tier information for {stackName}"); } result = false; } // read deployment S3 bucket name var tierModuleDeploymentBucketArnParts = GetStackOutput("DeploymentBucket")?.Split(':'); if ((tierModuleDeploymentBucketArnParts == null) && requireBucketName) { if (!optional && result) { LogError("could not find 'DeploymentBucket' output value for deployment tier settings", new LambdaSharpDeploymentTierOutOfDateException(settings.TierName)); } result = false; } if (tierModuleDeploymentBucketArnParts != null) { if ((tierModuleDeploymentBucketArnParts.Length != 6) || (tierModuleDeploymentBucketArnParts[0] != "arn") || (tierModuleDeploymentBucketArnParts[1] != "aws") || (tierModuleDeploymentBucketArnParts[2] != "s3")) { LogError("invalid value 'DeploymentBucket' output value for deployment tier settings", new LambdaSharpDeploymentTierOutOfDateException(settings.TierName)); result = false; tierModuleDeploymentBucketArnParts = null; } } var deploymentBucketName = tierModuleDeploymentBucketArnParts?[5]; // read logging S3 bucket name var tierModuleLoggingBucketArnParts = GetStackOutput("LoggingBucket")?.Split(':'); if (tierModuleLoggingBucketArnParts != null) { if ((tierModuleLoggingBucketArnParts.Length != 6) || (tierModuleLoggingBucketArnParts[0] != "arn") || (tierModuleLoggingBucketArnParts[1] != "aws") || (tierModuleLoggingBucketArnParts[2] != "s3")) { LogError("invalid value 'LoggingBucket' output value for deployment tier settings", new LambdaSharpDeploymentTierOutOfDateException(settings.TierName)); result = false; tierModuleLoggingBucketArnParts = null; } } var loggingBucketName = tierModuleLoggingBucketArnParts?[5]; // do some sanity checks if ( !ModuleInfo.TryParse(tierModuleInfoText, out var tierModuleInfo) || (tierModuleInfo.Namespace != "LambdaSharp") || (tierModuleInfo.Name != "Core") ) { LogError("LambdaSharp tier is not configured propertly", new LambdaSharpDeploymentTierSetupException(settings.TierName)); result = false; } // check if tier and tool versions are compatible if ( !optional && (tierModuleInfo != null) && requireVersionCheck && !VersionInfoCompatibility.IsTierVersionCompatibleWithToolVersion(tierModuleInfo.Version, settings.ToolVersion) ) { var tierToToolVersionComparison = VersionInfoCompatibility.CompareTierVersionToToolVersion(tierModuleInfo.Version, settings.ToolVersion); if (tierToToolVersionComparison < 0) { LogError($"LambdaSharp tier is not up to date (tool: {settings.ToolVersion}, tier: {tierModuleInfo.Version})", new LambdaSharpDeploymentTierOutOfDateException(settings.TierName)); result = false; } else if (tierToToolVersionComparison > 0) { // tier is newer; we expect the tier to be backwards compatible by exposing the same resources as before } else { LogError($"LambdaSharp tool is not compatible (tool: {settings.ToolVersion}, tier: {tierModuleInfo.Version})", new LambdaSharpToolOutOfDateException(tierModuleInfo.Version)); result = false; } } // read tier mode var coreServicesModeText = GetStackOutput("CoreServices"); if (!Enum.TryParse <CoreServices>(coreServicesModeText, true, out var coreServicesMode) && requireCoreServices) { if (!optional && result) { LogError("unable to parse CoreServices output value from stack"); } result = false; } // initialize settings settings.DeploymentBucketName = deploymentBucketName; settings.LoggingBucketName = loggingBucketName; settings.TierVersion = tierModuleInfo?.Version; settings.CoreServices = coreServicesMode; // cache deployment tier settings if (allowCaching && Settings.AllowCaching) { Directory.CreateDirectory(Path.GetDirectoryName(cachedDeploymentTierSettings)); await File.WriteAllTextAsync(cachedDeploymentTierSettings, JsonConvert.SerializeObject(new CachedDeploymentTierSettingsInfo { DeploymentBucketName = settings.DeploymentBucketName, TierVersion = settings.TierVersion, CoreServices = settings.CoreServices })); } return(result); // local functions string GetStackOutput(string key) => existing.Stack?.Outputs.FirstOrDefault(output => output.OutputKey == key)?.OutputValue; } return(true); } finally { Settings.LogInfoPerformance($"PopulateDeploymentTierSettingsAsync() for '{settings.TierName}'", stopwatch.Elapsed, cached); } }
//--- Methods -- public void Register(CommandLineApplication app) { app.Command("init", cmd => { cmd.HelpOption(); cmd.Description = "Create or update a LambdaSharp deployment tier"; // init options var protectStackOption = cmd.Option("--protect", "(optional) Enable termination protection for the CloudFormation stack", CommandOptionType.NoValue); var enableXRayTracingOption = cmd.Option("--xray[:<LEVEL>]", "(optional) Enable service-call tracing with AWS X-Ray for all resources in module (0=Disabled, 1=RootModule, 2=AllModules; RootModule if LEVEL is omitted)", CommandOptionType.SingleOrNoValue); var versionOption = cmd.Option("--version <VERSION>", "(optional) Specify version for LambdaSharp.Core module (default: same as CLI version)", CommandOptionType.SingleValue); var parametersFileOption = cmd.Option("--parameters <FILE>", "(optional) Specify source filename for module parameters (default: none)", CommandOptionType.SingleValue); var forceDeployOption = cmd.Option("--force-deploy", "(optional) Force module deployment", CommandOptionType.NoValue); var quickStartOption = cmd.Option("--quick-start", "(optional, create-only) Use safe defaults for quickly setting up a LambdaSharp deployment tier.", CommandOptionType.NoValue); var coreServicesOption = cmd.Option("--core-services <VALUE>", "(optional, create-only) Select if LambdaSharp.Core services should be enabled or not (either Enabled or Disabled, default prompts)", CommandOptionType.SingleValue); var existingS3BucketNameOption = cmd.Option("--existing-s3-bucket-name <NAME>", "(optional, create-only) Existing S3 bucket name for module deployments (blank value creates new bucket)", CommandOptionType.SingleValue); var localOption = cmd.Option("--local <PATH>", "(optional) Provide a path to a local check-out of the LambdaSharp modules (default: LAMBDASHARP environment variable)", CommandOptionType.SingleValue); var usePublishedOption = cmd.Option("--use-published", "(optional) Force the init command to use the published LambdaSharp modules", CommandOptionType.NoValue); var promptAllParametersOption = cmd.Option("--prompt-all", "(optional) Prompt for all missing parameters values (default: only prompt for missing parameters with no default value)", CommandOptionType.NoValue); var allowUpgradeOption = cmd.Option("--allow-upgrade", "(optional) Allow upgrading LambdaSharp.Core across major releases (default: prompt)", CommandOptionType.NoValue); var skipApiGatewayCheckOption = cmd.Option("--skip-apigateway-check", "(optional) Skip API Gateway role check", CommandOptionType.NoValue); var initSettingsCallback = CreateSettingsInitializer(cmd); AddStandardCommandOptions(cmd); cmd.OnExecute(async() => { ExecuteCommandActions(cmd); // check if .aws/credentials file needs to be created if ( !File.Exists(CredentialsFilePath) && ( (Environment.GetEnvironmentVariable("AWS_ACCESS_KEY_ID") == null) || (Environment.GetEnvironmentVariable("AWS_SECRET_ACCESS_KEY") == null) ) ) { var tmpSettings = new Settings(Version); // prompt for AWS credentials information Console.WriteLine(); tmpSettings.PromptLabel("Create AWS profile"); var accessKey = tmpSettings.PromptString("Enter the AWS Access Key ID"); var secretAccessKey = tmpSettings.PromptString("Enter the AWS Secret Access Key"); var region = tmpSettings.PromptString("Enter the AWS region", "us-east-1"); // create credentials file var credentialsTemplate = ReadResource("credentials.txt", new Dictionary <string, string> { ["REGION"] = region, ["ACCESSKEY"] = accessKey, ["SECRETACCESSKEY"] = secretAccessKey }); try { if (!Directory.Exists(CredentialsFolder)) { Directory.CreateDirectory(CredentialsFolder); } File.WriteAllText(CredentialsFilePath, credentialsTemplate); } catch { LogError("unable to create .aws/credentials file"); return; } } // initialize settings var settings = await initSettingsCallback(); if (settings == null) { return; } // check x-ray settings if (!TryParseEnumOption(enableXRayTracingOption, XRayTracingLevel.Disabled, XRayTracingLevel.RootModule, out var xRayTracingLevel)) { // NOTE (2018-08-04, bjorg): no need to add an error message since it's already added by 'TryParseEnumOption' return; } if (!TryParseEnumOption(coreServicesOption, CoreServices.Undefined, CoreServices.Undefined, out var coreServices)) { // NOTE (2018-08-04, bjorg): no need to add an error message since it's already added by 'TryParseEnumOption' return; } // set initialization parameters var existingS3BucketName = existingS3BucketNameOption.Value(); if (quickStartOption.HasValue()) { // check if --core-services was specified, if not default to 'disabled' coreServices = coreServicesOption.HasValue() ? coreServices : CoreServices.Disabled; // check if --existing-s3-bucket-name was specified, if not default to "" existingS3BucketName = existingS3BucketNameOption.HasValue() ? existingS3BucketNameOption.Value() : ""; } // begin tier initialization await Init( settings, allowDataLoos: true, protectStackOption.HasValue(), forceDeployOption.HasValue(), versionOption.HasValue() ? VersionInfo.Parse(versionOption.Value()) : VersionInfoCompatibility.GetCoreServicesReferenceVersion(Version), usePublishedOption.HasValue() ? null : (localOption.Value() ?? Environment.GetEnvironmentVariable("LAMBDASHARP")), parametersFileOption.Value(), promptAllParametersOption.HasValue(), xRayTracingLevel, coreServices, existingS3BucketName, allowUpgradeOption.HasValue(), skipApiGatewayCheckOption.HasValue(), quickStartOption.HasValue() ); }); }); }
public async Task <bool> Init( Settings settings, bool allowDataLoos, bool protectStack, bool forceDeploy, VersionInfo version, string lambdaSharpPath, string parametersFilename, bool promptAllParameters, XRayTracingLevel xRayTracingLevel, CoreServices coreServices, string existingS3BucketName, bool allowUpgrade, bool skipApiGatewayCheck, bool quickStart ) { // 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 = forceDeploy; var disableCoreServicesForUpgrade = false; if (!createNewTier && !updateExistingTier) { // check if core services state was not specified if (coreServices == CoreServices.Undefined) { // inherit current state coreServices = settings.CoreServices; } // always upgrade Bootstrap state to Enabled, since the former is a transitional state only if (coreServices == CoreServices.Bootstrap) { coreServices = CoreServices.Enabled; } // determine if the deployment tier needs to be updated var tierToToolVersionComparison = VersionInfoCompatibility.CompareTierVersionToToolVersion(settings.TierVersion, settings.ToolVersion); 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.ToolVersion.IsPreRelease() // we're running in contributor mode, which means new binaries may be built || (lambdaSharpPath != null) // deployment tier is running core services state is different from requested state || (settings.CoreServices != coreServices) || await IsNewerCoreModuleVersionAvailable(); // local functions async Task <bool> IsNewerCoreModuleVersionAvailable() { var coreModuleInfo = new ModuleInfo("LambdaSharp", "Core", settings.CoreServicesReferenceVersion, "lambdasharp"); var latestCoreModuleLocation = await new ModelManifestLoader(settings, "").ResolveInfoToLocationAsync( coreModuleInfo, coreModuleInfo.Origin, ModuleManifestDependencyType.Shared, allowImport: true, showError: false ); var latestCoreModuleVersion = latestCoreModuleLocation?.ModuleInfo.Version; return(settings.TierVersion.IsLessThanVersion(latestCoreModuleVersion)); } } else if (tierToToolVersionComparison > 0) { // tier is newer; tool needs to get updated LogError($"LambdaSharp tool is out of date (tool: {settings.ToolVersion}, 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 (!VersionInfoCompatibility.IsTierVersionCompatibleWithToolVersion(settings.TierVersion, settings.ToolVersion)) { // update requires core service to be replaced disableCoreServicesForUpgrade = true; // check if an interactive confirmation prompt is required if (!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.CoreServicesReferenceVersion}?", defaultAnswer: false); } } if (!updateExistingTier) { return(false); } } else if (!forceDeploy) { LogError($"LambdaSharp tool is not compatible (tool: {settings.ToolVersion}, 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) // we have to disable core services to upgrade || disableCoreServicesForUpgrade )) ) { // 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 if (!skipApiGatewayCheck) { await CheckApiGatewayRole(settings); } if (HasErrors) { return(false); } // standard modules var standardModules = new List <string> { "LambdaSharp.Core" }; // 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); } // gather list of module to build and publish standardModules.AddRange( Directory.GetDirectories(Path.Combine(lambdaSharpPath, "Modules")) .Where(directory => File.Exists(Path.Combine(directory, "Module.yml"))) .Select(directory => Path.GetFileName(directory)) .Where(module => module != "LambdaSharp.Core") ); 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: new GitTool(BuildEventsConfig).GetGitShaValue(settings.WorkingDirectory, showWarningOnFailure: false), gitBranch: new GitTool(BuildEventsConfig).GetGitBranch(settings.WorkingDirectory, showWarningOnFailure: false), buildConfiguration: "Release", selector: null, moduleSource: moduleSource, moduleVersion: moduleVersion, forceBuild: true, buildPolicy: null )) { return(false); } // publish module var moduleReference = await buildPublishDeployCommand.PublishStepAsync(settings, forcePublish : true, moduleOrigin : "lambdasharp"); if (moduleReference == null) { return(false); } } } // check if core services do not need to be updated further if (coreServices == CoreServices.Disabled) { if (!updated) { Console.WriteLine(); Console.WriteLine("Core Services disabled. 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) { if (parametersFilename != null) { parameters = CliBuildPublishDeployCommand.ReadInputParametersFiles(settings, parametersFilename); if (HasErrors) { return(false); } } else { parameters = new Dictionary <string, string>(); } } if (quickStart) { // set all LambdaSharp.Core parameters to their default input values if (!parameters.ContainsKey("DeadLetterQueue")) { parameters["DeadLetterQueue"] = ""; } if (!parameters.ContainsKey("LoggingFirehoseStream")) { parameters["LoggingFirehoseStream"] = ""; } if (!parameters.ContainsKey("CoreSecretsKey")) { parameters["CoreSecretsKey"] = ""; } if (!parameters.ContainsKey("LoggingStreamRole")) { parameters["LoggingStreamRole"] = ""; } if (!parameters.ContainsKey("LoggingBucket")) { parameters["LoggingBucket"] = ""; } } // deploy LambdaSharp module foreach (var module in standardModules) { var isLambdaSharpCoreModule = (module == "LambdaSharp.Core"); // explicitly import the LambdaSharp module (if it wasn't built locally) if (lambdaSharpPath == null) { if (!await buildPublishDeployCommand.ImportStepAsync( settings, ModuleInfo.Parse($"{module}:{version}@lambdasharp"), forcePublish: true )) { return(false); } } // deploy module 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, allowDependencyUpgrades: true )) { 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.CoreServicesReferenceVersion.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 '{settings.TierName}'"); } else { Console.WriteLine($"Updating LambdaSharp tier '{settings.TierName}'"); } // 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"] = ( string.IsNullOrEmpty(existingS3BucketName) || existingS3BucketName.StartsWith("arn:", StringComparison.Ordinal) ) ? existingS3BucketName : "arn:aws:s3:::" + existingS3BucketName; } // prompt for missing parameters var templateParameters = await PromptMissingTemplateParameters( settings, stackName, bootstrapParameters, template, quickStart ); 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 {Settings.InfoColor}{stackName}{Settings.ResetColor}"); 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(); Console.WriteLine($"=> Stack update initiated for {Settings.InfoColor}{stackName}{Settings.ResetColor}"); 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); } }