Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The main scenario for Aggregator (3.x) is supporting Azure DevOps and cloud scen

- use of new Azure DevOps REST API
- simple deployment via CLI tool
- Rule object model similar to v2
- Rule object model similar to Aggregator v2



Expand Down Expand Up @@ -48,7 +48,7 @@ If you specify the Resource Group, you can have more than one Instance in the Re
After creating the Instance, you upload the code of Aggregator **Rules**.
A Rule is code that reacts to one or more Azure DevOps event.
Each Aggregator Rule becomes an Azure Function in the Aggregator instance i.e. the Azure Function Application.
The Rule language is C# (hopefully more in the future) and uses Aggregator Runtime and [Azure Functions Runtime](https://docs.microsoft.com/en-us/azure/azure-functions/functions-versions) 2.0
The Rule language is C# (hopefully more in the future) and uses Aggregator Runtime and [Azure Functions Runtime](https://docs.microsoft.com/en-us/azure/azure-functions/functions-versions) 3.0
to do its work.
When you create an Instance, a Rule or update them, CLI checks GitHub Releases
to ensure that Aggregator Runtime is up-to-date or match the specified version.
Expand Down
59 changes: 59 additions & 0 deletions src/aggregator-cli/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;

namespace aggregator.cli
{
internal static class Extensions
{
internal static async Task<string> GetEmbeddedResourceContent(this Assembly assembly, string resourceName)
{
var fullName = assembly.GetManifestResourceNames()
.SingleOrDefault(str => str.EndsWith(resourceName))
?? throw new FileNotFoundException($"Embedded Resource '{resourceName}' not found.");

string content;
using (var stream = assembly.GetManifestResourceStream(fullName))
{
using (var source = new StreamReader(stream))
{
content = await source.ReadToEndAsync();
}
}

return content;
}

internal static async Task AddFunctionDefaultFiles(this IDictionary<string, string> uploadFiles, Stream assemblyStream)
{
var context = new AssemblyLoadContext(null, isCollectible: true);

using (var memoryStream = new MemoryStream())
{
assemblyStream.CopyTo(memoryStream);
memoryStream.Position = 0;
var assembly = context.LoadFromStream(memoryStream);

await AddFunctionDefaultFiles(uploadFiles, assembly);
}

context.Unload();
}

internal static async Task AddFunctionDefaultFiles(this IDictionary<string, string> uploadFiles, Assembly assembly)
{
{
var content = await assembly.GetEmbeddedResourceContent("function.json");
uploadFiles.Add("function.json", content);
}

{
var content = await assembly.GetEmbeddedResourceContent("run.csx");
uploadFiles.Add("run.csx", content);
}
}
}
}
50 changes: 50 additions & 0 deletions src/aggregator-cli/Instances/AggregatorInstances.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -297,5 +298,54 @@ internal async Task<bool> StreamLogsAsync(InstanceName instance, CancellationTok

return true;
}

internal async Task<bool> UpdateAsync(InstanceName instance, string requiredVersion, string sourceUrl, CancellationToken cancellationToken)
{
// update runtime package
var package = new FunctionRuntimePackage(_logger);
bool ok = await package.UpdateVersionAsync(requiredVersion, sourceUrl, instance, _azure, cancellationToken);

{
// Change V2 to V3 FUNCTIONS_EXTENSION_VERSION ~3
var webFunctionApp = await GetWebApp(instance, cancellationToken);
var currentAzureRuntimeVersion = webFunctionApp.GetAppSettings()
.GetValueOrDefault("FUNCTIONS_EXTENSION_VERSION");
webFunctionApp.Update()
.WithAppSetting("FUNCTIONS_EXTENSION_VERSION", "~3")
.Apply(); ;
}

{
var uploadFiles = new Dictionary<string, string>();
using (var archive = System.IO.Compression.ZipFile.OpenRead(package.RuntimePackageFile))
{
var entry = archive.Entries
.Single(e => string.Equals("aggregator-function.dll", e.Name, StringComparison.OrdinalIgnoreCase));

using (var assemblyStream = entry.Open())
{
await uploadFiles.AddFunctionDefaultFiles(assemblyStream);
}
}
//TODO handle FileNotFound Exception when trying to get resource content, and resource not found

var rules = new AggregatorRules(_azure, _logger);
var allRules = await rules.ListAsync(instance, cancellationToken);

foreach (var ruleName in allRules.Select(r => r.RuleName))
{
_logger.WriteInfo($"Updating Rule '{ruleName}'");
await rules.UploadRuleFilesAsync(instance, ruleName, uploadFiles, cancellationToken);
}
}

return false;
}


private void DomainOnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
//throw new NotImplementedException();
}
}
}
22 changes: 21 additions & 1 deletion src/aggregator-cli/Instances/FunctionRuntimePackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal FunctionRuntimePackage(ILogger logger)
.Version;
}

private string RuntimePackageFile => "FunctionRuntime.zip";
public string RuntimePackageFile => "FunctionRuntime.zip";

internal async Task<bool> UpdateVersionAsync(string requiredVersion, string sourceUrl, InstanceName instance, IAzure azure, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -213,6 +213,26 @@ internal async Task<SemVersion> GetDeployedRuntimeVersion(InstanceName instance,
return uploadedRuntimeVer;
}

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<SemVersion> GetLocalPackageVersionAsync(string runtimePackageFile)
{
if (!File.Exists(runtimePackageFile))
Expand Down
38 changes: 38 additions & 0 deletions src/aggregator-cli/Instances/UpdateInstance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using CommandLine;


namespace aggregator.cli.Instances
{
[Verb("update.instance", HelpText = "Updates an existing Aggregator instance in Azure, with latest runtime binaries.")]
class UpdateInstanceCommand : CommandBase
{
[Option('g', "resourceGroup", Required = false, Default = "", HelpText = "Azure Resource Group hosting the Aggregator instances.")]
public string ResourceGroup { get; set; }
[Option('i', "instance", Required = true, HelpText = "Aggregator instance name.")]
public string Instance { get; set; }

[Option("requiredVersion", SetName = "nourl", Required = false, HelpText = "Version of Aggregator Runtime required.")]
public string RequiredVersion { get; set; }
[Option("sourceUrl", SetName = "url", Required = false, HelpText = "URL of Aggregator Runtime.")]
public string SourceUrl { get; set; }

internal override async Task<int> RunAsync(CancellationToken cancellationToken)
{
var context = await Context
.WithAzureLogon()
.BuildAsync(cancellationToken);

var instances = new AggregatorInstances(context.Azure, context.Logger);
var instance = new InstanceName(Instance, ResourceGroup);

bool ok = await instances.UpdateAsync(instance, RequiredVersion, SourceUrl, cancellationToken);
return ok ? 0 : 1;
}
}
}
2 changes: 1 addition & 1 deletion src/aggregator-cli/Instances/instance-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~2"
"value": "~3"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
Expand Down
2 changes: 2 additions & 0 deletions src/aggregator-cli/Mappings/AggregatorMappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ internal async Task<Guid> AddAsync(string projectName, string @event, EventFilte
{ "commentPattern", null },
*/
},
// Resource Version 1.0 currently needed for WorkItems, newer Version send EMPTY Relation Information.
ResourceVersion = "1.0",
};
if (!string.IsNullOrWhiteSpace(filters.AreaPath))
{
Expand Down
8 changes: 6 additions & 2 deletions src/aggregator-cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
using System;
using System.Threading;

using aggregator.cli.Instances;


namespace aggregator.cli
{
/*
Expand Down Expand Up @@ -60,8 +63,8 @@ void cancelEventHandler(object sender, ConsoleCancelEventArgs e)
{
typeof(TestCommand),
typeof(LogonAzureCommand), typeof(LogonDevOpsCommand),
typeof(ListInstancesCommand), typeof(InstallInstanceCommand), typeof(UninstallInstanceCommand),
typeof(ConfigureInstanceCommand), typeof(StreamLogsCommand),
typeof(ListInstancesCommand), typeof(InstallInstanceCommand), typeof(UpdateInstanceCommand),
typeof(UninstallInstanceCommand), typeof(ConfigureInstanceCommand), typeof(StreamLogsCommand),
typeof(ListRulesCommand), typeof(AddRuleCommand), typeof(RemoveRuleCommand),
typeof(ConfigureRuleCommand), typeof(UpdateRuleCommand), typeof(InvokeRuleCommand),
typeof(ListMappingsCommand), typeof(MapRuleCommand), typeof(UnmapRuleCommand)
Expand All @@ -75,6 +78,7 @@ void cancelEventHandler(object sender, ConsoleCancelEventArgs e)
.WithParsed<LogonDevOpsCommand>(cmd => rc = cmd.Run(cancellationToken))
.WithParsed<ListInstancesCommand>(cmd => rc = cmd.Run(cancellationToken))
.WithParsed<InstallInstanceCommand>(cmd => rc = cmd.Run(cancellationToken))
.WithParsed<UpdateInstanceCommand>(cmd => rc = cmd.Run(cancellationToken))
.WithParsed<UninstallInstanceCommand>(cmd => rc = cmd.Run(cancellationToken))
.WithParsed<ConfigureInstanceCommand>(cmd => rc = cmd.Run(cancellationToken))
.WithParsed<ListRulesCommand>(cmd => rc = cmd.Run(cancellationToken))
Expand Down
Loading