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);
            }
        }
Example #4
0
        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);
        }
Example #5
0
        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));
        }
Example #7
0
        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);
        }
Example #10
0
        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);
        }
Example #12
0
        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);
        }
Example #13
0
        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);
        }
Example #14
0
        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);
        }
Example #15
0
        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);
        }
Example #16
0
        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);
        }
Example #18
0
        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);
        }
Example #19
0
 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);
        }
Example #21
0
        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);
        }
Example #23
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);
            }
        }
Example #24
0
        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);
        }
Example #25
0
        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);
        }