public async Task <IEnumerable <Subscription> > FilterSubscriptions(IRemote remote)
        {
            IEnumerable <DefaultChannel> defaultChannels = await remote.GetDefaultChannelsAsync();

            return((await remote.GetSubscriptionsAsync()).Where(subscription =>
            {
                return SubcriptionFilter(subscription, defaultChannels);
            }));
        }
        /// <summary>
        ///     Evaluate the metric.
        /// </summary>
        /// <param name="remoteFactory">Remote factory</param>
        /// <returns>True if the metric passed, false otherwise</returns>
        public override async Task EvaluateAsync()
        {
            IRemote remote = await RemoteFactory.GetRemoteAsync(Repository, Logger);

            Logger.LogInformation("Evaluating subscription health metrics for {repo}@{branch}", Repository, Branch);

            // Get subscriptions that target this repo/branch
            Subscriptions = (await remote.GetSubscriptionsAsync(targetRepo: Repository))
                            .Where(s => s.TargetBranch.Equals(Branch, StringComparison.OrdinalIgnoreCase)).ToList();

            // Get the dependencies of the repository/branch. Skip pinned and subscriptions tied to another
            // dependency (coherent parent), as well as those not selected by the dependency selector.
            try
            {
                Dependencies = (await remote.GetDependenciesAsync(Repository, Branch))
                               .Where(d => !d.Pinned && string.IsNullOrEmpty(d.CoherentParentDependencyName))
                               .Where(d => DependencySelector(d))
                               .ToList();
            }
            catch (DependencyFileNotFoundException)
            {
                // When the dependency file is not found, then we're good as long as this repo is not
                // targeted by any subscriptions
                if (Subscriptions.Any())
                {
                    MissingVersionDetailsFile = true;
                    Result = HealthResult.Failed;
                    return;
                }
                else
                {
                    Result = HealthResult.Passed;
                    return;
                }
            }

            Dictionary <string, Subscription> latestAssets = await GetLatestAssetsAndComputeConflicts(remote);

            ComputeSubscriptionUse(latestAssets);

            // Determine the result. A conflict or missing subscription is an error.
            // A non-flowing subscription or unused subscription is a warning
            if (DependenciesMissingSubscriptions.Any() ||
                ConflictingSubscriptions.Any())
            {
                Result = HealthResult.Failed;
            }
            else if (UnusedSubscriptions.Any() || DependenciesThatDoNotFlow.Any())
            {
                Result = HealthResult.Warning;
            }
            else
            {
                Result = HealthResult.Passed;
            }
        }
示例#3
0
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                IEnumerable <RepositoryBranch> allRepositories = await remote.GetRepositoriesAsync();

                IEnumerable <RepositoryBranch> filteredRepositories = allRepositories.Where(repositories =>
                                                                                            (string.IsNullOrEmpty(_options.Repo) || repositories.Repository.Contains(_options.Repo, StringComparison.OrdinalIgnoreCase)) &&
                                                                                            (string.IsNullOrEmpty(_options.Branch) || repositories.Branch.Contains(_options.Branch, StringComparison.OrdinalIgnoreCase)));

                // List only those repos and branches that are targeted by a batchable subscription (active) unless the user
                // passes --all.
                if (!_options.All)
                {
                    HashSet <string> batchableTargets = (await remote.GetSubscriptionsAsync())
                                                        .Where(s => s.Policy.Batchable)
                                                        .Select <Subscription, string>(s => $"{s.TargetRepository}{s.TargetBranch}")
                                                        .ToHashSet(StringComparer.OrdinalIgnoreCase);
                    var targetedRepositories = filteredRepositories.Where(r => batchableTargets.Contains($"{r.Repository}{r.Branch}"));

                    // If the number of repositories we're about to print is less than what we could have printed, then print a
                    // message.
                    int difference = filteredRepositories.Count() - targetedRepositories.Count();
                    if (difference != 0)
                    {
                        Console.WriteLine($"Filtered {difference} policies for branches not targeted by an active batchable subscription. To include, pass --all.{Environment.NewLine}");
                    }

                    filteredRepositories = targetedRepositories;
                }

                foreach (var repository in filteredRepositories)
                {
                    Console.WriteLine($"{repository.Repository} @ {repository.Branch}");
                    Console.Write(UxHelpers.GetMergePoliciesDescription(repository.MergePolicies));
                }

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve repositories");
                return(Constants.ErrorCode);
            }
        }
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                var subscriptions = (await remote.GetSubscriptionsAsync()).Where(subscription =>
                {
                    return(_options.SubcriptionFilter(subscription));
                });

                if (subscriptions.Count() == 0)
                {
                    Console.WriteLine("No subscriptions found matching the specified criteria.");
                    return(Constants.ErrorCode);
                }

                // Based on the current output schema, sort by source repo, target repo, target branch, etc.
                // Concat the input strings as a simple sorting mechanism.
                foreach (var subscription in subscriptions.OrderBy(subscription =>
                                                                   $"{subscription.SourceRepository}{subscription.Channel}{subscription.TargetRepository}{subscription.TargetBranch}"))
                {
                    Console.WriteLine($"{subscription.SourceRepository} ({subscription.Channel.Name}) ==> '{subscription.TargetRepository}' ('{subscription.TargetBranch}')");
                    Console.WriteLine($"  - Id: {subscription.Id}");
                    Console.WriteLine($"  - Update Frequency: {subscription.Policy.UpdateFrequency}");
                    Console.WriteLine($"  - Enabled: {subscription.Enabled}");
                    Console.WriteLine($"  - Batchable: {subscription.Policy.Batchable}");
                    // If batchable, the merge policies come from the repository
                    IEnumerable <MergePolicy> mergePolicies = subscription.Policy.MergePolicies;
                    if (subscription.Policy.Batchable == true)
                    {
                        mergePolicies = await remote.GetRepositoryMergePoliciesAsync(subscription.TargetRepository, subscription.TargetBranch);
                    }

                    Console.Write(UxHelpers.GetMergePoliciesDescription(mergePolicies, "  "));

                    // Currently the API only returns the last applied build for requests to specific subscriptions.
                    // This will be fixed, but for now, don't print the last applied build otherwise.
                    if (subscription.LastAppliedBuild != null)
                    {
                        Console.WriteLine($"  - Last Build: {subscription.LastAppliedBuild.AzureDevOpsBuildNumber} ({subscription.LastAppliedBuild.Commit})");
                    }
                }
                return(Constants.SuccessCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve subscriptions");
                return(Constants.ErrorCode);
            }
        }
示例#5
0
        /// <summary>
        ///     Assigns a build to a channel.
        /// </summary>
        /// <returns>Process exit code.</returns>
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                // Find the build to give someone info
                Build build = await remote.GetBuildAsync(_options.Id);

                if (build == null)
                {
                    Console.WriteLine($"Could not find a build with id '{_options.Id}'");
                    return(Constants.ErrorCode);
                }

                Channel targetChannel = await UxHelpers.ResolveSingleChannel(remote, _options.Channel);

                if (targetChannel == null)
                {
                    return(Constants.ErrorCode);
                }

                if (build.Channels.Any(c => c.Id == targetChannel.Id))
                {
                    Console.WriteLine($"Build '{build.Id}' has already been assigned to '{targetChannel.Name}'");
                    return(Constants.SuccessCode);
                }

                Console.WriteLine($"Assigning the following build to channel '{targetChannel.Name}':");
                Console.WriteLine();
                OutputHelpers.PrintBuild(build);

                await remote.AssignBuildToChannel(_options.Id, targetChannel.Id);

                // Be helpful. Let the user know what will happen.
                string buildRepo = build.GitHubRepository ?? build.AzureDevOpsRepository;
                List <Subscription> applicableSubscriptions = (await remote.GetSubscriptionsAsync(
                                                                   sourceRepo: buildRepo, channelId: targetChannel.Id)).ToList();

                PrintSubscriptionInfo(applicableSubscriptions);

                return(Constants.SuccessCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Error: Failed to assign build '{_options.Id}' to channel '{_options.Channel}'.");
                return(Constants.ErrorCode);
            }
        }
        public async Task UpdateLongestBuildPathAsync(CancellationToken cancellationToken)
        {
            using (Logger.BeginScope($"Updating Longest Build Path table"))
            {
                List <Channel> channels = Context.Channels.Select(c => new Channel()
                {
                    Id = c.Id, Name = c.Name
                }).ToList();

                // Get the flow graph
                IRemote barOnlyRemote = await RemoteFactory.GetBarOnlyRemoteAsync(Logger);

                List <Microsoft.DotNet.Maestro.Client.Models.DefaultChannel> defaultChannels = (await barOnlyRemote.GetDefaultChannelsAsync()).ToList();
                List <Microsoft.DotNet.Maestro.Client.Models.Subscription>   subscriptions   = (await barOnlyRemote.GetSubscriptionsAsync()).ToList();

                IEnumerable <string> frequencies = new[] { "everyWeek", "twiceDaily", "everyDay", "everyBuild", "none", };

                Logger.LogInformation($"Will update '{channels.Count}' channels");

                foreach (var channel in channels)
                {
                    // Build, then prune out what we don't want to see if the user specified channels.
                    DependencyFlowGraph flowGraph = await DependencyFlowGraph.BuildAsync(defaultChannels, subscriptions, barOnlyRemote, 30);

                    flowGraph.PruneGraph(
                        node => DependencyFlowGraph.IsInterestingNode(channel.Name, node),
                        edge => DependencyFlowGraph.IsInterestingEdge(edge, false, frequencies));

                    if (flowGraph.Nodes.Count > 0)
                    {
                        flowGraph.MarkBackEdges();
                        flowGraph.CalculateLongestBuildPaths();
                        flowGraph.MarkLongestBuildPath();

                        // Get the nodes on the longest path and order them by path time so that the
                        // contributing repos are in the right order
                        List <DependencyFlowNode> longestBuildPathNodes = flowGraph.Nodes
                                                                          .Where(n => n.OnLongestBuildPath)
                                                                          .OrderByDescending(n => n.BestCasePathTime)
                                                                          .ToList();

                        LongestBuildPath lbp = new LongestBuildPath()
                        {
                            ChannelId                = channel.Id,
                            BestCaseTimeInMinutes    = longestBuildPathNodes.Max(n => n.BestCasePathTime),
                            WorstCaseTimeInMinutes   = longestBuildPathNodes.Max(n => n.WorstCasePathTime),
                            ContributingRepositories = String.Join(';', longestBuildPathNodes.Select(n => $"{n.Repository}@{n.Branch}").ToArray()),
                            ReportDate               = DateTimeOffset.UtcNow,
                        };

                        Logger.LogInformation($"Will update {channel.Name} to best case time {lbp.BestCaseTimeInMinutes} and worst case time {lbp.WorstCaseTimeInMinutes}");
                        await Context.LongestBuildPaths.AddAsync(lbp);
                    }
                }

                await Context.SaveChangesAsync();
            }
        }
        /// <summary>
        /// Implements the 'add-subscription' operation
        /// </summary>
        /// <param name="options"></param>
        public override async Task <int> ExecuteAsync()
        {
            IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

            if (_options.IgnoreChecks.Count() > 0 && !_options.AllChecksSuccessfulMergePolicy)
            {
                Console.WriteLine($"--ignore-checks must be combined with --all-checks-passed");
                return(Constants.ErrorCode);
            }

            // Parse the merge policies
            List <MergePolicy> mergePolicies = new List <MergePolicy>();

            if (_options.NoExtraCommitsMergePolicy)
            {
                mergePolicies.Add(
                    new MergePolicy
                {
                    Name = MergePolicyConstants.NoExtraCommitsMergePolicyName
                });
            }

            if (_options.AllChecksSuccessfulMergePolicy)
            {
                mergePolicies.Add(
                    new MergePolicy
                {
                    Name       = MergePolicyConstants.AllCheckSuccessfulMergePolicyName,
                    Properties = ImmutableDictionary.Create <string, JToken>()
                                 .Add(MergePolicyConstants.IgnoreChecksMergePolicyPropertyName, JToken.FromObject(_options.IgnoreChecks))
                });
            }

            if (_options.NoRequestedChangesMergePolicy)
            {
                mergePolicies.Add(
                    new MergePolicy
                {
                    Name       = MergePolicyConstants.NoRequestedChangesMergePolicyName,
                    Properties = ImmutableDictionary.Create <string, JToken>()
                });
            }

            if (_options.StandardAutoMergePolicies)
            {
                mergePolicies.Add(
                    new MergePolicy
                {
                    Name       = MergePolicyConstants.StandardMergePolicyName,
                    Properties = ImmutableDictionary.Create <string, JToken>()
                });
            }

            if (_options.Batchable && mergePolicies.Count > 0)
            {
                Console.WriteLine("Batchable subscriptions cannot be combined with merge policies. " +
                                  "Merge policies are specified at a repository+branch level.");
                return(Constants.ErrorCode);
            }

            string channel          = _options.Channel;
            string sourceRepository = _options.SourceRepository;
            string targetRepository = _options.TargetRepository;
            string targetBranch     = GitHelpers.NormalizeBranchName(_options.TargetBranch);
            string updateFrequency  = _options.UpdateFrequency;
            bool   batchable        = _options.Batchable;

            // If in quiet (non-interactive mode), ensure that all options were passed, then
            // just call the remote API
            if (_options.Quiet && !_options.ReadStandardIn)
            {
                if (string.IsNullOrEmpty(channel) ||
                    string.IsNullOrEmpty(sourceRepository) ||
                    string.IsNullOrEmpty(targetRepository) ||
                    string.IsNullOrEmpty(targetBranch) ||
                    string.IsNullOrEmpty(updateFrequency) ||
                    !Constants.AvailableFrequencies.Contains(updateFrequency, StringComparer.OrdinalIgnoreCase))
                {
                    Logger.LogError($"Missing input parameters for the subscription. Please see command help or remove --quiet/-q for interactive mode");
                    return(Constants.ErrorCode);
                }
            }
            else
            {
                // Grab existing subscriptions to get suggested values.
                // TODO: When this becomes paged, set a max number of results to avoid
                // pulling too much.
                var suggestedRepos    = remote.GetSubscriptionsAsync();
                var suggestedChannels = remote.GetChannelsAsync();

                // Help the user along with a form.  We'll use the API to gather suggested values
                // from existing subscriptions based on the input parameters.
                AddSubscriptionPopUp addSubscriptionPopup =
                    new AddSubscriptionPopUp("add-subscription/add-subscription-todo",
                                             Logger,
                                             channel,
                                             sourceRepository,
                                             targetRepository,
                                             targetBranch,
                                             updateFrequency,
                                             batchable,
                                             mergePolicies,
                                             (await suggestedChannels).Select(suggestedChannel => suggestedChannel.Name),
                                             (await suggestedRepos).SelectMany(subscription => new List <string> {
                    subscription.SourceRepository, subscription.TargetRepository
                }).ToHashSet(),
                                             Constants.AvailableFrequencies,
                                             Constants.AvailableMergePolicyYamlHelp);

                UxManager uxManager = new UxManager(_options.GitLocation, Logger);
                int       exitCode  = _options.ReadStandardIn ? uxManager.ReadFromStdIn(addSubscriptionPopup) : uxManager.PopUp(addSubscriptionPopup);
                if (exitCode != Constants.SuccessCode)
                {
                    return(exitCode);
                }
                channel          = addSubscriptionPopup.Channel;
                sourceRepository = addSubscriptionPopup.SourceRepository;
                targetRepository = addSubscriptionPopup.TargetRepository;
                targetBranch     = addSubscriptionPopup.TargetBranch;
                updateFrequency  = addSubscriptionPopup.UpdateFrequency;
                mergePolicies    = addSubscriptionPopup.MergePolicies;
                batchable        = addSubscriptionPopup.Batchable;
            }

            try
            {
                // If we are about to add a batchable subscription and the merge policies are empty for the
                // target repo/branch, warn the user.
                if (batchable)
                {
                    var existingMergePolicies = await remote.GetRepositoryMergePoliciesAsync(targetRepository, targetBranch);

                    if (!existingMergePolicies.Any())
                    {
                        Console.WriteLine("Warning: Batchable subscription doesn't have any repository merge policies. " +
                                          "PRs will not be auto-merged.");
                        Console.WriteLine($"Please use 'darc set-repository-policies --repo {targetRepository} --branch {targetBranch}' " +
                                          $"to set policies.{Environment.NewLine}");
                    }
                }

                // Verify the target
                IRemote targetVerifyRemote = RemoteFactory.GetRemote(_options, targetRepository, Logger);
                if (!(await UxHelpers.VerifyAndConfirmBranchExistsAsync(targetVerifyRemote, targetRepository, targetBranch, !_options.Quiet)))
                {
                    Console.WriteLine("Aborting subscription creation.");
                    return(Constants.ErrorCode);
                }

                // Verify the source.
                IRemote sourceVerifyRemote = RemoteFactory.GetRemote(_options, sourceRepository, Logger);
                if (!(await UxHelpers.VerifyAndConfirmRepositoryExistsAsync(sourceVerifyRemote, sourceRepository, !_options.Quiet)))
                {
                    Console.WriteLine("Aborting subscription creation.");
                    return(Constants.ErrorCode);
                }

                var newSubscription = await remote.CreateSubscriptionAsync(channel,
                                                                           sourceRepository,
                                                                           targetRepository,
                                                                           targetBranch,
                                                                           updateFrequency,
                                                                           batchable,
                                                                           mergePolicies);

                Console.WriteLine($"Successfully created new subscription with id '{newSubscription.Id}'.");

                // Prompt the user to trigger the subscription unless they have explicitly disallowed it
                if (!_options.NoTriggerOnCreate)
                {
                    bool triggerAutomatically = _options.TriggerOnCreate || UxHelpers.PromptForYesNo("Trigger this subscription immediately?");
                    if (triggerAutomatically)
                    {
                        await remote.TriggerSubscriptionAsync(newSubscription.Id.ToString());

                        Console.WriteLine($"Subscription '{newSubscription.Id}' triggered.");
                    }
                }

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (RestApiException e) when(e.Response.Status == (int)System.Net.HttpStatusCode.BadRequest)
            {
                // Could have been some kind of validation error (e.g. channel doesn't exist)
                Logger.LogError($"Failed to create subscription: {e.Response.Content}");
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Failed to create subscription.");
                return(Constants.ErrorCode);
            }
        }
示例#8
0
        /// <summary>
        /// Implements the 'update-subscription' operation
        /// </summary>
        /// <param name="options"></param>
        public override async Task <int> ExecuteAsync()
        {
            IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

            // First, try to get the subscription. If it doesn't exist the call will throw and the exception will be
            // caught by `RunOperation`
            Subscription subscription = await remote.GetSubscriptionAsync(_options.Id);

            var suggestedRepos    = remote.GetSubscriptionsAsync();
            var suggestedChannels = remote.GetChannelsAsync();

            UpdateSubscriptionPopUp updateSubscriptionPopUp = new UpdateSubscriptionPopUp(
                "update-subscription/update-subscription-todo",
                Logger,
                subscription,
                (await suggestedChannels).Select(suggestedChannel => suggestedChannel.Name),
                (await suggestedRepos).SelectMany(subs => new List <string> {
                subscription.SourceRepository, subscription.TargetRepository
            }).ToHashSet(),
                Constants.AvailableFrequencies,
                Constants.AvailableMergePolicyYamlHelp);

            UxManager uxManager = new UxManager(Logger);

            int exitCode = uxManager.PopUp(updateSubscriptionPopUp);

            if (exitCode != Constants.SuccessCode)
            {
                return(exitCode);
            }

            string             channel          = updateSubscriptionPopUp.Channel;
            string             sourceRepository = updateSubscriptionPopUp.SourceRepository;
            string             updateFrequency  = updateSubscriptionPopUp.UpdateFrequency;
            bool               batchable        = updateSubscriptionPopUp.Batchable;
            bool               enabled          = updateSubscriptionPopUp.Enabled;
            List <MergePolicy> mergePolicies    = updateSubscriptionPopUp.MergePolicies;

            try
            {
                SubscriptionUpdate subscriptionToUpdate = new SubscriptionUpdate
                {
                    ChannelName      = channel ?? subscription.Channel.Name,
                    SourceRepository = sourceRepository ?? subscription.SourceRepository,
                    Enabled          = enabled,
                    Policy           = subscription.Policy,
                };
                subscriptionToUpdate.Policy.Batchable       = batchable;
                subscriptionToUpdate.Policy.UpdateFrequency = Enum.Parse <UpdateFrequency>(updateFrequency);
                subscriptionToUpdate.Policy.MergePolicies   = mergePolicies?.ToImmutableList();

                var updatedSubscription = await remote.UpdateSubscriptionAsync(
                    _options.Id,
                    subscriptionToUpdate);

                Console.WriteLine($"Successfully updated subscription with id '{updatedSubscription.Id}'.");

                return(Constants.SuccessCode);
            }
            catch (RestApiException e) when(e.Response.StatusCode == System.Net.HttpStatusCode.BadRequest)
            {
                // Could have been some kind of validation error (e.g. channel doesn't exist)
                Logger.LogError($"Failed to update subscription: {e.Response.Content}");
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Failed to update subscription.");
                return(Constants.ErrorCode);
            }
        }
        /// <summary>
        /// Triggers subscriptions
        /// </summary>
        /// <returns></returns>
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                bool noConfirm = _options.NoConfirmation;
                List <Subscription> subscriptionsToTrigger = new List <Subscription>();

                if (!string.IsNullOrEmpty(_options.Id))
                {
                    // Look up subscription so we can print it later.
                    try
                    {
                        Subscription subscription = await remote.GetSubscriptionAsync(_options.Id);

                        subscriptionsToTrigger.Add(subscription);
                    }
                    catch (RestApiException e) when(e.Response.StatusCode == HttpStatusCode.NotFound)
                    {
                        Console.WriteLine($"Subscription with id '{_options.Id}' was not found.");
                        return(Constants.ErrorCode);
                    }
                }
                else
                {
                    if (string.IsNullOrEmpty(_options.TargetRepository) &&
                        string.IsNullOrEmpty(_options.TargetBranch) &&
                        string.IsNullOrEmpty(_options.SourceRepository) &&
                        string.IsNullOrEmpty(_options.Channel))
                    {
                        Console.WriteLine($"Please specify one or more filters to select which subscriptions should be triggered (see help).");
                        return(Constants.ErrorCode);
                    }

                    IEnumerable <Subscription> subscriptions = (await remote.GetSubscriptionsAsync()).Where(subscription =>
                    {
                        return(_options.SubcriptionFilter(subscription));
                    });

                    if (subscriptions.Count() == 0)
                    {
                        Console.WriteLine("No subscriptions found matching the specified criteria.");
                        return(Constants.ErrorCode);
                    }

                    subscriptionsToTrigger.AddRange(subscriptions);
                }

                if (!noConfirm)
                {
                    // Print out the list of subscriptions about to be triggered.
                    Console.WriteLine($"Will trigger the following {subscriptionsToTrigger.Count} subscriptions...");
                    foreach (var subscription in subscriptionsToTrigger)
                    {
                        Console.WriteLine($"  {UxHelpers.GetSubscriptionDescription(subscription)}");
                    }

                    char keyChar;
                    do
                    {
                        Console.Write("Continue? (y/n) ");
                        ConsoleKeyInfo keyInfo = Console.ReadKey();
                        keyChar = char.ToUpperInvariant(keyInfo.KeyChar);
                        Console.WriteLine();
                    }while (keyChar != 'Y' && keyChar != 'N');

                    if (keyChar == 'N')
                    {
                        Console.WriteLine($"No subscriptions triggered, exiting.");
                        return(Constants.ErrorCode);
                    }
                }

                Console.Write($"Triggering {subscriptionsToTrigger.Count} subscriptions...{(noConfirm ? Environment.NewLine : "")}");
                foreach (var subscription in subscriptionsToTrigger)
                {
                    // If noConfirm was passed, print out the subscriptions as we go
                    if (noConfirm)
                    {
                        Console.WriteLine($"  {UxHelpers.GetSubscriptionDescription(subscription)}");
                    }
                    await remote.TriggerSubscriptionAsync(subscription.Id.ToString());
                }
                Console.WriteLine($"done");

                return(Constants.SuccessCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Unexpected error while triggering subscriptions.");
                return(Constants.ErrorCode);
            }
        }
        public override async Task <int> ExecuteAsync()
        {
            IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

            IEnumerable <Subscription> subscriptions = await remote.GetSubscriptionsAsync();

            IEnumerable <DefaultChannel> defaultChannels = await remote.GetDefaultChannelsAsync();

            IEnumerable <Channel> channels = await remote.GetChannelsAsync();

            HashSet <string> channelsToEvaluate = ComputeChannelsToEvaluate(channels);
            HashSet <string> reposToEvaluate    = ComputeRepositoriesToEvaluate(defaultChannels, subscriptions);

            // Print out what will be evaluated. If no channels or repos are in the initial sets, then
            // this is currently an error. Because different PKPIs apply to different input items differently,
            // this check may not be useful in the future.

            if (channelsToEvaluate.Any())
            {
                Console.WriteLine("Evaluating the following channels:");
                foreach (string channel in channelsToEvaluate)
                {
                    Console.WriteLine($"  {channel}");
                }
            }
            else
            {
                Console.WriteLine($"There were no channels found to evaluate based on inputs, exiting.");
                return(Constants.ErrorCode);
            }

            if (reposToEvaluate.Any())
            {
                Console.WriteLine("Evaluating the following repositories:");
                foreach (string repo in reposToEvaluate)
                {
                    Console.WriteLine($"  {repo}");
                }
            }
            else
            {
                Console.WriteLine($"There were no repositories found to evaluate based on inputs, exiting.");
                return(Constants.ErrorCode);
            }

            Console.WriteLine();

            // Compute metrics, then run in parallel.

            List <Func <Task <HealthMetricWithOutput> > > metricsToRun = ComputeMetricsToRun(channelsToEvaluate, reposToEvaluate,
                                                                                             subscriptions, defaultChannels, channels);

            // Run the metrics
            HealthMetricWithOutput[] results = await Task.WhenAll <HealthMetricWithOutput>(metricsToRun.Select(metric => metric()));

            // Walk through and print the results out
            bool passed = true;

            foreach (var healthResult in results)
            {
                if (healthResult.Metric.Result != HealthResult.Passed)
                {
                    passed = false;
                }

                Console.WriteLine($"{healthResult.Metric.MetricDescription} - ({healthResult.Metric.Result})");
                if (healthResult.Metric.Result != HealthResult.Passed)
                {
                    Console.WriteLine();
                    Console.WriteLine(healthResult.FormattedConsoleOutput);
                }
            }

            return(passed ? Constants.SuccessCode : Constants.ErrorCode);
        }
        /// <summary>
        ///     Gets the latest build for a repo
        /// </summary>
        /// <returns>Process exit code.</returns>
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                // Calculate out possible repos based on the input strings.
                // Today the DB has no way of searching for builds by substring, so for now
                // grab source/targets repos of subscriptions matched on substring,
                // and then add the explicit repo from the options.
                // Then search channels by substring
                // Then run GetLatestBuild for each permutation.

                var subscriptions = await remote.GetSubscriptionsAsync();

                var possibleRepos = subscriptions
                                    .SelectMany(subscription => new List <string> {
                    subscription.SourceRepository, subscription.TargetRepository
                })
                                    .Where(r => r.Contains(_options.Repo, StringComparison.OrdinalIgnoreCase))
                                    .ToHashSet(StringComparer.OrdinalIgnoreCase);
                possibleRepos.Add(_options.Repo);

                var channels = (await remote.GetChannelsAsync())
                               .Where(c => string.IsNullOrEmpty(_options.Channel) || c.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase));

                if (!channels.Any())
                {
                    Console.WriteLine($"Could not find a channel with name containing '{_options.Channel}'");
                    return(Constants.ErrorCode);
                }

                bool foundBuilds = false;
                foreach (string possibleRepo in possibleRepos)
                {
                    foreach (Channel channel in channels)
                    {
                        Build latestBuild = await remote.GetLatestBuildAsync(possibleRepo, channel.Id);

                        if (latestBuild != null)
                        {
                            if (foundBuilds)
                            {
                                Console.WriteLine();
                            }
                            foundBuilds = true;
                            Console.Write(UxHelpers.GetTextBuildDescription(latestBuild));
                        }
                    }
                }

                if (!foundBuilds)
                {
                    Console.WriteLine("No latest build found matching the specified criteria");
                    return(Constants.ErrorCode);
                }

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve latest build.");
                return(Constants.ErrorCode);
            }
        }
示例#12
0
        /// <summary>
        /// Implements the 'update-subscription' operation
        /// </summary>
        /// <param name="options"></param>
        public override async Task <int> ExecuteAsync()
        {
            IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

            // First, try to get the subscription. If it doesn't exist the call will throw and the exception will be
            // caught by `RunOperation`
            Subscription subscription = await remote.GetSubscriptionAsync(_options.Id);

            var suggestedRepos    = remote.GetSubscriptionsAsync();
            var suggestedChannels = remote.GetChannelsAsync();

            UpdateSubscriptionPopUp updateSubscriptionPopUp = new UpdateSubscriptionPopUp(
                "update-subscription/update-subscription-todo",
                Logger,
                subscription,
                (await suggestedChannels).Select(suggestedChannel => suggestedChannel.Name),
                (await suggestedRepos).SelectMany(subs => new List <string> {
                subscription.SourceRepository, subscription.TargetRepository
            }).ToHashSet(),
                Constants.AvailableFrequencies,
                Constants.AvailableMergePolicyYamlHelp);

            UxManager uxManager = new UxManager(_options.GitLocation, Logger);

            int exitCode = uxManager.PopUp(updateSubscriptionPopUp);

            if (exitCode != Constants.SuccessCode)
            {
                return(exitCode);
            }

            string             channel          = updateSubscriptionPopUp.Channel;
            string             sourceRepository = updateSubscriptionPopUp.SourceRepository;
            string             updateFrequency  = updateSubscriptionPopUp.UpdateFrequency;
            bool               batchable        = updateSubscriptionPopUp.Batchable;
            bool               enabled          = updateSubscriptionPopUp.Enabled;
            List <MergePolicy> mergePolicies    = updateSubscriptionPopUp.MergePolicies;

            try
            {
                SubscriptionUpdate subscriptionToUpdate = new SubscriptionUpdate
                {
                    ChannelName      = channel ?? subscription.Channel.Name,
                    SourceRepository = sourceRepository ?? subscription.SourceRepository,
                    Enabled          = enabled,
                    Policy           = subscription.Policy,
                };
                subscriptionToUpdate.Policy.Batchable       = batchable;
                subscriptionToUpdate.Policy.UpdateFrequency = Enum.Parse <UpdateFrequency>(updateFrequency);
                subscriptionToUpdate.Policy.MergePolicies   = mergePolicies?.ToImmutableList();

                var updatedSubscription = await remote.UpdateSubscriptionAsync(
                    _options.Id,
                    subscriptionToUpdate);

                Console.WriteLine($"Successfully updated subscription with id '{updatedSubscription.Id}'.");

                // Determine whether the subscription should be triggered.
                if (!_options.NoTriggerOnUpdate)
                {
                    bool triggerAutomatically = _options.TriggerOnUpdate;
                    // Determine whether we should prompt if the user hasn't explicitly
                    // said one way or another. We shouldn't prompt if nothing changes or
                    // if non-interesting options have changed
                    if (!triggerAutomatically &&
                        ((subscriptionToUpdate.ChannelName != subscription.Channel.Name) ||
                         (subscriptionToUpdate.SourceRepository != subscription.SourceRepository) ||
                         (subscriptionToUpdate.Enabled.Value && !subscription.Enabled) ||
                         (subscriptionToUpdate.Policy.UpdateFrequency != UpdateFrequency.None && subscriptionToUpdate.Policy.UpdateFrequency !=
                          subscription.Policy.UpdateFrequency)))
                    {
                        triggerAutomatically = UxHelpers.PromptForYesNo("Trigger this subscription immediately?");
                    }

                    if (triggerAutomatically)
                    {
                        await remote.TriggerSubscriptionAsync(updatedSubscription.Id.ToString());

                        Console.WriteLine($"Subscription '{updatedSubscription.Id}' triggered.");
                    }
                }

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (RestApiException e) when(e.Response.Status == (int)System.Net.HttpStatusCode.BadRequest)
            {
                // Could have been some kind of validation error (e.g. channel doesn't exist)
                Logger.LogError($"Failed to update subscription: {e.Response.Content}");
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Failed to update subscription.");
                return(Constants.ErrorCode);
            }
        }
示例#13
0
        /// <summary>
        ///     Get a specific build of a repository
        /// </summary>
        /// <returns>Process exit code.</returns>
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                List <Build> matchingBuilds = null;
                if (_options.Id != 0)
                {
                    if (!string.IsNullOrEmpty(_options.Repo) ||
                        !string.IsNullOrEmpty(_options.Commit))
                    {
                        Console.WriteLine("--id should not be used with other options.");
                        return(Constants.ErrorCode);
                    }

                    matchingBuilds = new List <Build>()
                    {
                        await remote.GetBuildAsync(_options.Id)
                    };
                }
                else if (!string.IsNullOrEmpty(_options.Repo) || !string.IsNullOrEmpty(_options.Commit))
                {
                    if (string.IsNullOrEmpty(_options.Repo) != string.IsNullOrEmpty(_options.Commit))
                    {
                        Console.WriteLine("--repo and --commit should be used together.");

                        return(Constants.ErrorCode);
                    }
                    var subscriptions = await remote.GetSubscriptionsAsync();

                    var possibleRepos = subscriptions
                                        .SelectMany(subscription => new List <string> {
                        subscription.SourceRepository, subscription.TargetRepository
                    })
                                        .Where(r => r.Contains(_options.Repo, StringComparison.OrdinalIgnoreCase))
                                        .ToHashSet(StringComparer.OrdinalIgnoreCase);

                    matchingBuilds = new List <Build>();
                    foreach (string repo in possibleRepos)
                    {
                        matchingBuilds.AddRange(await remote.GetBuildsAsync(repo, _options.Commit));
                    }
                }
                else
                {
                    Console.WriteLine("Please specify --id, --uri, or --repo and --commit to lookup a build.");
                    return(Constants.ErrorCode);
                }

                // Print the build info.
                if (!matchingBuilds.Any())
                {
                    Console.WriteLine($"Could not any builds matching the given criteria.");
                    return(Constants.ErrorCode);
                }

                switch (_options.OutputFormat)
                {
                case DarcOutputType.text:
                    foreach (Build build in matchingBuilds)
                    {
                        Console.Write(UxHelpers.GetTextBuildDescription(build));
                    }
                    break;

                case DarcOutputType.json:
                    Console.WriteLine(JsonConvert.SerializeObject(
                                          matchingBuilds.Select(build => UxHelpers.GetJsonBuildDescription(build)), Formatting.Indented));
                    break;

                default:
                    throw new NotImplementedException($"Output format type {_options.OutputFormat} not yet supported for get-build.");
                }

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve build information.");
                return(Constants.ErrorCode);
            }
        }
示例#14
0
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                var subscriptions = (await remote.GetSubscriptionsAsync()).Where(subscription =>
                {
                    return(_options.SubcriptionFilter(subscription));
                });

                if (subscriptions.Count() == 0)
                {
                    Console.WriteLine("No subscriptions found matching the specified criteria.");
                    return(Constants.ErrorCode);
                }

                // Based on the current output schema, sort by source repo, target repo, target branch, etc.
                // Concat the input strings as a simple sorting mechanism.
                foreach (var subscription in subscriptions.OrderBy(subscription =>
                                                                   $"{subscription.SourceRepository}{subscription.Channel}{subscription.TargetRepository}{subscription.TargetBranch}"))
                {
                    Console.WriteLine($"{subscription.SourceRepository} ({subscription.Channel.Name}) ==> '{subscription.TargetRepository}' ('{subscription.TargetBranch}')");
                    Console.WriteLine($"  - Id: {subscription.Id}");
                    Console.WriteLine($"  - Update Frequency: {subscription.Policy.UpdateFrequency}");
                    Console.WriteLine($"  - Enabled: {subscription.Enabled}");
                    Console.WriteLine($"  - Batchable: {subscription.Policy.Batchable}");
                    // If batchable, the merge policies come from the repository
                    IEnumerable <MergePolicy> mergePolicies = subscription.Policy.MergePolicies;
                    if (subscription.Policy.Batchable == true)
                    {
                        mergePolicies = await remote.GetRepositoryMergePoliciesAsync(subscription.TargetRepository, subscription.TargetBranch);
                    }
                    if (mergePolicies.Any())
                    {
                        Console.WriteLine($"  - Merge Policies:");
                        foreach (MergePolicy mergePolicy in mergePolicies)
                        {
                            Console.WriteLine($"    {mergePolicy.Name}");
                            if (mergePolicy.Properties != null)
                            {
                                foreach (var mergePolicyProperty in mergePolicy.Properties)
                                {
                                    // The merge policy property is a key value pair.  For formatting, turn it into a string.
                                    // It's often a JToken, so handle appropriately
                                    // 1. If the number of lines in the string is 1, write on same line as key
                                    // 2. If the number of lines in the string is more than one, start on new
                                    //    line and indent.
                                    string   valueString = mergePolicyProperty.Value.ToString();
                                    string[] valueLines  = valueString.Split(System.Environment.NewLine);
                                    string   keyString   = $"      {mergePolicyProperty.Key} = ";
                                    Console.Write(keyString);
                                    if (valueLines.Length == 1)
                                    {
                                        Console.WriteLine(valueString);
                                    }
                                    else
                                    {
                                        string indentString = new string(' ', keyString.Length);
                                        Console.WriteLine();
                                        foreach (string line in valueLines)
                                        {
                                            Console.WriteLine($"{indentString}{line}");
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        Console.WriteLine($"  - Merge Policies: []");
                    }
                    // Currently the API only returns the last applied build for requests to specific subscriptions.
                    // This will be fixed, but for now, don't print the last applied build otherwise.
                    if (subscription.LastAppliedBuild != null)
                    {
                        Console.WriteLine($"  - Last Build: {subscription.LastAppliedBuild.AzureDevOpsBuildNumber} ({subscription.LastAppliedBuild.Commit})");
                    }
                }
                return(Constants.SuccessCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve subscriptions");
                return(Constants.ErrorCode);
            }
        }
        /// <summary>
        /// Implements the 'add-subscription' operation
        /// </summary>
        /// <param name="options"></param>
        public override async Task <int> ExecuteAsync()
        {
            IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

            if (_options.IgnoreChecks.Count() > 0 && !_options.AllChecksSuccessfulMergePolicy)
            {
                Console.WriteLine($"--ignore-checks must be combined with --all-checks-passed");
                return(Constants.ErrorCode);
            }

            // Parse the merge policies
            List <MergePolicy> mergePolicies = new List <MergePolicy>();

            if (_options.NoExtraCommitsMergePolicy)
            {
                mergePolicies.Add(
                    new MergePolicy
                {
                    Name = "NoExtraCommits"
                });
            }

            if (_options.AllChecksSuccessfulMergePolicy)
            {
                mergePolicies.Add(
                    new MergePolicy
                {
                    Name       = "AllChecksSuccessful",
                    Properties = ImmutableDictionary.Create <string, JToken>()
                                 .Add("ignoreChecks", JToken.FromObject(_options.IgnoreChecks))
                });
            }

            if (_options.NoRequestedChangesMergePolicy)
            {
                mergePolicies.Add(
                    new MergePolicy
                {
                    Name       = "NoRequestedChanges",
                    Properties = ImmutableDictionary.Create <string, JToken>()
                });
            }

            if (_options.StandardAutoMergePolicies)
            {
                mergePolicies.Add(
                    new MergePolicy
                {
                    Name       = "Standard",
                    Properties = ImmutableDictionary.Create <string, JToken>()
                });
            }

            if (_options.Batchable && mergePolicies.Count > 0)
            {
                Console.WriteLine("Batchable subscriptions cannot be combined with merge policies. " +
                                  "Merge policies are specified at a repository+branch level.");
                return(Constants.ErrorCode);
            }

            string channel          = _options.Channel;
            string sourceRepository = _options.SourceRepository;
            string targetRepository = _options.TargetRepository;
            string targetBranch     = _options.TargetBranch;
            string updateFrequency  = _options.UpdateFrequency;
            bool   batchable        = _options.Batchable;

            // If in quiet (non-interactive mode), ensure that all options were passed, then
            // just call the remote API
            if (_options.Quiet)
            {
                if (string.IsNullOrEmpty(channel) ||
                    string.IsNullOrEmpty(sourceRepository) ||
                    string.IsNullOrEmpty(targetRepository) ||
                    string.IsNullOrEmpty(targetBranch) ||
                    string.IsNullOrEmpty(updateFrequency) ||
                    !Constants.AvailableFrequencies.Contains(updateFrequency, StringComparer.OrdinalIgnoreCase))
                {
                    Logger.LogError($"Missing input parameters for the subscription. Please see command help or remove --quiet/-q for interactive mode");
                    return(Constants.ErrorCode);
                }
            }
            else
            {
                // Grab existing subscriptions to get suggested values.
                // TODO: When this becomes paged, set a max number of results to avoid
                // pulling too much.
                var suggestedRepos    = remote.GetSubscriptionsAsync();
                var suggestedChannels = remote.GetChannelsAsync();

                // Help the user along with a form.  We'll use the API to gather suggested values
                // from existing subscriptions based on the input parameters.
                AddSubscriptionPopUp initEditorPopUp =
                    new AddSubscriptionPopUp("add-subscription/add-subscription-todo",
                                             Logger,
                                             channel,
                                             sourceRepository,
                                             targetRepository,
                                             targetBranch,
                                             updateFrequency,
                                             batchable,
                                             mergePolicies,
                                             (await suggestedChannels).Select(suggestedChannel => suggestedChannel.Name),
                                             (await suggestedRepos).SelectMany(subscription => new List <string> {
                    subscription.SourceRepository, subscription.TargetRepository
                }).ToHashSet(),
                                             Constants.AvailableFrequencies,
                                             Constants.AvailableMergePolicyYamlHelp);

                UxManager uxManager = new UxManager(Logger);
                int       exitCode  = uxManager.PopUp(initEditorPopUp);
                if (exitCode != Constants.SuccessCode)
                {
                    return(exitCode);
                }
                channel          = initEditorPopUp.Channel;
                sourceRepository = initEditorPopUp.SourceRepository;
                targetRepository = initEditorPopUp.TargetRepository;
                targetBranch     = initEditorPopUp.TargetBranch;
                updateFrequency  = initEditorPopUp.UpdateFrequency;
                mergePolicies    = initEditorPopUp.MergePolicies;
                batchable        = initEditorPopUp.Batchable;
            }

            try
            {
                var newSubscription = await remote.CreateSubscriptionAsync(channel,
                                                                           sourceRepository,
                                                                           targetRepository,
                                                                           targetBranch,
                                                                           updateFrequency,
                                                                           batchable,
                                                                           mergePolicies);

                Console.WriteLine($"Successfully created new subscription with id '{newSubscription.Id}'.");
                return(Constants.SuccessCode);
            }
            catch (RestApiException e) when(e.Response.StatusCode == System.Net.HttpStatusCode.BadRequest)
            {
                // Could have been some kind of validation error (e.g. channel doesn't exist)
                Logger.LogError($"Failed to create subscription: {e.Response.Content}");
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Failed to create subscription.");
                return(Constants.ErrorCode);
            }
        }