// 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));
            }
        }
Пример #4
0
        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));
            }
        }
Пример #5
0
        private void DeleteFunctionArtifacts(FunctionMetadataResponse function)
        {
            var testDataPath = function.GetFunctionTestDataFilePath(_config);

            if (!string.IsNullOrEmpty(testDataPath))
            {
                FileUtility.DeleteFileSafe(testDataPath);
            }
        }
Пример #6
0
        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());
     }
 }
Пример #8
0
        /// <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);
        }
Пример #9
0
        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));
            }
        }
Пример #11
0
        /// <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());
            }
        }
Пример #12
0
        /// <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());
            }
        }
Пример #13
0
        /// <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);
        }
Пример #14
0
        /// <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);