コード例 #1
0
        internal override async Task <int> RunAsync(CancellationToken cancellationToken)
        {
            var context = await Context
                          .WithAzureLogon()
                          .WithDevOpsLogon()
                          .BuildAsync(cancellationToken);

            context.ResourceGroupDeprecationCheck(this.ResourceGroup);
            var  mappings = new AggregatorMappings(context.Devops, context.Azure, context.Logger, context.Naming);
            bool ok       = DevOpsEvents.IsValidEvent(Event);

            if (!ok)
            {
                context.Logger.WriteError($"Invalid event type.");
                return(ExitCodes.InvalidArguments);
            }

            var filters = new EventFilters
            {
                AreaPath  = FilterAreaPath,
                Type      = FilterType,
                Tag       = FilterTag,
                Fields    = FilterFields,
                OnlyLinks = FilterOnlyLinks,
            };

            var instance = context.Naming.Instance(Instance, ResourceGroup);
            var id       = await mappings.AddAsync(Project, Event, filters, instance, Rule, ImpersonateExecution, cancellationToken);

            return(id.Equals(Guid.Empty) ? ExitCodes.Failure : ExitCodes.Success);
        }
コード例 #2
0
        public static IEnumerable <KeyValuePair <string, string> > ToInputs(this EventFilters filters)
        {
            if (!string.IsNullOrWhiteSpace(filters.AreaPath))
            {
                var areaPath = filters.AreaPath.First() == '\\' ? filters.AreaPath : $@"\{filters.AreaPath}";
                areaPath = filters.AreaPath.Last() == '\\' ? areaPath : $@"{areaPath}\";

                // Filter events to include only work items under the specified area path.
                yield return(new KeyValuePair <string, string>("areaPath", areaPath));
            }
            if (!string.IsNullOrWhiteSpace(filters.Type))
            {
                // Filter events to include only work items of the specified type.
                yield return(new KeyValuePair <string, string>("workItemType", filters.Type));
            }
            if (!string.IsNullOrWhiteSpace(filters.Tag))
            {
                // Filter events to include only work items containing the specified tag.
                yield return(new KeyValuePair <string, string>("tag", filters.Tag));
            }
            if (filters.Fields?.Any() ?? false)
            {
                // Filter events to include only work items with the specified field(s) changed
                yield return(new KeyValuePair <string, string>("changedFields", string.Join(',', filters.Fields)));
            }
        }
コード例 #3
0
        internal override async Task <int> RunAsync(CancellationToken cancellationToken)
        {
            var context = await Context
                          .WithAzureLogon()
                          .WithDevOpsLogon()
                          .BuildAsync(cancellationToken);

            var  mappings = new AggregatorMappings(context.Devops, context.Azure, context.Logger);
            bool ok       = DevOpsEvents.IsValidEvent(Event);

            if (!ok)
            {
                context.Logger.WriteError($"Invalid event type.");
                return(2);
            }

            var filters = new EventFilters
            {
                AreaPath = FilterAreaPath,
                Type     = FilterType,
                Tag      = FilterTag,
                Fields   = FilterFields
            };

            var instance = new InstanceName(Instance, ResourceGroup);
            var id       = await mappings.AddAsync(Project, Event, filters, instance, Rule, ImpersonateExecution, cancellationToken);

            return(id.Equals(Guid.Empty) ? 1 : 0);
        }
コード例 #4
0
        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));
        }
コード例 #5
0
 public static IEnumerable <InputFilterCondition> ToFilterConditions(this EventFilters filters)
 {
     return(filters.ToInputs()
            .Select(input => new InputFilterCondition
     {
         InputId = input.Key,
         InputValue = input.Value,
         Operator = InputFilterOperator.Equals,
         CaseSensitive = false
     }));
 }
コード例 #6
0
        internal async Task <Guid> AddFromUrlAsync(string projectName, string @event, EventFilters filters, Uri targetUrl, string ruleName, bool impersonateExecution, CancellationToken cancellationToken)
        {
            async Task <(Uri, string)> RetrieveHostedUrl(string _ruleName, CancellationToken _cancellationToken)
            {
                string apiKey = MagicConstants.InvalidApiKey;

                logger.WriteVerbose($"Validating target URL {targetUrl.AbsoluteUri}");

                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);

                var configUrl = new UriBuilder(targetUrl);

                configUrl.Path += $"config/key";
                var handler = new HttpClientHandler()
                {
                    SslProtocols = SslProtocols.Tls12,// | SslProtocols.Tls11 | SslProtocols.Tls,
                    ServerCertificateCustomValidationCallback = delegate { return(true); }
                };

                using (var client = new HttpClient(handler))
                    using (var request = new HttpRequestMessage(HttpMethod.Post, configUrl.Uri))
                    {
                        using (request.Content = new StringContent($"\"{proof}\"", Encoding.UTF8, "application/json"))
                            using (var response = await client.SendAsync(request, cancellationToken))
                            {
                                switch (response.StatusCode)
                                {
                                case HttpStatusCode.OK:
                                    logger.WriteVerbose($"Connection to {targetUrl} succeded");
                                    apiKey = await response.Content.ReadAsStringAsync();

                                    logger.WriteInfo($"Configuration retrieved.");
                                    break;

                                default:
                                    logger.WriteError($"{targetUrl} returned {response.ReasonPhrase}.");
                                    break;
                                }//switch
                            }
                    }

                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}";
                return(b.Uri, apiKey);
            }

            return(await CoreAddAsync(projectName, @event, filters, ruleName, impersonateExecution, RetrieveHostedUrl, MagicConstants.ApiKeyAuthenticationHeaderName, cancellationToken));
        }
コード例 #7
0
#pragma warning disable S107 // Methods should not have too many parameters
        protected async Task <Guid> CoreAddAsync(string projectName, string @event, EventFilters filters, string ruleName, bool impersonateExecution, Func <string, CancellationToken, Task <(Uri, string)> > urlRetriever, string headerName, CancellationToken cancellationToken)
コード例 #8
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);
        }