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); if (!ok) { return(false); } await ForceFunctionRuntimeVersionAsync(instance, cancellationToken); var uploadFiles = await UpdateDefaultFilesAsync(package); 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(true); }
internal override async Task <int> RunAsync(CancellationToken cancellationToken) { var context = await Context .WithAzureLogon() .BuildAsync(cancellationToken); var instance = context.Naming.Instance(Instance, ResourceGroup); var rules = new AggregatorRules(context.Azure, context.Logger); bool any = false; foreach (var ruleInformation in await rules.ListAsync(instance, cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); context.Logger.WriteOutput(ruleInformation); any = true; } if (!any) { context.Logger.WriteInfo($"No rules found in aggregator instance {instance.PlainName}."); return(ExitCodes.NotFound); } else { return(ExitCodes.Success); } }
internal override async Task <int> RunAsync(CancellationToken cancellationToken) { var context = await Context .WithAzureLogon() .WithDevOpsLogon() .BuildAsync(cancellationToken); var rules = new AggregatorRules(context.Azure, context.Logger); bool ok = DevOpsEvents.IsValidEvent(Event); if (!ok) { context.Logger.WriteError($"Invalid event type."); return(ExitCodes.InvalidArguments); } if (Local) { ok = await rules.InvokeLocalAsync(Project, Event, WorkItemId, Source, DryRun, SaveMode, ImpersonateExecution, cancellationToken); return(ok ? ExitCodes.Success : ExitCodes.Failure); } else { var instance = context.Naming.Instance(Instance, ResourceGroup); context.Logger.WriteWarning("Untested feature!"); ok = await rules.InvokeRemoteAsync(Account, Project, Event, WorkItemId, instance, Name, DryRun, SaveMode, ImpersonateExecution, cancellationToken); return(ok ? ExitCodes.Success : ExitCodes.Failure); } }
internal async Task <bool> RemoveRuleEventAsync(string @event, InstanceName instance, string rule) { logger.WriteInfo($"Querying the VSTS subscriptions for rule(s) {instance.PlainName}/{rule}"); var serviceHooksClient = vsts.GetClient <ServiceHooksPublisherHttpClient>(); var subscriptions = await serviceHooksClient.QuerySubscriptionsAsync("tfs"); var ruleSubs = subscriptions // TODO can we trust this? // && s.ActionDescription == $"To host {instance.DnsHostName}" .Where(s => s.ConsumerInputs["url"].ToString().StartsWith( instance.FunctionAppUrl)); if (@event != "*") { ruleSubs = ruleSubs.Where(s => s.EventType == @event); } if (rule != "*") { ruleSubs = ruleSubs .Where(s => s.ConsumerInputs["url"].ToString().StartsWith( AggregatorRules.GetInvocationUrl(instance, rule))); } foreach (var ruleSub in ruleSubs) { logger.WriteVerbose($"Deleting subscription {ruleSub.EventDescription}..."); await serviceHooksClient.DeleteSubscriptionAsync(ruleSub.Id); logger.WriteInfo($"Subscription {ruleSub.EventDescription} deleted."); } return(true); }
internal async Task <Guid> Add(string projectName, string @event, InstanceName instance, string ruleName) { logger.WriteVerbose($"Connecting to VSTS..."); var projectClient = vsts.GetClient <ProjectHttpClient>(); var project = await projectClient.GetProject(projectName); logger.WriteInfo($"{projectName} data read."); var rules = new AggregatorRules(azure, logger); logger.WriteVerbose($"Retrieving {ruleName} Function Key..."); (string ruleUrl, string ruleKey) = await rules.GetInvocationUrlAndKey(instance, ruleName); logger.WriteInfo($"{ruleName} Function Key retrieved."); var serviceHooksClient = vsts.GetClient <ServiceHooksPublisherHttpClient>(); // see https://docs.microsoft.com/en-us/vsts/service-hooks/consumers?toc=%2Fvsts%2Fintegrate%2Ftoc.json&bc=%2Fvsts%2Fintegrate%2Fbreadcrumb%2Ftoc.json&view=vsts#web-hooks var subscriptionParameters = new Subscription() { ConsumerId = "webHooks", ConsumerActionId = "httpRequest", ConsumerInputs = new Dictionary <string, string> { { "url", ruleUrl }, { "httpHeaders", $"x-functions-key:{ruleKey}" }, // careful with casing! { "resourceDetailsToSend", "all" }, { "messagesToSend", "none" }, { "detailedMessagesToSend", "none" }, }, EventType = @event, PublisherId = VstsEvents.PublisherId, PublisherInputs = new Dictionary <string, string> { { "projectId", project.Id.ToString() }, /* TODO consider offering these filters * { "tfsSubscriptionId", vsts.ServerId }, * { "teamId", null }, * // Filter events to include only work items under the specified area path. * { "areaPath", null }, * // Filter events to include only work items of the specified type. * { "workItemType", null }, * // Filter events to include only work items with the specified field(s) changed * { "changedFields", null }, * // The string that must be found in the comment. * { "commentPattern", null }, */ }, }; logger.WriteVerbose($"Adding mapping for {@event}..."); var newSubscription = await serviceHooksClient.CreateSubscriptionAsync(subscriptionParameters); logger.WriteInfo($"Event subscription {newSubscription.Id} setup."); return(newSubscription.Id); }
internal async Task <Guid> AddAsync(string projectName, string @event, EventFilters filters, InstanceName instance, string ruleName, bool impersonateExecution, CancellationToken cancellationToken) { async Task <(Uri, string)> RetrieveAzureFunctionUrl(string _ruleName, CancellationToken _cancellationToken) { var rules = new AggregatorRules(azure, logger); return(await rules.GetInvocationUrlAndKey(instance, _ruleName, _cancellationToken)); } return(await CoreAddAsync(projectName, @event, filters, ruleName, impersonateExecution, RetrieveAzureFunctionUrl, "x-functions-key", cancellationToken)); }
internal override async Task <int> RunAsync() { var context = await Context .WithAzureLogon() .Build(); var instance = new InstanceName(Instance); var rules = new AggregatorRules(context.Azure, context.Logger); bool ok = await rules.AddAsync(instance, Name, File); return(ok ? 0 : 1); }
internal override async Task <int> RunAsync(CancellationToken cancellationToken) { var context = await Context .WithAzureLogon() .BuildAsync(cancellationToken); var instance = context.Naming.Instance(Instance, ResourceGroup); var rules = new AggregatorRules(context.Azure, context.Logger); bool ok = await rules.UpdateAsync(instance, Name, File, RequiredVersion, SourceUrl, cancellationToken); return(ok ? ExitCodes.Success : ExitCodes.Failure); }
internal override async Task <int> RunAsync(CancellationToken cancellationToken) { var context = await Context .WithAzureLogon() .BuildAsync(cancellationToken); var instance = new InstanceName(Instance, ResourceGroup); var rules = new AggregatorRules(context.Azure, context.Logger); bool ok = await rules.UpdateAsync(instance, Name, File, RequiredVersion, cancellationToken); return(ok ? 0 : 1); }
internal async Task <Guid> Add(string projectName, string @event, InstanceName instance, string ruleName) { var projectClient = vsts.GetClient <ProjectHttpClient>(); var project = await projectClient.GetProject(projectName); var rules = new AggregatorRules(azure, logger); (string ruleUrl, string ruleKey)invocation = await rules.GetInvocationUrlAndKey(instance, ruleName); var serviceHooksClient = vsts.GetClient <ServiceHooksPublisherHttpClient>(); // TODO see https://docs.microsoft.com/en-us/vsts/service-hooks/events?toc=%2Fvsts%2Fintegrate%2Ftoc.json&bc=%2Fvsts%2Fintegrate%2Fbreadcrumb%2Ftoc.json&view=vsts#work-item-created var subscriptionParameters = new Subscription() { ConsumerId = "webHooks", ConsumerActionId = "httpRequest", ConsumerInputs = new Dictionary <string, string> { { "url", invocation.ruleUrl }, { "httpHeaders", $"x-functions-key:{invocation.ruleKey}" }, { "resourceDetailsToSend", "All" }, // TODO these are not respected!!! { "messagesToSend", "None" }, { "detailedMessagesToSend", "None" }, }, EventType = @event, PublisherId = "tfs", PublisherInputs = new Dictionary <string, string> { { "projectId", project.Id.ToString() }, /* TODO consider offering these filters * { "tfsSubscriptionId", vsts.ServerId }, * { "teamId", null }, * // Filter events to include only work items under the specified area path. * { "areaPath", null }, * // Filter events to include only work items of the specified type. * { "workItemType", null }, * // Filter events to include only work items with the specified field(s) changed * { "changedFields", null }, * // The string that must be found in the comment. * { "commentPattern", null }, */ }, }; var newSubscription = await serviceHooksClient.CreateSubscriptionAsync(subscriptionParameters); return(newSubscription.Id); }
internal override async Task <int> RunAsync(CancellationToken cancellationToken) { var context = await Context .WithAzureLogon() .BuildAsync(cancellationToken); var instance = new InstanceName(Instance, ResourceGroup); var rules = new AggregatorRules(context.Azure, context.Logger); var disable = GetDisableStatus(Disable, Enable); var impersonate = GetEnableStatus(DisableImpersonateExecution, EnableImpersonateExecution); var ok = await rules.ConfigureAsync(instance, Name, disable, impersonate, cancellationToken); return(ok ? 0 : 1); }
internal override async Task <int> RunAsync() { var context = await Context .WithAzureLogon() .Build(); var instance = new InstanceName(Instance, ResourceGroup); var rules = new AggregatorRules(context.Azure, context.Logger); bool ok = false; if (Disable || Enable) { ok = await rules.EnableAsync(instance, Name, Disable); } return(ok ? 0 : 1); }
internal override async Task <int> RunAsync(CancellationToken cancellationToken) { var context = await Context .WithAzureLogon() .BuildAsync(cancellationToken); context.ResourceGroupDeprecationCheck(this.ResourceGroup); var instance = context.Naming.Instance(Instance, ResourceGroup); var rules = new AggregatorRules(context.Azure, context.Logger); var disable = GetDisableStatus(Disable, Enable); var impersonate = GetEnableStatus(DisableImpersonateExecution, EnableImpersonateExecution); var ok = await rules.ConfigureAsync(instance, Name, disable, impersonate, cancellationToken); return(ok ? ExitCodes.Success : ExitCodes.Failure); }
internal async Task <bool> RemoveRuleEventAsync(string @event, InstanceName instance, string projectName, string rule) { logger.WriteInfo($"Querying the Azure DevOps subscriptions for rule(s) {instance.PlainName}/{rule}"); var serviceHooksClient = devops.GetClient <ServiceHooksPublisherHttpClient>(); var subscriptions = await serviceHooksClient.QuerySubscriptionsAsync(DevOpsEvents.PublisherId); var ruleSubs = subscriptions // TODO can we trust this equality? // && s.ActionDescription == $"To host {instance.DnsHostName}" .Where(s => s.ConsumerInputs.GetValue("url", "").StartsWith( instance.FunctionAppUrl)); if (@event != "*") { ruleSubs = ruleSubs.Where(s => string.Equals(s.EventType, @event, StringComparison.OrdinalIgnoreCase)); } if (projectName != "*") { logger.WriteVerbose($"Reading Azure DevOps project data..."); var projectClient = devops.GetClient <ProjectHttpClient>(); var project = await projectClient.GetProject(projectName); logger.WriteInfo($"Project {projectName} data read."); ruleSubs = ruleSubs.Where(s => string.Equals(s.PublisherInputs["projectId"], project.Id.ToString(), StringComparison.OrdinalIgnoreCase)); } if (rule != "*") { var invocationUrl = AggregatorRules.GetInvocationUrl(instance, rule).ToString(); ruleSubs = ruleSubs.Where(s => s.ConsumerInputs .GetValue("url", "") .StartsWith(invocationUrl, StringComparison.OrdinalIgnoreCase)); } foreach (var ruleSub in ruleSubs) { logger.WriteVerbose($"Deleting subscription {ruleSub.EventDescription} {ruleSub.EventType}..."); await serviceHooksClient.DeleteSubscriptionAsync(ruleSub.Id); logger.WriteInfo($"Subscription {ruleSub.EventDescription} {ruleSub.EventType} deleted."); } return(true); }
internal override async Task <int> RunAsync() { var context = await Context .WithAzureLogon() .WithVstsLogon() .Build(); var instance = new InstanceName(Instance); var mappings = new AggregatorMappings(context.Vsts, context.Azure, context.Logger); bool ok = await mappings.RemoveRuleAsync(instance, Name); var rules = new AggregatorRules(context.Azure, context.Logger); //rules.Progress += Instances_Progress; ok = ok && await rules.RemoveAsync(instance, Name); return(ok ? 0 : 1); }
internal override async Task <int> RunAsync(CancellationToken cancellationToken) { var context = await Context .WithAzureLogon() .WithDevOpsLogon() .BuildAsync(cancellationToken); var instance = new InstanceName(Instance, ResourceGroup); var mappings = new AggregatorMappings(context.Devops, context.Azure, context.Logger); bool ok = await mappings.RemoveRuleAsync(instance, Name); var rules = new AggregatorRules(context.Azure, context.Logger); //rules.Progress += Instances_Progress; ok = ok && await rules.RemoveAsync(instance, Name, cancellationToken); return(ok ? 0 : 1); }
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); }
internal override async Task <int> RunAsync() { var context = await Context .WithAzureLogon() .Build(); var instance = new InstanceName(Instance); var rules = new AggregatorRules(context.Azure, context.Logger); bool ok = false; if (Disable || Enable) { ok = await rules.EnableAsync(instance, Name, Disable); } if (!string.IsNullOrEmpty(Update)) { ok = await rules.UpdateAsync(instance, Name, Update); } return(ok ? 0 : 1); }
internal override async Task<int> RunAsync() { var context = await Context .WithAzureLogon() .Build(); var instance = new InstanceName(Instance, ResourceGroup); var rules = new AggregatorRules(context.Azure, context.Logger); bool any = false; foreach (var item in await rules.ListAsync(instance)) { context.Logger.WriteOutput( item, (data) => $"Rule {instance.PlainName}/{item.Name} {(item.Config.Disabled ? "(disabled)" : string.Empty)}"); any = true; } if (!any) { context.Logger.WriteInfo($"No rules found in aggregator instance {instance.PlainName}."); } return 0; }
internal override async Task <int> RunAsync(CancellationToken cancellationToken) { var context = await Context .WithAzureLogon() .WithDevOpsLogon() .BuildAsync(cancellationToken); var instance = context.Naming.Instance(Instance, ResourceGroup); var mappings = new AggregatorMappings(context.Devops, context.Azure, context.Logger, context.Naming); var outcome = await mappings.RemoveRuleAsync(instance, Name); if (outcome == RemoveOutcome.Failed) { return(ExitCodes.Failure); } var rules = new AggregatorRules(context.Azure, context.Logger); bool ok = await rules.RemoveAsync(instance, Name, cancellationToken); return(ok ? ExitCodes.Success : ExitCodes.Failure); }
internal override async Task <int> RunAsync() { var context = await Context .WithAzureLogon() .WithDevOpsLogon() .Build(); var rules = new AggregatorRules(context.Azure, context.Logger); if (Local) { bool ok = await rules.InvokeLocalAsync(Project, Event, WorkItemId, Source, DryRun, SaveMode); return(ok ? 0 : 1); } else { var instance = new InstanceName(Instance, ResourceGroup); context.Logger.WriteWarning("Not implemented yet."); return(2); } }
internal override async Task <int> RunAsync(CancellationToken cancellationToken) { var context = await Context .WithAzureLogon() .BuildAsync(cancellationToken); var instance = new InstanceName(Instance, ResourceGroup); var rules = new AggregatorRules(context.Azure, context.Logger); bool any = false; foreach (var item in await rules.ListAsync(instance, cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); context.Logger.WriteOutput(new RuleOutputData(instance, item)); any = true; } if (!any) { context.Logger.WriteInfo($"No rules found in aggregator instance {instance.PlainName}."); } return(0); }
internal override async Task <int> RunAsync(CancellationToken cancellationToken) { var context = await Context .WithAzureLogon() .WithDevOpsLogon() .BuildAsync(cancellationToken); var rules = new AggregatorRules(context.Azure, context.Logger); if (Local) { bool ok = await rules.InvokeLocalAsync(Project, Event, WorkItemId, Source, DryRun, SaveMode, ImpersonateExecution, cancellationToken); return(ok ? 0 : 1); } else { var instance = context.Naming.Instance(Instance, ResourceGroup); context.Logger.WriteWarning("Untested feature!"); bool ok = await rules.InvokeRemoteAsync(Account, Project, Event, WorkItemId, instance, Name, DryRun, SaveMode, ImpersonateExecution, cancellationToken); return(ok ? 0 : 1); } }
internal async Task <Guid> AddAsync(string projectName, string @event, EventFilters filters, InstanceName instance, string ruleName, bool impersonateExecution, CancellationToken cancellationToken) { logger.WriteVerbose($"Reading Azure DevOps project data..."); var projectClient = devops.GetClient <ProjectHttpClient>(); var project = await projectClient.GetProject(projectName); logger.WriteInfo($"Project {projectName} data read."); var rules = new AggregatorRules(azure, logger); logger.WriteVerbose($"Retrieving {ruleName} Function Key..."); (Uri ruleUrl, string ruleKey) = await rules.GetInvocationUrlAndKey(instance, ruleName, cancellationToken); logger.WriteInfo($"{ruleName} Function Key retrieved."); ruleUrl = ruleUrl.AddToUrl(impersonate: impersonateExecution); // check if the subscription already exists and bail out var query = new SubscriptionsQuery { PublisherId = DevOpsEvents.PublisherId, PublisherInputFilters = new InputFilter[] { new InputFilter { Conditions = new List <InputFilterCondition> (filters.ToFilterConditions()) { new InputFilterCondition { InputId = "projectId", InputValue = project.Id.ToString(), Operator = InputFilterOperator.Equals, CaseSensitive = false } } } }, EventType = @event, ConsumerInputFilters = new InputFilter[] { new InputFilter { Conditions = new List <InputFilterCondition> { new InputFilterCondition { InputId = "url", InputValue = ruleUrl.ToString(), Operator = InputFilterOperator.Equals, CaseSensitive = false } } } } }; cancellationToken.ThrowIfCancellationRequested(); var serviceHooksClient = devops.GetClient <ServiceHooksPublisherHttpClient>(); var queryResult = await serviceHooksClient.QuerySubscriptionsAsync(query); if (queryResult.Results.Any()) { logger.WriteWarning($"There is already such a mapping."); return(Guid.Empty); } // see https://docs.microsoft.com/en-us/azure/devops/service-hooks/consumers?toc=%2Fvsts%2Fintegrate%2Ftoc.json&bc=%2Fvsts%2Fintegrate%2Fbreadcrumb%2Ftoc.json&view=vsts#web-hooks var subscriptionParameters = new Subscription() { ConsumerId = "webHooks", ConsumerActionId = "httpRequest", ConsumerInputs = new Dictionary <string, string> { { "url", ruleUrl.ToString() }, { "httpHeaders", $"x-functions-key:{ruleKey}" }, // careful with casing! { "resourceDetailsToSend", "all" }, { "messagesToSend", "none" }, { "detailedMessagesToSend", "none" }, }, EventType = @event, PublisherId = DevOpsEvents.PublisherId, PublisherInputs = new Dictionary <string, string> (filters.ToInputs()) { { "projectId", project.Id.ToString() }, /* TODO consider offering additional filters using the following * { "tfsSubscriptionId", devops.ServerId }, * { "teamId", null }, * // The string that must be found in the comment. * { "commentPattern", null }, */ }, // Resource Version 1.0 currently needed for WorkItems, newer Version send EMPTY Relation Information. ResourceVersion = "1.0", }; logger.WriteVerbose($"Adding mapping for {@event}..."); var newSubscription = await serviceHooksClient.CreateSubscriptionAsync(subscriptionParameters); logger.WriteInfo($"Event subscription {newSubscription.Id} setup."); return(newSubscription.Id); }
internal async Task <UpdateOutcome> RemapAsync(InstanceName sourceInstance, InstanceName destInstance, string projectName, CancellationToken cancellationToken) { logger.WriteVerbose($"Searching aggregator mappings in Azure DevOps..."); var serviceHooksClient = devops.GetClient <ServiceHooksPublisherHttpClient>(); var subscriptions = await serviceHooksClient.QuerySubscriptionsAsync(); var filteredSubs = subscriptions.Where(s => s.PublisherId == DevOpsEvents.PublisherId && s.ConsumerInputs.GetValue("url", "").StartsWith( sourceInstance.FunctionAppUrl, StringComparison.OrdinalIgnoreCase)); var projectClient = devops.GetClient <ProjectHttpClient>(); var projects = await projectClient.GetProjects(); var projectsDict = projects.ToDictionary(p => p.Id); int processedCount = 0; int succeededCount = 0; foreach (var subscription in filteredSubs) { var foundProject = projectsDict[ new Guid(subscription.PublisherInputs["projectId"]) ]; if (!string.IsNullOrEmpty(projectName) && foundProject.Name != projectName) { logger.WriteInfo($"Skipping mapping {subscription.Id} in project {projectName}"); continue; } if (subscription.Status != SubscriptionStatus.Enabled && subscription.Status != SubscriptionStatus.OnProbation) { logger.WriteInfo($"Skipping mapping {subscription.Id} because has status {subscription.Status}"); continue; } processedCount++; Uri ruleUrl = new Uri(subscription.ConsumerInputs.GetValue("url", MagicConstants.MissingUrl)); string ruleName = ruleUrl.Segments.LastOrDefault() ?? string.Empty; var rules = new AggregatorRules(azure, logger); try { var(destRuleUrl, destRuleKey) = await rules.GetInvocationUrlAndKey(destInstance, ruleName, cancellationToken); // PATCH the object subscription.ConsumerInputs["url"] = destRuleUrl.AbsoluteUri; subscription.ConsumerInputs["httpHeaders"] = $"{MagicConstants.AzureFunctionKeyHeaderName}:{destRuleKey}"; logger.WriteVerbose($"Replacing {subscription.EventType} mapping from {ruleUrl.AbsoluteUri} to {subscription.Url}..."); try { var newSubscription = await serviceHooksClient.UpdateSubscriptionAsync(subscription); logger.WriteInfo($"Event subscription {newSubscription.Id} updated."); succeededCount++; } catch (Exception ex) { logger.WriteError($"Failed updating subscription {subscription.Id}: {ex.Message}."); } } catch (Exception ex) { logger.WriteError($"Destination rule {destInstance.PlainName}/{ruleName} does not exists or cannot retrieve key: {ex.Message}."); } } #pragma warning disable S3358 // Extract this nested ternary operation into an independent statement return(processedCount == 0 ? UpdateOutcome.NotFound : (processedCount > succeededCount) ? UpdateOutcome.Failed : UpdateOutcome.Succeeded); #pragma warning restore S3358 // Extract this nested ternary operation into an independent statement }
internal async Task <Guid> AddAsync(string projectName, string @event, EventFilters filters, InstanceName instance, string ruleName, CancellationToken cancellationToken) { logger.WriteVerbose($"Reading Azure DevOps project data..."); var projectClient = devops.GetClient <ProjectHttpClient>(); var project = await projectClient.GetProject(projectName); logger.WriteInfo($"Project {projectName} data read."); var rules = new AggregatorRules(azure, logger); logger.WriteVerbose($"Retrieving {ruleName} Function Key..."); (string ruleUrl, string ruleKey) = await rules.GetInvocationUrlAndKey(instance, ruleName, cancellationToken); logger.WriteInfo($"{ruleName} Function Key retrieved."); var serviceHooksClient = devops.GetClient <ServiceHooksPublisherHttpClient>(); // check if the subscription already exists and bail out var query = new SubscriptionsQuery { PublisherId = DevOpsEvents.PublisherId, PublisherInputFilters = new InputFilter[] { new InputFilter { Conditions = new List <InputFilterCondition> { new InputFilterCondition { InputId = "projectId", InputValue = project.Id.ToString(), Operator = InputFilterOperator.Equals, CaseSensitive = false } } } }, EventType = @event, ConsumerInputFilters = new InputFilter[] { new InputFilter { Conditions = new List <InputFilterCondition> { new InputFilterCondition { InputId = "url", InputValue = ruleUrl, Operator = InputFilterOperator.Equals, CaseSensitive = false } } } } }; cancellationToken.ThrowIfCancellationRequested(); var queryResult = await serviceHooksClient.QuerySubscriptionsAsync(query); if (queryResult.Results.Any()) { logger.WriteWarning($"There is already such a mapping."); return(Guid.Empty); } // see https://docs.microsoft.com/en-us/azure/devops/service-hooks/consumers?toc=%2Fvsts%2Fintegrate%2Ftoc.json&bc=%2Fvsts%2Fintegrate%2Fbreadcrumb%2Ftoc.json&view=vsts#web-hooks var subscriptionParameters = new Subscription() { ConsumerId = "webHooks", ConsumerActionId = "httpRequest", ConsumerInputs = new Dictionary <string, string> { { "url", ruleUrl }, { "httpHeaders", $"x-functions-key:{ruleKey}" }, // careful with casing! { "resourceDetailsToSend", "all" }, { "messagesToSend", "none" }, { "detailedMessagesToSend", "none" }, }, EventType = @event, PublisherId = DevOpsEvents.PublisherId, PublisherInputs = new Dictionary <string, string> { { "projectId", project.Id.ToString() }, /* TODO consider offering additional filters using the following * { "tfsSubscriptionId", devops.ServerId }, * { "teamId", null }, * // The string that must be found in the comment. * { "commentPattern", null }, */ }, }; if (!string.IsNullOrWhiteSpace(filters.AreaPath)) { // Filter events to include only work items under the specified area path. subscriptionParameters.PublisherInputs.Add("areaPath", filters.AreaPath); } if (!string.IsNullOrWhiteSpace(filters.Type)) { // Filter events to include only work items of the specified type. subscriptionParameters.PublisherInputs.Add("workItemType", filters.Type); } if (!string.IsNullOrWhiteSpace(filters.Tag)) { // Filter events to include only work items containing the specified tag. subscriptionParameters.PublisherInputs.Add("tag", filters.Tag); } if (filters.Fields.Any()) { // Filter events to include only work items with the specified field(s) changed subscriptionParameters.PublisherInputs.Add("changedFields", string.Join(',', filters.Fields)); } logger.WriteVerbose($"Adding mapping for {@event}..."); var newSubscription = await serviceHooksClient.CreateSubscriptionAsync(subscriptionParameters); logger.WriteInfo($"Event subscription {newSubscription.Id} setup."); return(newSubscription.Id); }