internal async Task <(string url, string key)> GetInvocationUrlAndKey(InstanceName instance, string rule, CancellationToken cancellationToken)
        {
            var instances = new AggregatorInstances(_azure, _logger);
            var kudu      = new KuduApi(instance, _azure, _logger);

            // see https://github.com/projectkudu/kudu/wiki/Functions-API
            using (var client = new HttpClient())
                using (var request = await kudu.GetRequestAsync(HttpMethod.Post, $"api/functions/{rule}/listsecrets", cancellationToken))
                {
                    using (var response = await client.SendAsync(request, cancellationToken))
                    {
                        if (response.IsSuccessStatusCode)
                        {
                            using (var stream = await response.Content.ReadAsStreamAsync())
                                using (var sr = new StreamReader(stream))
                                    using (var jtr = new JsonTextReader(sr))
                                    {
                                        var js     = new JsonSerializer();
                                        var secret = js.Deserialize <KuduSecret>(jtr);

                                        (string url, string key)invocation = (GetInvocationUrl(instance, rule), secret.Key);
                                        return(invocation);
                                    }
                        }

                        string error = await response.Content.ReadAsStringAsync();

                        _logger.WriteError($"Failed to retrieve function key: {error}");
                        throw new InvalidOperationException("Failed to retrieve function key.");
                    }
                }
        }
示例#2
0
        internal async Task <(string url, string key)> GetInvocationUrlAndKey(InstanceName instance, string rule)
        {
            var instances = new AggregatorInstances(azure, logger);
            var kudu      = new KuduApi(instance, azure, logger);

            logger.WriteVerbose($"Querying Function key...");
            // see https://github.com/projectkudu/kudu/wiki/Functions-API
            using (var client = new HttpClient())
                using (var request = await kudu.GetRequestAsync(HttpMethod.Post, $"api/functions/{rule}/listsecrets"))
                {
                    using (var response = await client.SendAsync(request))
                    {
                        if (response.IsSuccessStatusCode)
                        {
                            using (var stream = await response.Content.ReadAsStreamAsync())
                                using (var sr = new StreamReader(stream))
                                    using (var jtr = new JsonTextReader(sr))
                                    {
                                        var js     = new JsonSerializer();
                                        var secret = js.Deserialize <KuduSecret>(jtr);

                                        (string url, string key)invocation = (GetInvocationUrl(instance, rule), secret.Key);
                                        logger.WriteInfo($"Function key retrieved.");
                                        return(invocation);
                                    }
                        }
                        else
                        {
                            return(default);
        internal async Task <IEnumerable <KuduFunction> > ListAsync(InstanceName instance, CancellationToken cancellationToken)
        {
            var kudu = new KuduApi(instance, _azure, _logger);

            _logger.WriteInfo($"Retrieving Functions in {instance.PlainName}...");
            using (var client = new HttpClient())
                using (var request = await kudu.GetRequestAsync(HttpMethod.Get, $"api/functions", cancellationToken))
                    using (var response = await client.SendAsync(request, cancellationToken))
                    {
                        var stream = await response.Content.ReadAsStreamAsync();

                        if (response.IsSuccessStatusCode)
                        {
                            using (var sr = new StreamReader(stream))
                                using (var jtr = new JsonTextReader(sr))
                                {
                                    var js           = new JsonSerializer();
                                    var functionList = js.Deserialize <KuduFunction[]>(jtr);
                                    return(functionList);
                                }
                        }

                        _logger.WriteError($"{response.ReasonPhrase} {await response.Content.ReadAsStringAsync()}");
                        return(new KuduFunction[0]);
                    }
        }
        internal async Task <SemVersion> GetDeployedRuntimeVersion(InstanceName instance, IAzure azure, CancellationToken cancellationToken)
        {
            logger.WriteVerbose($"Retrieving functions runtime from {instance.PlainName} app");
            SemVersion uploadedRuntimeVer;
            var        kudu = new KuduApi(instance, azure, logger);

            using (var client = new HttpClient())
                using (var request = await kudu.GetRequestAsync(HttpMethod.Get, $"api/vfs/site/wwwroot/aggregator-manifest.ini", cancellationToken))
                    using (var response = await client.SendAsync(request, cancellationToken))
                    {
                        string manifest = await response.Content.ReadAsStringAsync();

                        if (response.IsSuccessStatusCode)
                        {
                            uploadedRuntimeVer = ManifestParser.Parse(manifest).Version;
                        }
                        else
                        {
                            logger.WriteWarning($"Cannot read aggregator-manifest.ini: {response.ReasonPhrase} (disregard this message on new instances)");
                            uploadedRuntimeVer = new SemVersion(0, 0, 0);
                        }
                    }

            logger.WriteVerbose($"Function Runtime version is {uploadedRuntimeVer}.");
            return(uploadedRuntimeVer);
        }
示例#5
0
        internal async Task <IEnumerable <KuduFunction> > List(InstanceName instance)
        {
            var instances = new AggregatorInstances(azure, logger);
            var kudu      = new KuduApi(instance, azure, logger);

            using (var client = new HttpClient())
                using (var request = await kudu.GetRequestAsync(HttpMethod.Get, $"api/functions"))
                    using (var response = await client.SendAsync(request))
                    {
                        var stream = await response.Content.ReadAsStreamAsync();

                        if (response.IsSuccessStatusCode)
                        {
                            using (var sr = new StreamReader(stream))
                                using (var jtr = new JsonTextReader(sr))
                                {
                                    var js           = new JsonSerializer();
                                    var functionList = js.Deserialize <KuduFunction[]>(jtr);
                                    return(functionList);
                                }
                        }
                        else
                        {
                            return(new KuduFunction[0]);
                        }
                    }
        }
示例#6
0
        internal async Task <bool> StreamLogsAsync(InstanceName instance, CancellationToken cancellationToken)
        {
            var kudu = new KuduApi(instance, azure, logger);

            logger.WriteVerbose($"Connecting to {instance.PlainName}...");

            // Main takes care of resetting color
            Console.ForegroundColor = ConsoleColor.Green;

            await kudu.StreamLogsAsync(Console.Out, cancellationToken);

            return(true);
        }
        internal async Task <bool> RemoveAsync(InstanceName instance, string name, CancellationToken cancellationToken)
        {
            var kudu = new KuduApi(instance, _azure, _logger);

            // undocumented but works, see https://github.com/projectkudu/kudu/wiki/Functions-API
            _logger.WriteInfo($"Removing Function {name} in {instance.PlainName}...");
            using (var client = new HttpClient())
                using (var request = await kudu.GetRequestAsync(HttpMethod.Delete, $"api/functions/{name}", cancellationToken))
                    using (var response = await client.SendAsync(request, cancellationToken))
                    {
                        bool ok = response.IsSuccessStatusCode;
                        if (!ok)
                        {
                            _logger.WriteError($"Failed removing Function {name} from {instance.PlainName} with {response.ReasonPhrase}");
                        }

                        return(ok);
                    }
        }
示例#8
0
        internal async Task <bool> AddAsync(InstanceName instance, string name, string filePath)
        {
            var kudu = new KuduApi(instance, azure, logger);

            logger.WriteVerbose($"Layout rule files");
            string baseDirPath = LayoutRuleFiles(name, filePath);

            logger.WriteInfo($"Packaging {filePath} into rule {name} complete.");

            logger.WriteVerbose($"Uploading rule files to {instance.PlainName}");
            bool ok = await UploadRuleFiles(instance, name, baseDirPath);

            if (ok)
            {
                logger.WriteInfo($"All {name} files uploaded to {instance.PlainName}.");
            }
            CleanupRuleFiles(baseDirPath);
            logger.WriteInfo($"Cleaned local working directory.");
            return(ok);
        }
        internal static async Task <Stream> GetDeployedFunctionEntrypoint(InstanceName instance, IAzure azure, ILogger logger, CancellationToken cancellationToken)
        {
            logger.WriteVerbose($"Retrieving deployed aggregator-function.dll");
            var kudu = new KuduApi(instance, azure, logger);

            using (var client = new HttpClient())
                using (var request = await kudu.GetRequestAsync(HttpMethod.Get, $"api/vfs/site/wwwroot/bin/aggregator-function.dll", cancellationToken))
                {
                    var response = await client.SendAsync(request, cancellationToken);

                    var stream = await response.Content.ReadAsStreamAsync();

                    if (response.IsSuccessStatusCode)
                    {
                        return(stream);
                    }

                    logger.WriteError($"Cannot read aggregator-function.dll: {response.ReasonPhrase}");
                    return(null);
                }
        }
示例#10
0
        private async Task <bool> UploadRuntimeZip(InstanceName instance, byte[] zipContent)
        {
            var kudu = new KuduApi(instance, azure, logger);
            // POST /api/zipdeploy?isAsync=true
            // Deploy from zip asynchronously. The Location header of the response will contain a link to a pollable deployment status.
            var body = new ByteArrayContent(zipContent);

            using (var client = new HttpClient())
                using (var request = await kudu.GetRequestAsync(HttpMethod.Post, $"api/zipdeploy"))
                {
                    request.Content = body;
                    using (var response = await client.SendAsync(request))
                    {
                        bool ok = response.IsSuccessStatusCode;
                        if (!ok)
                        {
                            logger.WriteError($"Upload failed with {response.ReasonPhrase}");
                        }
                        return(ok);
                    }
                }
        }
        private async Task <bool> UploadRuleFilesAsync(InstanceName instance, string name, IDictionary <string, string> inMemoryFiles, CancellationToken cancellationToken)
        {
            /*
             * PUT /api/vfs/{path}
             * Puts a file at path.
             *
             * PUT /api/vfs/{path}/
             * Creates a directory at path. The path can be nested, e.g. `folder1/folder2`.
             *
             * Note: when updating or deleting a file, ETag behavior will apply. You can pass a If-Match: "*" header to disable the ETag check.
             */
            var kudu        = new KuduApi(instance, _azure, _logger);
            var relativeUrl = $"api/vfs/site/wwwroot/{name}/";

            using (var client = new HttpClient())
            {
                var exists = false;

                // check if function already exists
                using (var request = await kudu.GetRequestAsync(HttpMethod.Head, relativeUrl, cancellationToken))
                {
                    _logger.WriteVerbose($"Checking if function {name} already exists in {instance.PlainName}...");
                    using (var response = await client.SendAsync(request))
                    {
                        exists = response.IsSuccessStatusCode;
                    }
                }

                if (!exists)
                {
                    _logger.WriteVerbose($"Creating function {name} in {instance.PlainName}...");
                    using (var request = await kudu.GetRequestAsync(HttpMethod.Put, relativeUrl, cancellationToken))
                    {
                        using (var response = await client.SendAsync(request, cancellationToken))
                        {
                            bool ok = response.IsSuccessStatusCode;
                            if (!ok)
                            {
                                _logger.WriteError($"Upload failed with {response.ReasonPhrase}");
                                return(false);
                            }
                        }
                    }

                    _logger.WriteInfo($"Function {name} created.");
                }

                foreach (var(fileName, fileContent) in inMemoryFiles)
                {
                    _logger.WriteVerbose($"Uploading {fileName} to {instance.PlainName}...");
                    var fileUrl = $"{relativeUrl}{fileName}";
                    using (var request = await kudu.GetRequestAsync(HttpMethod.Put, fileUrl, cancellationToken))
                    {
                        //HACK -> request.Headers.IfMatch.Add(new EntityTagHeaderValue("*", false)); <- won't work
                        request.Headers.Add("If-Match", "*");
                        request.Content = new StringContent(fileContent);
                        using (var response = await client.SendAsync(request, cancellationToken))
                        {
                            bool ok = response.IsSuccessStatusCode;
                            if (!ok)
                            {
                                _logger.WriteError($"Failed uploading {fileName} with {response.ReasonPhrase}");
                                return(false);
                            }
                        }
                    }

                    _logger.WriteInfo($"{fileName} successfully uploaded to {instance.PlainName}.");
                }//for
            }

            return(true);
        }
示例#12
0
        private async Task <bool> UploadRuleFiles(InstanceName instance, string name, string baseDirPath)
        {
            /*
             * PUT /api/vfs/{path}
             * Puts a file at path.
             *
             * PUT /api/vfs/{path}/
             * Creates a directory at path. The path can be nested, e.g. `folder1/folder2`.
             *
             * Note: when updating or deleting a file, ETag behavior will apply. You can pass a If-Match: "*" header to disable the ETag check.
             */
            var    kudu        = new KuduApi(instance, azure, logger);
            string relativeUrl = $"api/vfs/site/wwwroot/{name}/";

            var instances = new AggregatorInstances(azure, logger);

            using (var client = new HttpClient())
            {
                bool exists = false;

                // check if function already exists
                using (var request = await kudu.GetRequestAsync(HttpMethod.Head, relativeUrl))
                {
                    logger.WriteVerbose($"Checking if function {name} already exists in {instance.PlainName}...");
                    using (var response = await client.SendAsync(request))
                    {
                        exists = response.IsSuccessStatusCode;
                    }
                }

                if (!exists)
                {
                    logger.WriteVerbose($"Creating function {name} in {instance.PlainName}...");
                    using (var request = await kudu.GetRequestAsync(HttpMethod.Put, relativeUrl))
                    {
                        using (var response = await client.SendAsync(request))
                        {
                            bool ok = response.IsSuccessStatusCode;
                            if (!ok)
                            {
                                logger.WriteError($"Upload failed with {response.ReasonPhrase}");
                                return(ok);
                            }
                        }
                    }
                    logger.WriteInfo($"Function {name} created.");
                }

                var files = Directory.EnumerateFiles(baseDirPath, "*", SearchOption.AllDirectories);
                foreach (var file in files)
                {
                    logger.WriteVerbose($"Uploading {Path.GetFileName(file)} to {instance.PlainName}...");
                    string fileUrl = $"{relativeUrl}{Path.GetFileName(file)}";
                    using (var request = await kudu.GetRequestAsync(HttpMethod.Put, fileUrl))
                    {
                        //HACK -> request.Headers.IfMatch.Add(new EntityTagHeaderValue("*", false)); <- won't work
                        request.Headers.Add("If-Match", "*");
                        request.Content = new StringContent(File.ReadAllText(file));
                        using (var response = await client.SendAsync(request))
                        {
                            bool ok = response.IsSuccessStatusCode;
                            if (!ok)
                            {
                                logger.WriteError($"Failed uploading {file} with {response.ReasonPhrase}");
                                return(ok);
                            }
                        }
                    }
                    logger.WriteInfo($"{Path.GetFileName(file)} uploaded to {instance.PlainName}.");
                }//for
            }
            return(true);
        }
示例#13
0
        protected KuduApi GetKudu(InstanceName instance)
        {
            var kudu = new KuduApi(instance, _azure, _logger);

            return(kudu);
        }
示例#14
0
        internal async Task <bool> UpdateVersion(string requiredVersion, InstanceName instance, IAzure azure)
        {
            string tag = string.IsNullOrWhiteSpace(requiredVersion)
                ? "latest"
                : (requiredVersion != "latest"
                    ? (requiredVersion[0] != 'v'
                        ? "v" + requiredVersion
                        : requiredVersion)
                    : requiredVersion);

            logger.WriteVerbose($"Checking runtime package versions in GitHub");
            (string rel_name, DateTimeOffset? rel_when, string rel_url) = await FindVersionInGitHub(tag);

            if (string.IsNullOrEmpty(rel_name))
            {
                logger.WriteError($"Requested runtime {requiredVersion} version does not exists.");
                return(false);
            }
            if (rel_name[0] == 'v')
            {
                rel_name = rel_name.Substring(1);
            }
            var requiredRuntimeVer = SemVersion.Parse(rel_name);

            logger.WriteVerbose($"Latest Runtime package version is {requiredRuntimeVer} (released on {rel_when}).");

            string localPackageVersion = GetLocalPackageVersion(RuntimePackageFile);
            var    localRuntimeVer     = SemVersion.Parse(localPackageVersion);

            logger.WriteVerbose($"Cached Runtime package version is {localRuntimeVer}.");

            // TODO check the uploaded version before overwriting?
            string manifestVersion = "0.0.0";

            logger.WriteVerbose($"Retrieving functions runtime");
            var kudu = new KuduApi(instance, azure, logger);

            using (var client = new HttpClient())
                using (var request = await kudu.GetRequestAsync(HttpMethod.Get, $"api/vfs/site/wwwroot/aggregator-manifest.ini"))
                    using (var response = await client.SendAsync(request))
                    {
                        string manifest = await response.Content.ReadAsStringAsync();

                        if (response.IsSuccessStatusCode)
                        {
                            // HACK refactor so that there is a single class parsing the manifest
                            var parts = manifest.Split('=');
                            if (parts[0] == "version")
                            {
                                manifestVersion = parts[1];
                            }
                        }
                        else
                        {
                            logger.WriteWarning($"Cannot read aggregator-manifest.ini: {response.ReasonPhrase}");
                        }
                    }
            var uploadedRuntimeVer = SemVersion.Parse(manifestVersion);

            logger.WriteVerbose($"Function Runtime version is {uploadedRuntimeVer}.");

            if (requiredRuntimeVer > uploadedRuntimeVer || localRuntimeVer > uploadedRuntimeVer)
            {
                if (requiredRuntimeVer > localRuntimeVer)
                {
                    logger.WriteVerbose($"Downloading runtime package {rel_name}");
                    await Download(rel_url);

                    logger.WriteInfo($"Runtime package downloaded.");
                }
                else
                {
                    logger.WriteInfo($"Using cached runtime package {localRuntimeVer}");
                }

                logger.WriteVerbose($"Uploading runtime package to {instance.DnsHostName}");
                bool ok = await UploadRuntimeZip(instance, azure);

                if (ok)
                {
                    logger.WriteInfo($"Runtime package uploaded to {instance.PlainName}.");
                }
                else
                {
                    logger.WriteError($"Failed uploading Runtime to {instance.DnsHostName}.");
                }
                return(ok);
            }
            else
            {
                logger.WriteInfo($"Runtime package is up to date.");
                return(true);
            }
        }