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."); } } }
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); }
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]); } } }
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); } }
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); } }
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); }
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); }
protected KuduApi GetKudu(InstanceName instance) { var kudu = new KuduApi(instance, _azure, _logger); return(kudu); }
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); } }