diff --git a/.dockerignore b/.dockerignore index a81817b7..ba8ceee3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,10 @@ **/.vscode **/*.zip docker/ +!docker/*.exe +!docker/*.cmd +!docker/*.sh +!docker/*.pem outputs/ marketplace/ src/.build-trigger diff --git a/.gitignore b/.gitignore index 70eabd1e..dd50e9db 100644 --- a/.gitignore +++ b/.gitignore @@ -348,6 +348,7 @@ src/aggregator-function/test*/* src/aggregator-cli/FunctionRuntime.zip .DS_Store *.exe +!docker/certoc.exe *.diagsession *.vsix *.rdp diff --git a/Next-Release-ChangeLog.md b/Next-Release-ChangeLog.md index 2a0a9d34..d8864ab6 100644 --- a/Next-Release-ChangeLog.md +++ b/Next-Release-ChangeLog.md @@ -1,25 +1,29 @@ -This is the final **Release Candidate** version. +This is the inital **1.0** release. CLI Commands and Options ======================== * Resolve code analysis warnings. * Fix incorrect HTTP Header in web hook Subscriptions made by `map.local.rule`. +* Fix incorrect URL in web hook Subscriptions made by `map.local.rule`. Docker and Azure Function Hosting ======================== * Resolve code analysis warnings. -* Fix error CS1705 +* Fix CS1705 error. +* Support for `Aggregator_AzureDevOpsCertificate` when Azure DevOps is using a certificate issued by non-trusted Certification Authority (e.g.self-signed). +* Better handling of API keys. Rule Language ======================== -* optional `events` directive +* New optional `events` directive. Rule Interpreter Engine ======================== * Resolve code analysis warnings. +* Improved some messages. Build, Test, Documentation @@ -29,6 +33,9 @@ Build, Test, Documentation * Resolve code analysis warnings. * Fix default branch reference in CI workflow. * Push docker images to GitHub Container Registry (beta) in addition to Docker Hub. +* Local version number is now `0.0.1-localdev`. +* Fix build badge, added SonarQube badge. +* Trimmed `.dockerignore`. File Hashes diff --git a/docker/certoc.exe b/docker/certoc.exe new file mode 100644 index 00000000..758e767e Binary files /dev/null and b/docker/certoc.exe differ diff --git a/docker/linux-x64.Dockerfile b/docker/linux-x64.Dockerfile index c75f30fc..b0ba7865 100644 --- a/docker/linux-x64.Dockerfile +++ b/docker/linux-x64.Dockerfile @@ -37,7 +37,12 @@ ENV Aggregator_ApiKeysPath=/secrets/apikeys.json ENV Logging__LogLevel__Aggregator=Debug ENV ASPNETCORE_URLS=https://*:5320 ENV AGGREGATOR_TELEMETRY_DISABLED=false +ENV Aggregator_AzureDevOpsCertificate= EXPOSE 5320/tcp -ENTRYPOINT [ "dotnet", "aggregator-host.dll" ] +COPY ./docker/start.sh /app/start.sh +RUN chmod +x /app/start.sh + +ENTRYPOINT /app/start.sh +#ENTRYPOINT [ "dotnet", "aggregator-host.dll" ] diff --git a/docker/start.cmd b/docker/start.cmd new file mode 100644 index 00000000..79d8f1c1 --- /dev/null +++ b/docker/start.cmd @@ -0,0 +1,21 @@ +@ECHO OFF + +REM Bypass "Terminate Batch Job" prompt. +if "%~1"=="-FIXED_CTRL_C" ( + REM Remove the -FIXED_CTRL_C parameter + SHIFT +) ELSE ( + REM Run the batch with GetDeployedRuntimeVersion(InstanceName instance, } else { - logger.WriteWarning($"Cannot read aggregator-manifest.ini: {response.ReasonPhrase}"); + logger.WriteWarning($"Cannot read aggregator-manifest.ini: {response.ReasonPhrase} (disregard this message on new instances)"); uploadedRuntimeVer = new SemVersion(0, 0, 0); } } diff --git a/src/aggregator-cli/Mappings/AggregatorMappings.cs b/src/aggregator-cli/Mappings/AggregatorMappings.cs index 57a49df4..cc2db284 100644 --- a/src/aggregator-cli/Mappings/AggregatorMappings.cs +++ b/src/aggregator-cli/Mappings/AggregatorMappings.cs @@ -91,11 +91,15 @@ internal async Task AddFromUrlAsync(string projectName, string @event, Eve { async Task<(Uri, string)> RetrieveHostedUrl(string _ruleName, CancellationToken _cancellationToken) { - string apiKey = "INVALID"; + string apiKey = MagicConstants.InvalidApiKey; logger.WriteVerbose($"Validating target URL {targetUrl.AbsoluteUri}"); - string userManagedPassword = Environment.GetEnvironmentVariable("Aggregator_SharedSecret"); + string userManagedPassword = Environment.GetEnvironmentVariable(MagicConstants.EnvironmentVariable_SharedSecret); + if (string.IsNullOrEmpty(userManagedPassword)) + { + throw new ApplicationException($"{MagicConstants.EnvironmentVariable_SharedSecret} environment variable is required for this command"); + } string proof = SharedSecret.DeriveFromPassword(userManagedPassword); @@ -115,7 +119,7 @@ internal async Task AddFromUrlAsync(string projectName, string @event, Eve switch (response.StatusCode) { case HttpStatusCode.OK: - logger.WriteVerbose($"Connected to {targetUrl}"); + logger.WriteVerbose($"Connection to {targetUrl} succeded"); apiKey = await response.Content.ReadAsStringAsync(); logger.WriteInfo($"Configuration retrieved."); break; @@ -127,10 +131,13 @@ internal async Task AddFromUrlAsync(string projectName, string @event, Eve } } - logger.WriteInfo($"Target URL is working"); + if (string.IsNullOrEmpty(apiKey) || apiKey == MagicConstants.InvalidApiKey) + { + throw new ApplicationException("Unable to retrieve API Key, please check Shared secret configuration"); + } var b = new UriBuilder(targetUrl); - b.Path += $"/workitem/{_ruleName}"; + b.Path += $"workitem/{_ruleName}"; return (b.Uri, apiKey); } diff --git a/src/aggregator-host/Controllers/ConfigController.cs b/src/aggregator-host/Controllers/ConfigController.cs index 809e1015..011a6fc8 100644 --- a/src/aggregator-host/Controllers/ConfigController.cs +++ b/src/aggregator-host/Controllers/ConfigController.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System; +using System.Text.Json; using aggregator; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -48,15 +49,22 @@ public string RetrieveKey([FromBody] JsonElement body) _log.LogDebug("RetrieveKey method was called!"); string proof = body.GetString(); - string userManagedPassword = _configuration.GetValue("Aggregator_SharedSecret"); + string userManagedPassword = _configuration.GetValue(MagicConstants.EnvironmentVariable_SharedSecret); + if (string.IsNullOrEmpty(userManagedPassword)) + { + throw new ApplicationException($"{MagicConstants.EnvironmentVariable_SharedSecret} environment variable is required by CLI"); + } if (proof == SharedSecret.DeriveFromPassword(userManagedPassword)) { return _apiKeyRepo.PickValidKey(); } + else + { + _log.LogError("API Key request failed from {0}", HttpContext.Connection.RemoteIpAddress); + return MagicConstants.InvalidApiKey; - return "OK"; - + } } } } diff --git a/src/aggregator-ruleng/RuleExecutor.cs b/src/aggregator-ruleng/RuleExecutor.cs index 8cb80339..d0ffe841 100644 --- a/src/aggregator-ruleng/RuleExecutor.cs +++ b/src/aggregator-ruleng/RuleExecutor.cs @@ -40,8 +40,19 @@ private async Task ExecAsyncImpl(IRule rule, WorkItemEventContext eventC // TODO improve from https://github.com/Microsoft/vsts-work-item-migrator using var devops = new VssConnection(eventContext.CollectionUri, clientCredentials); - await devops.ConnectAsync(cancellationToken); - logger.WriteInfo($"Connected to Azure DevOps"); + try + { + await devops.ConnectAsync(cancellationToken); + logger.WriteInfo($"Connected to Azure DevOps"); + } + catch (System.Exception ex) + { + logger.WriteError(ex.Message); + if (ex.InnerException != null) { + logger.WriteError(ex.InnerException.Message); + } + throw ex; + } using var clientsContext = new AzureDevOpsClientsContext(devops); var engine = new RuleEngine(logger, configuration.SaveMode, configuration.DryRun); diff --git a/src/aggregator-shared/MagicConstants.cs b/src/aggregator-shared/MagicConstants.cs index ba828163..847d2b4e 100644 --- a/src/aggregator-shared/MagicConstants.cs +++ b/src/aggregator-shared/MagicConstants.cs @@ -4,8 +4,10 @@ public static class MagicConstants { public const string LoggerCategoryName = "Aggregator"; public const string ApiKeyAuthenticationHeaderName = "X-Auth-ApiKey"; + public const string InvalidApiKey = "INVALID"; #pragma warning disable S1075 // URIs should not be hardcoded public const string MissingUrl = "https://this.should.never.come.up.example.com/"; #pragma warning restore S1075 // URIs should not be hardcoded + public const string EnvironmentVariable_SharedSecret = "Aggregator_SharedSecret"; } }