Example #1
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 #3
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 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);
        }
Example #5
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 #6
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
        }