// parse metadata response into RawFunctionMetadata objects for AggregateFunctionMetadataProvider to further parse and validate internal void ProcessFunctionMetadataResponses(FunctionMetadataResponse functionMetadataResponse) { _workerChannelLogger.LogDebug("Received the worker function metadata response from worker {worker_id}", _workerId); if (functionMetadataResponse.Result.IsFailure(out Exception metadataResponseEx)) { _workerChannelLogger?.LogError(metadataResponseEx, "Worker failed to index functions"); } var functions = new List <RawFunctionMetadata>(); if (functionMetadataResponse.UseDefaultMetadataIndexing == false) { foreach (var metadata in functionMetadataResponse.FunctionMetadataResults) { if (metadata == null) { continue; } if (metadata.Status != null && metadata.Status.IsFailure(out Exception metadataRequestEx)) { _workerChannelLogger.LogError($"Worker failed to index function {metadata.FunctionId}"); _metadataRequestErrors[metadata.FunctionId] = metadataRequestEx; } var functionMetadata = new FunctionMetadata() { FunctionDirectory = metadata.Directory, ScriptFile = metadata.ScriptFile, EntryPoint = metadata.EntryPoint, Name = metadata.Name }; functionMetadata.SetFunctionId(metadata.FunctionId); var bindings = new List <string>(); foreach (string binding in metadata.RawBindings) { bindings.Add(binding); } functions.Add(new RawFunctionMetadata() { Metadata = functionMetadata, Bindings = bindings, UseDefaultMetadataIndexing = functionMetadataResponse.UseDefaultMetadataIndexing }); } } else { functions.Add(new RawFunctionMetadata() { UseDefaultMetadataIndexing = functionMetadataResponse.UseDefaultMetadataIndexing }); } // set it as task result because we cannot directly return from SendWorkerMetadataRequest _functionsIndexingTask.SetResult(functions); }
/// <summary> /// Maps FunctionMetadata to FunctionMetadataResponse. /// </summary> /// <param name="functionMetadata">FunctionMetadata to be mapped.</param> /// <param name="request">Current HttpRequest</param> /// <param name="config">ScriptHostConfig</param> /// <returns>Promise of a FunctionMetadataResponse</returns> public static async Task <FunctionMetadataResponse> ToFunctionMetadataResponse(this FunctionMetadata functionMetadata, HttpRequest request, ScriptHostConfiguration config) { var functionPath = Path.Combine(config.RootScriptPath, functionMetadata.Name); var functionMetadataFilePath = Path.Combine(functionPath, ScriptConstants.FunctionMetadataFileName); var baseUrl = request != null ? $"{request.Scheme}://{request.Host}" : "https://localhost/"; var response = new FunctionMetadataResponse { Name = functionMetadata.Name, // Q: can functionMetadata.ScriptFile be null or empty? ScriptHref = VirtualFileSystem.FilePathToVfsUri(Path.Combine(config.RootScriptPath, functionMetadata.ScriptFile), baseUrl, config), ConfigHref = VirtualFileSystem.FilePathToVfsUri(functionMetadataFilePath, baseUrl, config), ScriptRootPathHref = VirtualFileSystem.FilePathToVfsUri(functionPath, baseUrl, config, isDirectory: true), TestDataHref = VirtualFileSystem.FilePathToVfsUri(functionMetadata.GetTestDataFilePath(config), baseUrl, config), Href = GetFunctionHref(functionMetadata.Name, baseUrl), TestData = await GetTestData(functionMetadata.GetTestDataFilePath(config), config), Config = await GetFunctionConfig(functionMetadataFilePath, config), // Properties below this comment are not present in the kudu version. IsDirect = functionMetadata.IsDirect, IsDisabled = functionMetadata.IsDisabled, IsProxy = functionMetadata.IsProxy }; return(response); }
public async Task <IActionResult> CreateOrUpdate(string name, [FromBody] FunctionMetadataResponse functionMetadata, [FromServices] IFileMonitoringService fileMonitoringService) { if (!Utility.IsValidFunctionName(name)) { return(BadRequest($"{name} is not a valid function name")); } bool success, configChanged; FunctionMetadataResponse functionMetadataResponse; using (fileMonitoringService.SuspendRestart()) { (success, configChanged, functionMetadataResponse) = await _functionsManager.CreateOrUpdate(name, functionMetadata, Request); } await ScheduleRestartAsync(fileMonitoringService); if (success) { return(Created(Request.GetDisplayUrl(), functionMetadataResponse)); } else { return(StatusCode(500)); } }
public async Task <IActionResult> CreateOrUpdate(string name, [FromBody] FunctionMetadataResponse functionMetadata, [FromServices] IFileMonitoringService fileMonitoringService) { if (_applicationHostOptions.Value.IsFileSystemReadOnly) { return(BadRequest("Your app is currently in read only mode. Cannot create or update functions.")); } if (!Utility.IsValidFunctionName(name)) { return(BadRequest($"{name} is not a valid function name")); } bool success, configChanged; FunctionMetadataResponse functionMetadataResponse; using (fileMonitoringService.SuspendRestart(true)) { (success, configChanged, functionMetadataResponse) = await _functionsManager.CreateOrUpdate(name, functionMetadata, Request); } if (success) { return(Created(Request.GetDisplayUrl(), functionMetadataResponse)); } else { return(StatusCode(500)); } }
private void DeleteFunctionArtifacts(FunctionMetadataResponse function) { var testDataPath = function.GetFunctionTestDataFilePath(_config); if (!string.IsNullOrEmpty(testDataPath)) { FileUtility.DeleteFileSafe(testDataPath); } }
private void DeleteFunctionArtifacts(FunctionMetadataResponse function) { var hostOptions = _applicationHostOptions.CurrentValue.ToHostOptions(); var testDataPath = function.GetFunctionTestDataFilePath(hostOptions); if (!string.IsNullOrEmpty(testDataPath)) { FileUtility.DeleteFileSafe(testDataPath); } }
/// <summary> /// Delete a function and all it's artifacts. /// </summary> /// <param name="function">Function to be deleted</param> /// <returns>(success, errorMessage)</returns> public (bool, string) TryDeleteFunction(FunctionMetadataResponse function) { try { FileUtility.DeleteDirectoryContentsSafe(function.GetFunctionPath(_config)); DeleteFunctionArtifacts(function); return(true, string.Empty); } catch (Exception e) { return(false, e.ToString()); } }
/// <summary> /// Maps FunctionMetadata to FunctionMetadataResponse. /// </summary> /// <param name="functionMetadata">FunctionMetadata to be mapped.</param> /// <param name="hostOptions">The host options</param> /// <returns>Promise of a FunctionMetadataResponse</returns> public static async Task <FunctionMetadataResponse> ToFunctionMetadataResponse(this FunctionMetadata functionMetadata, ScriptJobHostOptions hostOptions, string routePrefix, string baseUrl) { string functionPath = GetFunctionPathOrNull(hostOptions.RootScriptPath, functionMetadata.Name); string functionMetadataFilePath = GetMetadataPathOrNull(functionPath); if (string.IsNullOrEmpty(baseUrl)) { baseUrl = "https://localhost/"; } var response = new FunctionMetadataResponse { Name = functionMetadata.Name, Href = GetFunctionHref(functionMetadata.Name, baseUrl), Config = await GetFunctionConfig(functionMetadata, functionMetadataFilePath), // Properties below this comment are not present in the kudu version. IsDirect = functionMetadata.IsDirect(), IsDisabled = functionMetadata.IsDisabled(), IsProxy = functionMetadata.IsProxy(), Language = functionMetadata.Language, InvokeUrlTemplate = GetFunctionInvokeUrlTemplate(baseUrl, functionMetadata, routePrefix) }; if (!string.IsNullOrEmpty(functionPath)) { response.ScriptRootPathHref = VirtualFileSystem.FilePathToVfsUri(functionPath, baseUrl, hostOptions, isDirectory: true); } if (!string.IsNullOrEmpty(functionMetadataFilePath)) { response.ConfigHref = VirtualFileSystem.FilePathToVfsUri(functionMetadataFilePath, baseUrl, hostOptions); } if (!string.IsNullOrEmpty(hostOptions.TestDataPath)) { var testDataFilePath = functionMetadata.GetTestDataFilePath(hostOptions); response.TestDataHref = VirtualFileSystem.FilePathToVfsUri(testDataFilePath, baseUrl, hostOptions); response.TestData = await GetTestData(testDataFilePath, hostOptions); } if (!string.IsNullOrEmpty(functionMetadata.ScriptFile)) { response.ScriptHref = VirtualFileSystem.FilePathToVfsUri(Path.Combine(hostOptions.RootScriptPath, functionMetadata.ScriptFile), baseUrl, hostOptions); } return(response); }
public void PublishWorkerMetadataResponse(string workerId, string functionId, IEnumerable <FunctionMetadata> functionMetadata, bool successful, bool useDefaultMetadataIndexing = false, bool overallStatus = true) { StatusResult statusResult = new StatusResult(); if (successful) { statusResult.Status = StatusResult.Types.Status.Success; } else { statusResult.Status = StatusResult.Types.Status.Failure; } FunctionMetadataResponse overallResponse = new FunctionMetadataResponse(); overallResponse.UseDefaultMetadataIndexing = useDefaultMetadataIndexing; if (functionMetadata != null) { foreach (FunctionMetadata response in functionMetadata) { RpcFunctionMetadata indexingResponse = new RpcFunctionMetadata() { Name = response.Name, Language = response.Language, Status = statusResult, FunctionId = functionId }; overallResponse.FunctionMetadataResults.Add(indexingResponse); } } overallResponse.Result = new StatusResult() { Status = overallStatus == true ? StatusResult.Types.Status.Success : StatusResult.Types.Status.Failure }; StreamingMessage responseMessage = new StreamingMessage() { FunctionMetadataResponse = overallResponse }; _eventManager.Publish(new InboundGrpcEvent(_workerId, responseMessage)); }
public async Task <IActionResult> CreateOrUpdate(string name, [FromBody] FunctionMetadataResponse functionMetadata) { if (!Utility.IsValidFunctionName(name)) { return(BadRequest($"{name} is not a valid function name")); } (var success, var configChanged, var functionMetadataResponse) = await _functionsManager.CreateOrUpdate(name, functionMetadata, Request); if (success) { return(Created(Request.GetDisplayUrl(), functionMetadataResponse)); } else { return(StatusCode(500)); } }
/// <summary> /// Delete a function and all it's artifacts. /// </summary> /// <param name="function">Function to be deleted</param> /// <returns>(success, errorMessage)</returns> public (bool, string) TryDeleteFunction(FunctionMetadataResponse function) { try { var functionPath = function.GetFunctionPath(_hostOptions); if (!string.IsNullOrEmpty(functionPath)) { FileUtility.DeleteDirectoryContentsSafe(functionPath); } DeleteFunctionArtifacts(function); return(true, string.Empty); } catch (Exception e) { return(false, e.ToString()); } }
/// <summary> /// Delete a function and all it's artifacts. /// </summary> /// <param name="function">Function to be deleted</param> /// <returns>(success, errorMessage)</returns> public (bool, string) TryDeleteFunction(FunctionMetadataResponse function) { try { var hostOptions = _applicationHostOptions.CurrentValue.ToHostOptions(); var functionPath = function.GetFunctionPath(hostOptions); if (!string.IsNullOrEmpty(functionPath)) { FileUtility.DeleteDirectoryContentsSafe(functionPath); } DeleteFunctionArtifacts(function); _functionsSyncManager.TrySyncTriggersAsync(); return(true, string.Empty); } catch (Exception e) { return(false, e.ToString()); } }
/// <summary> /// Maps FunctionMetadata to FunctionMetadataResponse. /// </summary> /// <param name="functionMetadata">FunctionMetadata to be mapped.</param> /// <param name="request">Current HttpRequest</param> /// <param name="hostOptions">The host options</param> /// <returns>Promise of a FunctionMetadataResponse</returns> public static async Task <FunctionMetadataResponse> ToFunctionMetadataResponse(this FunctionMetadata functionMetadata, HttpRequest request, ScriptJobHostOptions hostOptions, string routePrefix) { var functionPath = Path.Combine(hostOptions.RootScriptPath, functionMetadata.Name); var functionMetadataFilePath = Path.Combine(functionPath, ScriptConstants.FunctionMetadataFileName); var baseUrl = request != null ? $"{request.Scheme}://{request.Host}" : "https://localhost/"; var response = new FunctionMetadataResponse { Name = functionMetadata.Name, ConfigHref = VirtualFileSystem.FilePathToVfsUri(functionMetadataFilePath, baseUrl, hostOptions), ScriptRootPathHref = VirtualFileSystem.FilePathToVfsUri(functionPath, baseUrl, hostOptions, isDirectory: true), Href = GetFunctionHref(functionMetadata.Name, baseUrl), Config = await GetFunctionConfig(functionMetadataFilePath), // Properties below this comment are not present in the kudu version. IsDirect = functionMetadata.IsDirect, IsDisabled = functionMetadata.IsDisabled, IsProxy = functionMetadata.IsProxy, Language = functionMetadata.Language, InvokeUrlTemplate = GetFunctionInvokeUrlTemplate(baseUrl, functionMetadata, routePrefix) }; if (!string.IsNullOrEmpty(hostOptions.TestDataPath)) { var testDataFilePath = functionMetadata.GetTestDataFilePath(hostOptions); response.TestDataHref = VirtualFileSystem.FilePathToVfsUri(testDataFilePath, baseUrl, hostOptions); response.TestData = await GetTestData(testDataFilePath, hostOptions); } if (!string.IsNullOrEmpty(functionMetadata.ScriptFile)) { response.ScriptHref = VirtualFileSystem.FilePathToVfsUri(Path.Combine(hostOptions.RootScriptPath, functionMetadata.ScriptFile), baseUrl, hostOptions); } return(response); }
/// <summary> /// It handles creating a new function or updating an existing one. /// It attempts to clean left over artifacts from a possible previous function with the same name /// if config is changed, then `configChanged` is set to true so the caller can call SyncTriggers if needed. /// </summary> /// <param name="name">name of the function to be created</param> /// <param name="functionMetadata">in case of update for function.json</param> /// <param name="request">Current HttpRequest.</param> /// <returns>(success, configChanged, functionMetadataResult)</returns> public async Task <(bool, bool, FunctionMetadataResponse)> CreateOrUpdate(string name, FunctionMetadataResponse functionMetadata, HttpRequest request) { var configChanged = false; var functionDir = Path.Combine(_config.RootScriptPath, name); // Make sure the function folder exists if (!FileUtility.DirectoryExists(functionDir)) { // Cleanup any leftover artifacts from a function with the same name before. DeleteFunctionArtifacts(functionMetadata); Directory.CreateDirectory(functionDir); } string newConfig = null; string configPath = Path.Combine(functionDir, ScriptConstants.FunctionMetadataFileName); string dataFilePath = FunctionMetadataExtensions.GetTestDataFilePath(name, _config); // If files are included, write them out if (functionMetadata?.Files != null) { // If the config is passed in the file collection, save it and don't process it as a file if (functionMetadata.Files.TryGetValue(ScriptConstants.FunctionMetadataFileName, out newConfig)) { functionMetadata.Files.Remove(ScriptConstants.FunctionMetadataFileName); } // Delete all existing files in the directory. This will also delete current function.json, but it gets recreated below FileUtility.DeleteDirectoryContentsSafe(functionDir); await functionMetadata .Files .Select(e => FileUtility.WriteAsync(Path.Combine(functionDir, e.Key), e.Value)) .WhenAll(); } // Get the config (if it was not already passed in as a file) if (newConfig == null && functionMetadata?.Config != null) { newConfig = JsonConvert.SerializeObject(functionMetadata?.Config, Formatting.Indented); } // Get the current config, if any string currentConfig = null; if (FileUtility.FileExists(configPath)) { currentConfig = await FileUtility.ReadAsync(configPath); } // Save the file and set changed flag is it has changed. This helps optimize the syncTriggers call if (newConfig != currentConfig) { await FileUtility.WriteAsync(configPath, newConfig); configChanged = true; } if (functionMetadata.TestData != null) { await FileUtility.WriteAsync(dataFilePath, functionMetadata.TestData); } (var success, var functionMetadataResult) = await TryGetFunction(name, request); // test_data took from incoming request, it will not exceed the limit return(success, configChanged, functionMetadataResult); }
private void DeleteFunctionArtifacts(FunctionMetadataResponse function) { // TODO: clear secrets // TODO: clear logs FileUtility.DeleteFileSafe(function.GetFunctionTestDataFilePath(_config)); }
public static string GetFunctionPath(this FunctionMetadataResponse function, ScriptJobHostOptions config) => VirtualFileSystem.VfsUriToFilePath(function.ScriptRootPathHref, config);
public static string GetFunctionTestDataFilePath(this FunctionMetadataResponse function, ScriptHostConfiguration config) => VirtualFileSystem.VfsUriToFilePath(function.TestDataHref, config);