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

                Build build = await remote.GetBuildAsync(_options.Id);

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

                return(Constants.SuccessCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Error: Failed to retrieve build with id '{_options.Id}'");
                return(Constants.ErrorCode);
            }
        }
        public async override Task <int> ExecuteAsync()
        {
            if (!(_options.Released ^ _options.NotReleased))
            {
                Console.WriteLine("Please specify either --released or --not-released.");
                return(Constants.ErrorCode);
            }

            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                Build updatedBuild = await remote.UpdateBuildAsync(_options.Id, new BuildUpdate { Released = _options.Released });

                Console.WriteLine($"Updated build {_options.Id} with new information.");
                Console.WriteLine(UxHelpers.GetTextBuildDescription(updatedBuild));
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Error: Failed to update build with id '{_options.Id}'");
                return(Constants.ErrorCode);
            }

            return(Constants.SuccessCode);
        }
示例#3
0
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetRemote(_options, _options.Repository, Logger);

                // Users can ignore the flag and pass in -regex: but to prevent typos we'll avoid that.
                _options.Branch = _options.UseBranchAsRegex ? $"-regex:{_options.Branch}" : GitHelpers.NormalizeBranchName(_options.Branch);

                if (!(await UxHelpers.VerifyAndConfirmBranchExistsAsync(remote, _options.Repository, _options.Branch, !_options.NoConfirmation)))
                {
                    Console.WriteLine("Aborting default channel creation.");
                    return(Constants.ErrorCode);
                }

                await remote.AddDefaultChannelAsync(_options.Repository, _options.Branch, _options.Channel);

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to add a new default channel association.");
                return(Constants.ErrorCode);
            }
        }
示例#4
0
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetRemote(_options, _options.Repository, Logger);

                _options.Branch = GitHelpers.NormalizeBranchName(_options.Branch);

                if (!(await UxHelpers.VerifyAndConfirmBranchExistsAsync(remote, _options.Repository, _options.Branch, !_options.NoConfirmation)))
                {
                    Console.WriteLine("Aborting default channel creation.");
                    return(Constants.ErrorCode);
                }

                await remote.AddDefaultChannelAsync(_options.Repository, _options.Branch, _options.Channel);

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to add a new default channel association.");
                return(Constants.ErrorCode);
            }
        }
示例#5
0
 private async Task LogDependencyGraph(DependencyGraph graph)
 {
     using (StreamWriter writer = UxHelpers.GetOutputFileStreamOrConsole(_options.OutputFile))
     {
         await writer.WriteLineAsync($"Repositories:");
         await LogDependencyGraphNode(writer, graph.Root, "  ");
         await LogIncoherencies(writer, graph);
     }
 }
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                RemoteFactory remoteFactory = new RemoteFactory(_options);
                var           barOnlyRemote = await remoteFactory.GetBarOnlyRemoteAsync(Logger);

                List <DefaultChannel> defaultChannels = (await barOnlyRemote.GetDefaultChannelsAsync()).ToList();
                defaultChannels.Add(
                    new DefaultChannel(0, "https://github.com/dotnet/arcade", true)
                {
                    Branch  = "master",
                    Channel = await barOnlyRemote.GetChannelAsync(".NET Tools - Latest")
                }
                    );
                defaultChannels.Add(
                    new DefaultChannel(0, "https://github.com/dotnet/arcade", true)
                {
                    Branch  = "release/3.x",
                    Channel = await barOnlyRemote.GetChannelAsync(".NET 3 Tools")
                }
                    );
                List <Subscription> subscriptions = (await barOnlyRemote.GetSubscriptionsAsync()).ToList();

                // Build, then prune out what we don't want to see if the user specified
                // channels.
                DependencyFlowGraph flowGraph = DependencyFlowGraph.Build(defaultChannels, subscriptions);

                Channel targetChannel = null;
                if (!string.IsNullOrEmpty(_options.Channel))
                {
                    // Resolve the channel.
                    targetChannel = await UxHelpers.ResolveSingleChannel(barOnlyRemote, _options.Channel);

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

                if (targetChannel != null)
                {
                    flowGraph.PruneGraph(node => IsInterestingNode(targetChannel, node), edge => IsInterestingEdge(edge));
                }

                await LogGraphViz(targetChannel, flowGraph);

                return(Constants.SuccessCode);
            }
            catch (Exception exc)
            {
                Logger.LogError(exc, "Something failed while getting the dependency graph.");
                return(Constants.ErrorCode);
            }
        }
        internal static string GetString(List <DependencyDetail> expectedDependencies)
        {
            StringBuilder stringBuilder = new StringBuilder();

            foreach (DependencyDetail dependency in expectedDependencies)
            {
                stringBuilder.AppendLine(UxHelpers.DependencyToString(dependency));
            }

            return(stringBuilder.ToString());
        }
示例#8
0
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

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

                if (!subscriptions.Any())
                {
                    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 (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve subscriptions");
                return(Constants.ErrorCode);
            }
        }
示例#9
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);
            }
        }
        /// <summary>
        ///     Resolve channel based on the input options. If no channel could be resolved
        ///     based on the input options, returns null.
        /// </summary>
        /// <returns>Default channel or null</returns>
        protected async Task <DefaultChannel> ResolveSingleChannel()
        {
            IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

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

            // User should have supplied id or a combo of the channel name, repo, and branch.
            if (_options.Id != -1)
            {
                DefaultChannel defaultChannel = potentialDefaultChannels.SingleOrDefault(d => d.Id == _options.Id);
                if (defaultChannel == null)
                {
                    Console.WriteLine($"Could not find a default channel with id {_options.Id}");
                }
                return(defaultChannel);
            }
            else if (string.IsNullOrEmpty(_options.Repository) ||
                     string.IsNullOrEmpty(_options.Channel) ||
                     string.IsNullOrEmpty(_options.Branch))
            {
                Console.WriteLine("Please specify either the default channel id with --id or a combination of --channel, --branch and --repo");
                return(null);
            }

            // Otherwise, filter based on the other inputs. If more than one resolves, then print the possible
            // matches and return null
            var matchingChannels = potentialDefaultChannels.Where(d =>
            {
                return((string.IsNullOrEmpty(_options.Repository) || d.Repository.Contains(_options.Repository, StringComparison.OrdinalIgnoreCase)) &&
                       (string.IsNullOrEmpty(_options.Channel) || d.Channel.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase)) &&
                       (string.IsNullOrEmpty(_options.Branch) || d.Branch.Contains(GitHelpers.NormalizeBranchName(_options.Branch), StringComparison.OrdinalIgnoreCase)));
            });

            if (!matchingChannels.Any())
            {
                Console.WriteLine($"No channels found matching the specified criteria.");
                return(null);
            }
            else if (matchingChannels.Count() != 1)
            {
                Console.WriteLine($"More than one channel matching the specified criteria. Please change your options to be more specific.");
                foreach (DefaultChannel defaultChannel in matchingChannels)
                {
                    Console.WriteLine($"    {UxHelpers.GetDefaultChannelDescriptionString(defaultChannel)}");
                }
                return(null);
            }
            else
            {
                return(matchingChannels.Single());
            }
        }
示例#11
0
        /// <summary>
        ///     Log the dependency graph as a simple flat list of repo/sha combinations
        ///     that contribute to this graph.
        /// </summary>
        /// <param name="graph">Graph to log</param>
        private async Task LogFlatDependencyGraph(DependencyGraph graph)
        {
            using (StreamWriter writer = UxHelpers.GetOutputFileStreamOrConsole(_options.OutputFile))
            {
                await writer.WriteLineAsync($"Repositories:");

                foreach (DependencyGraphNode node in graph.Nodes)
                {
                    await LogBasicNodeDetails(writer, node, "  ");
                }
                await LogIncoherencies(writer, graph);
            }
        }
        /// <summary>
        ///     Deletes a build from 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}' is not assigned to channel '{targetChannel.Name}'");
                    return(Constants.SuccessCode);
                }

                Console.WriteLine($"Deleting the following build from channel '{targetChannel.Name}':");
                Console.WriteLine();
                Console.Write(UxHelpers.GetTextBuildDescription(build));

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

                // Let the user know they can trigger subscriptions if they'd like.
                Console.WriteLine("Subscriptions can be triggered to revert to the previous state using the following command:");
                Console.WriteLine($"darc trigger-subscriptions --source-repo {build.GitHubRepository ?? build.AzureDevOpsRepository} --channel {targetChannel.Name}");

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Error: Failed to delete build '{_options.Id}' from channel '{_options.Channel}'.");
                return(Constants.ErrorCode);
            }
        }
示例#13
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);
            }
        }
示例#14
0
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

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

                if (!subscriptions.Any())
                {
                    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}"))
                {
                    // 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);
                    }

                    string subscriptionInfo = UxHelpers.GetTextSubscriptionDescription(subscription, mergePolicies);
                    Console.Write(subscriptionInfo);
                }
                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve subscriptions");
                return(Constants.ErrorCode);
            }
        }
        /// <summary>
        /// Retrieve information about the default association between builds of a specific branch/repo
        /// and a channel.
        /// </summary>
        /// <returns></returns>
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                IEnumerable <DefaultChannel> defaultChannels = (await remote.GetDefaultChannelsAsync())
                                                               .Where(defaultChannel =>
                {
                    return((string.IsNullOrEmpty(_options.SourceRepository) ||
                            defaultChannel.Repository.Contains(_options.SourceRepository, StringComparison.OrdinalIgnoreCase)) &&
                           (string.IsNullOrEmpty(_options.Branch) ||
                            defaultChannel.Branch.Contains(_options.Branch, StringComparison.OrdinalIgnoreCase)) &&
                           (string.IsNullOrEmpty(_options.Channel) ||
                            defaultChannel.Channel.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase)));
                })
                                                               .OrderBy(df => df.Repository);

                if (defaultChannels.Count() == 0)
                {
                    Console.WriteLine("No matching channels were found.");
                }

                // Write out a simple list of each channel's name
                foreach (DefaultChannel defaultChannel in defaultChannels)
                {
                    Console.WriteLine(UxHelpers.GetDefaultChannelDescriptionString(defaultChannel));
                }

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve default channel information.");
                return(Constants.ErrorCode);
            }
        }
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                RemoteFactory remoteFactory = new RemoteFactory(_options);
                var           barOnlyRemote = await remoteFactory.GetBarOnlyRemoteAsync(Logger);

                Channel targetChannel = null;
                if (!string.IsNullOrEmpty(_options.Channel))
                {
                    // Resolve the channel.
                    targetChannel = await UxHelpers.ResolveSingleChannel(barOnlyRemote, _options.Channel);

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

                var flowGraph = await barOnlyRemote.GetDependencyFlowGraph(
                    targetChannel?.Id ?? 0,
                    _options.Days,
                    includeArcade : true,
                    includeBuildTimes : _options.IncludeBuildTimes,
                    includeDisabledSubscriptions : _options.IncludeDisabledSubscriptions,
                    includedFrequencies : _options.IncludedFrequencies?.ToList());

                await LogGraphVizAsync(targetChannel, flowGraph, _options.IncludeBuildTimes);

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception exc)
            {
                Logger.LogError(exc, "Something failed while getting the dependency graph.");
                return(Constants.ErrorCode);
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="channels"></param>
        /// <returns></returns>
        private HashSet <string> ComputeChannelsToEvaluate(IEnumerable <Channel> channels)
        {
            if (!string.IsNullOrEmpty(_options.Channel))
            {
                HashSet <string> channelsToTarget = new HashSet <string>(StringComparer.OrdinalIgnoreCase);
                Channel          targetChannel    = UxHelpers.ResolveSingleChannel(channels, _options.Channel);

                if (targetChannel != null)
                {
                    channelsToTarget.Add(targetChannel.Name);
                }

                return(channelsToTarget);
            }
            else
            {
                // Look up all channels
                return(channels.Select(c => c.Name).ToHashSet());
            }
        }
示例#18
0
        /// <summary>
        ///     Log the graph in graphviz (dot) format.
        /// </summary>
        /// <param name="graph">Graph to log</param>
        /// <remarks>
        /// Example of a graphviz graph description
        ///
        /// digraph graphname {
        ///    a -> b -> c;
        ///    b -> d;
        /// }
        ///
        /// For more info see https://www.graphviz.org/
        /// </remarks>
        /// <returns>Async task</returns>
        private async Task LogGraphViz(DependencyGraph graph)
        {
            using (StreamWriter writer = UxHelpers.GetOutputFileStreamOrConsole(_options.GraphVizOutputFile))
            {
                await writer.WriteLineAsync("digraph repositoryGraph {");

                await writer.WriteLineAsync("    node [shape=record]");

                foreach (DependencyGraphNode node in graph.Nodes)
                {
                    StringBuilder nodeBuilder = new StringBuilder();

                    // First add the node name
                    nodeBuilder.Append($"    {UxHelpers.CalculateGraphVizNodeName(node)}");

                    // Then add the label.  label looks like [label="<info here>"]
                    nodeBuilder.Append("[label=\"");

                    // Append friendly repo name
                    nodeBuilder.Append(UxHelpers.GetSimpleRepoName(node.Repository));
                    nodeBuilder.Append(@"\n");

                    // Append short commit sha
                    nodeBuilder.Append(node.Commit.Substring(0, node.Commit.Length < 10 ? node.Commit.Length : 10));

                    // Append a build string (with newline) if available
                    if (node.ContributingBuilds != null && node.ContributingBuilds.Any())
                    {
                        Build newestBuild = node.ContributingBuilds.OrderByDescending(b => b.DateProduced).First();
                        nodeBuilder.Append($"\\n{newestBuild.DateProduced.ToString("g")} (UTC)");
                    }

                    // Append a diff string if the graph contains diff info.
                    GitDiff diffFrom = node.DiffFrom;
                    if (diffFrom != null)
                    {
                        if (!diffFrom.Valid)
                        {
                            nodeBuilder.Append("\\ndiff unknown");
                        }
                        else if (diffFrom.Ahead != 0 || diffFrom.Behind != 0)
                        {
                            if (node.DiffFrom.Ahead != 0)
                            {
                                nodeBuilder.Append($"\\nahead: {node.DiffFrom.Ahead} commits");
                            }
                            if (node.DiffFrom.Behind != 0)
                            {
                                nodeBuilder.Append($"\\nbehind: {node.DiffFrom.Behind} commits");
                            }
                        }
                        else
                        {
                            nodeBuilder.Append("\\nlatest");
                        }
                    }

                    // Append end of label and end of node.
                    nodeBuilder.Append("\"];");

                    // Write it out.
                    await writer.WriteLineAsync(nodeBuilder.ToString());

                    // Now write the edges
                    foreach (DependencyGraphNode childNode in node.Children)
                    {
                        await writer.WriteLineAsync($"    {UxHelpers.CalculateGraphVizNodeName(node)} -> {UxHelpers.CalculateGraphVizNodeName(childNode)}");
                    }
                }

                await writer.WriteLineAsync("}");
            }
        }
        /// <summary>
        ///     Log the graph in graphviz (dot) format.
        /// </summary>
        /// <param name="graph">Graph to log</param>
        /// <remarks>
        /// Example of a graphviz graph description
        ///
        /// digraph graphname {
        ///    a -> b -> c;
        ///    b -> d;
        /// }
        ///
        /// For more info see https://www.graphviz.org/
        /// </remarks>
        /// <returns>Async task</returns>
        private async Task LogGraphVizAsync(Channel targetChannel, DependencyFlowGraph graph, bool includeBuildTimes)
        {
            StringBuilder subgraphClusterWriter  = null;
            bool          writeToSubgraphCluster = targetChannel != null;

            if (writeToSubgraphCluster)
            {
                subgraphClusterWriter = new StringBuilder();
                subgraphClusterWriter.AppendLine($"    subgraph cluster_{UxHelpers.CalculateGraphVizNodeName(targetChannel.Name)} {{");
                subgraphClusterWriter.AppendLine($"        label = \"{targetChannel.Name}\"");
            }

            using (StreamWriter writer = UxHelpers.GetOutputFileStreamOrConsole(_options.GraphVizOutputFile))
            {
                await writer.WriteLineAsync("digraph repositoryGraph {");

                await writer.WriteLineAsync("    node [shape=record]");

                foreach (DependencyFlowNode node in graph.Nodes)
                {
                    StringBuilder nodeBuilder = new StringBuilder();

                    string style = node.OnLongestBuildPath ? "style=\"diagonals,bold\" color=red" : "";

                    // First add the node name
                    nodeBuilder.Append($"    {UxHelpers.CalculateGraphVizNodeName(node)}");

                    // Then add the label.  label looks like [label="<info here>"]
                    nodeBuilder.Append($"[{style}\nlabel=\"");

                    // Append friendly repo name
                    nodeBuilder.Append(UxHelpers.GetSimpleRepoName(node.Repository));
                    nodeBuilder.Append(@"\n");

                    // Append branch name
                    nodeBuilder.Append(node.Branch);

                    if (includeBuildTimes)
                    {
                        // Append best case
                        nodeBuilder.Append(@"\n");
                        nodeBuilder.Append($"Best Case: {Math.Round(node.BestCasePathTime, 2, MidpointRounding.AwayFromZero)} min");
                        nodeBuilder.Append(@"\n");

                        // Append worst case
                        nodeBuilder.Append($"Worst Case: {Math.Round(node.WorstCasePathTime, 2, MidpointRounding.AwayFromZero)} min");
                        nodeBuilder.Append(@"\n");

                        // Append build times
                        nodeBuilder.Append($"Official Build Time: {Math.Round(node.OfficialBuildTime, 2, MidpointRounding.AwayFromZero)} min");
                        nodeBuilder.Append(@"\n");

                        nodeBuilder.Append($"PR Build Time: {Math.Round(node.PrBuildTime, 2, MidpointRounding.AwayFromZero)} min");
                    }

                    // Append end of label and end of node.
                    nodeBuilder.Append("\"];");

                    // If highlighting a specific channel, Add those nodes to a subgraph cluster
                    // if they output to the subgraph cluster.
                    if (writeToSubgraphCluster && node.OutputChannels.Any(c => c == targetChannel.Name))
                    {
                        subgraphClusterWriter.AppendLine($"        {UxHelpers.CalculateGraphVizNodeName(node)}");
                    }

                    // Write it out.
                    await writer.WriteLineAsync(nodeBuilder.ToString());
                }

                // Now write all the edges
                foreach (DependencyFlowEdge edge in graph.Edges)
                {
                    string fromNode = UxHelpers.CalculateGraphVizNodeName(edge.From);
                    string toNode   = UxHelpers.CalculateGraphVizNodeName(edge.To);
                    string label    = $"{edge.Subscription.Channel.Name} ({edge.Subscription.Policy.UpdateFrequency})";

                    if (writeToSubgraphCluster && edge.Subscription.Channel.Name == targetChannel.Name)
                    {
                        subgraphClusterWriter.AppendLine($"    {fromNode} -> {toNode} [{GetEdgeStyle(edge)}]");
                    }
                    else
                    {
                        await writer.WriteLineAsync($"    {fromNode} -> {toNode} [{GetEdgeStyle(edge)}]");
                    }
                }

                if (writeToSubgraphCluster)
                {
                    await writer.WriteLineAsync(subgraphClusterWriter.ToString());

                    await writer.WriteLineAsync("    }");
                }

                // Write a legend

                await writer.WriteLineAsync("    subgraph cluster1 {");

                await writer.WriteLineAsync("        rankdir=RL;");

                await writer.WriteLineAsync("        label = \"Legend\"");

                await writer.WriteLineAsync("        shape = rectangle;");

                await writer.WriteLineAsync("        color = black;");

                await writer.WriteLineAsync("        a[style = invis];");

                await writer.WriteLineAsync("        b[style = invis];");

                await writer.WriteLineAsync("        c[style = invis];");

                await writer.WriteLineAsync("        d[style = invis];");

                await writer.WriteLineAsync("        e[style = invis];");

                await writer.WriteLineAsync("        f[style = invis];");

                await writer.WriteLineAsync("        g[style = \"diagonals,bold\" color=red];");

                await writer.WriteLineAsync("        h[style = \"diagonals,bold\" color=red];");

                await writer.WriteLineAsync("        c->d[label = \"Updated Every Build\", style = bold];");

                await writer.WriteLineAsync("        a->b[label = \"Updated Every Day\", style = dashed];");

                await writer.WriteLineAsync("        e->f[label = \"Disabled/Updated On-demand\", style = dotted];");

                await writer.WriteLineAsync("        g->h[label = \"Longest Build Path\", color=\"red:invis:red\"];");

                await writer.WriteLineAsync("    }");

                await writer.WriteLineAsync("    subgraph cluster2{");

                await writer.WriteLineAsync("        rankdir=BT;");

                await writer.WriteLineAsync("        style=invis;");

                await writer.WriteLineAsync("        note[shape=plaintext label=\"Best Case: Time through the graph assuming no dependency flow\nWorst Case: Time through the graph with dependency flow (PRs)\"];");

                await writer.WriteLineAsync("    }");

                await writer.WriteLineAsync("    d->note[lhead=cluster2, ltail=cluster1, style=invis];");


                await writer.WriteLineAsync("}");
            }
        }
        /// <summary>
        ///     Compute the subscription health metrics to run based on the channels and input repositories
        /// </summary>
        /// <param name="channelsToEvaluate">Channels in the initial set.</param>
        /// <param name="reposToEvaluate">Repositories in the initial set.</param>
        /// <param name="subscriptions">Subscriptions in the build asset registry.</param>
        /// <returns>List of subscription health metrics</returns>
        /// <remarks>
        ///     Subscription health is based on repo and branch.  We need to find all the combinations that
        ///     that make sense to evaluate.
        ///
        ///     Since this is a target-of-subscription focused metric, use the set of target repos+branches from
        ///     <paramref name="subscriptions"/> where the target repo is in <paramref name="reposToEvaluate"/> and
        ///     the source channel is in <paramref name="channelsToEvaluate"/> or the target branch is in branches.
        ///     Also add in repos (in <paramref name="reposToEvaluate"/>) who have a default channel association targeting
        ///     a channel in <paramref name="channelsToEvaluate"/>
        ///
        ///     Note that this will currently miss completely untargeted branches, until those have at least one
        ///     default channel or subscription. This is a fairly benign limitation.
        /// </remarks>
        private List <Func <Task <HealthMetricWithOutput> > > ComputeSubscriptionHealthMetricsToRun(HashSet <string> channelsToEvaluate,
                                                                                                    HashSet <string> reposToEvaluate, IEnumerable <Subscription> subscriptions, IEnumerable <DefaultChannel> defaultChannels)
        {
            IRemoteFactory remoteFactory = new RemoteFactory(_options);

            HashSet <(string repo, string branch)> repoBranchCombinations =
                GetRepositoryBranchCombinations(channelsToEvaluate, reposToEvaluate, subscriptions, defaultChannels);

            return(repoBranchCombinations.Select <(string repo, string branch), Func <Task <HealthMetricWithOutput> > >(t =>
                                                                                                                        async() =>
            {
                SubscriptionHealthMetric healthMetric = new SubscriptionHealthMetric(t.repo, t.branch,
                                                                                     d => true, Logger, remoteFactory);

                await healthMetric.EvaluateAsync();

                StringBuilder outputBuilder = new StringBuilder();

                if (healthMetric.ConflictingSubscriptions.Any())
                {
                    outputBuilder.AppendLine($"  Conflicting subscriptions:");
                    foreach (var conflict in healthMetric.ConflictingSubscriptions)
                    {
                        outputBuilder.AppendLine($"    {conflict.Asset} would be updated by the following subscriptions:");
                        foreach (var subscription in conflict.Subscriptions)
                        {
                            outputBuilder.AppendLine($"      {UxHelpers.GetSubscriptionDescription(subscription)} ({subscription.Id})");
                        }
                    }
                }

                if (healthMetric.DependenciesMissingSubscriptions.Any())
                {
                    outputBuilder.AppendLine($"  Dependencies missing subscriptions:");
                    foreach (DependencyDetail dependency in healthMetric.DependenciesMissingSubscriptions)
                    {
                        outputBuilder.AppendLine($"    {dependency.Name}");
                    }
                }

                if (healthMetric.DependenciesThatDoNotFlow.Any())
                {
                    outputBuilder.AppendLine($"  Dependencies that do not flow automatically (disabled or frequency=none):");
                    foreach (DependencyDetail dependency in healthMetric.DependenciesThatDoNotFlow)
                    {
                        outputBuilder.AppendLine($"    {dependency.Name}");
                    }
                }

                if (healthMetric.UnusedSubscriptions.Any())
                {
                    outputBuilder.AppendLine($"  Subscriptions that do not have any effect:");
                    foreach (Subscription subscription in healthMetric.UnusedSubscriptions)
                    {
                        outputBuilder.AppendLine($"    {UxHelpers.GetSubscriptionDescription(subscription)} ({subscription.Id})");
                    }
                }

                return new HealthMetricWithOutput(healthMetric, outputBuilder.ToString());
            })
                   .ToList());
        }
        /// <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);
            }
        }
示例#22
0
        /// <summary>
        /// Implements the 'subscription-status' operation
        /// </summary>
        /// <param name="options"></param>
        public override async Task <int> ExecuteAsync()
        {
            if ((_options.Enable && _options.Disable) ||
                (!_options.Enable && !_options.Disable))
            {
                Console.WriteLine("Please specify either --enable or --disable");
                return(Constants.ErrorCode);
            }

            string presentTenseStatusMessage = _options.Enable ? "enable" : "disable";
            string pastTenseStatusMessage    = _options.Enable ? "enabled" : "disabled";
            string actionStatusMessage       = _options.Enable ? "Enabling" : "Disabling";

            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                bool noConfirm = _options.NoConfirmation;
                List <Subscription> subscriptionsToEnableDisable = 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);

                        subscriptionsToEnableDisable.Add(subscription);
                    }
                    catch (RestApiException e) when(e.Response.Status == (int)HttpStatusCode.NotFound)
                    {
                        Console.WriteLine($"Subscription with id '{_options.Id}' was not found.");
                        return(Constants.ErrorCode);
                    }
                }
                else
                {
                    if (!_options.HasAnyFilters())
                    {
                        Console.WriteLine($"Please specify one or more filters to select which subscriptions should be {pastTenseStatusMessage} (see help).");
                        return(Constants.ErrorCode);
                    }

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

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

                    subscriptionsToEnableDisable.AddRange(subscriptions);
                }

                // Filter away subscriptions that already have the desired state:
                subscriptionsToEnableDisable = subscriptionsToEnableDisable.Where(s => s.Enabled != _options.Enable).ToList();

                if (!subscriptionsToEnableDisable.Any())
                {
                    Console.WriteLine($"All subscriptions are already {pastTenseStatusMessage}.");
                    return(Constants.SuccessCode);
                }

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

                    if (!UxHelpers.PromptForYesNo("Continue?"))
                    {
                        Console.WriteLine($"No subscriptions {pastTenseStatusMessage}, exiting.");
                        return(Constants.ErrorCode);
                    }
                }

                Console.Write($"{actionStatusMessage} {subscriptionsToEnableDisable.Count} subscriptions...{(noConfirm ? Environment.NewLine : "")}");
                foreach (var subscription in subscriptionsToEnableDisable)
                {
                    // If noConfirm was passed, print out the subscriptions as we go
                    if (noConfirm)
                    {
                        Console.WriteLine($"  {UxHelpers.GetSubscriptionDescription(subscription)}");
                    }

                    SubscriptionUpdate subscriptionToUpdate = new SubscriptionUpdate
                    {
                        ChannelName      = subscription.Channel.Name,
                        SourceRepository = subscription.SourceRepository,
                        Enabled          = _options.Enable,
                        Policy           = subscription.Policy
                    };
                    subscriptionToUpdate.Policy.Batchable       = subscription.Policy.Batchable;
                    subscriptionToUpdate.Policy.UpdateFrequency = subscription.Policy.UpdateFrequency;
                    subscriptionToUpdate.Policy.MergePolicies   = subscription.Policy.MergePolicies;

                    var updatedSubscription = await remote.UpdateSubscriptionAsync(
                        subscription.Id.ToString(),
                        subscriptionToUpdate);
                }
                Console.WriteLine("done");

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Unexpected error while {actionStatusMessage.ToLower()} subscriptions.");
                return(Constants.ErrorCode);
            }
        }
示例#23
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.BuildUri) ||
                        !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.BuildUri))
                {
                    if (_options.Id != 0 || !string.IsNullOrEmpty(_options.Repo) || !string.IsNullOrEmpty(_options.Commit))
                    {
                        Console.WriteLine("--uri should not be used with other options.");

                        return(Constants.ErrorCode);
                    }

                    Console.WriteLine("This option is not yet supported.");
                    return(Constants.ErrorCode);
                }
                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);
                    }
                    else if (_options.Id != 0 || !string.IsNullOrEmpty(_options.BuildUri))
                    {
                        Console.WriteLine("--repo and --commit should not be used with other options.");
                        return(Constants.ErrorCode);
                    }

                    matchingBuilds = (await remote.GetBuildsAsync(_options.Repo, _options.Commit)).ToList();
                }
                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);
            }
        }
示例#24
0
        public override async Task <int> ExecuteAsync()
        {
            IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

            try
            {
                Channel targetChannel = null;
                if (!string.IsNullOrEmpty(_options.Channel))
                {
                    targetChannel = await UxHelpers.ResolveSingleChannel(remote, _options.Channel);

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

                // Starting with the remote, get information on the asset name + version
                List <Asset> matchingAssets =
                    (await remote.GetAssetsAsync(name: _options.Name, version: _options.Version)).ToList();

                string queryDescriptionString =
                    $"name '{_options.Name}'{(!string.IsNullOrEmpty(_options.Version) ? $" and version '{_options.Version}'" : "")}" +
                    $"{(targetChannel != null ? $" on channel '{targetChannel.Name}'" : "")} in the last {_options.MaxAgeInDays} days";

                // Only print the lookup string if the output type is text.
                if (_options.OutputFormat == DarcOutputType.text)
                {
                    Console.WriteLine($"Looking up assets with {queryDescriptionString}");
                }

                // Walk the assets and look up the corresponding builds, potentially filtering based on channel
                // if there is a target channel
                int maxAgeInDays  = _options.MaxAgeInDays;
                var now           = DateTimeOffset.Now;
                int checkedAssets = 0;

                List <(Asset asset, Build build)> matchingAssetsAfterDate = new List <(Asset, Build)>();

                foreach (Asset asset in matchingAssets)
                {
                    // Get build info for asset
                    Build buildInfo = await remote.GetBuildAsync(asset.BuildId);

                    if (now.Subtract(buildInfo.DateProduced).TotalDays > maxAgeInDays)
                    {
                        break;
                    }

                    checkedAssets++;

                    if (targetChannel != null && !buildInfo.Channels.Any(c => c.Id == targetChannel.Id))
                    {
                        continue;
                    }

                    matchingAssetsAfterDate.Add((asset, buildInfo));
                }

                if (!matchingAssetsAfterDate.Any())
                {
                    Console.WriteLine($"No assets found with {queryDescriptionString}");
                    int remaining = matchingAssets.Count - checkedAssets;
                    if (remaining > 0)
                    {
                        Console.WriteLine($"Skipping build lookup for {remaining} assets. Consider increasing --max-age to check the rest.");
                    }

                    return(Constants.ErrorCode);
                }

                switch (_options.OutputFormat)
                {
                case DarcOutputType.text:
                    foreach ((Asset asset, Build build) in matchingAssetsAfterDate)
                    {
                        Console.WriteLine($"{asset.Name} @ {asset.Version}");
                        Console.Write(UxHelpers.GetTextBuildDescription(build));
                        Console.WriteLine("Locations:");
                        if (asset.Locations.Any())
                        {
                            foreach (var location in asset.Locations)
                            {
                                if (location.IsValid)
                                {
                                    Console.WriteLine($"- {location.Location} ({location.Type})");
                                }
                            }
                        }
                        else
                        {
                            Console.WriteLine("- None");
                        }
                        Console.WriteLine();
                    }
                    break;

                case DarcOutputType.json:
                    var assets = matchingAssetsAfterDate.Select(assetAndBuild =>
                    {
                        return(new
                        {
                            name = assetAndBuild.asset.Name,
                            version = assetAndBuild.asset.Version,
                            build = UxHelpers.GetJsonBuildDescription(assetAndBuild.build),
                            locations = assetAndBuild.asset.Locations.Select(location => location.Location)
                        });
                    });
                    Console.WriteLine(JsonConvert.SerializeObject(assets, Formatting.Indented));
                    break;
                }

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve information about assets.");
                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);
            }
        }
示例#26
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);
            }
        }
        /// <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);
            }
        }
        /// <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.Status == (int)HttpStatusCode.NotFound)
                    {
                        Console.WriteLine($"Subscription with id '{_options.Id}' was not found.");
                        return(Constants.ErrorCode);
                    }
                }
                else
                {
                    if (!_options.HasAnyFilters())
                    {
                        Console.WriteLine($"Please specify one or more filters to select which subscriptions should be triggered (see help).");
                        return(Constants.ErrorCode);
                    }

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

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

                    subscriptionsToTrigger.AddRange(subscriptions);
                }

                if (_options.Build != 0)
                {
                    var specificBuild = await remote.GetBuildAsync(_options.Build);

                    if (specificBuild == null)
                    {
                        Console.WriteLine($"No build found in the BAR with id '{_options.Build}'");
                        return(Constants.ErrorCode);
                    }

                    // If the user specified repo and a build number, error out if anything doesn't match.
                    if (!_options.SubscriptionParameterMatches(_options.SourceRepository, specificBuild.GitHubRepository))
                    {
                        Console.WriteLine($"Build #{_options.Build} was made with repo {specificBuild.GitHubRepository} and does not match provided value ({_options.SourceRepository})");
                        return(Constants.ErrorCode);
                    }

                    Console.WriteLine($"Subscription updates will use Build # {_options.Build} instead of latest available");
                }

                // Filter away subscriptions that are disabled
                List <Subscription> disabledSubscriptions = subscriptionsToTrigger.Where(s => !s.Enabled).ToList();
                subscriptionsToTrigger = subscriptionsToTrigger.Where(s => s.Enabled).ToList();

                if (disabledSubscriptions.Any())
                {
                    Console.WriteLine($"The following {disabledSubscriptions.Count} subscription(s) are disabled and will not be triggered");
                    foreach (var subscription in disabledSubscriptions)
                    {
                        Console.WriteLine($"  {UxHelpers.GetSubscriptionDescription(subscription)}");
                    }
                }

                if (!subscriptionsToTrigger.Any())
                {
                    Console.WriteLine("No enabled subscriptions found matching the specified criteria.");
                    return(Constants.ErrorCode);
                }

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

                    if (!UxHelpers.PromptForYesNo("Continue?"))
                    {
                        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)}");
                    }
                    if (_options.Build > 0)
                    {
                        await remote.TriggerSubscriptionAsync(subscription.Id.ToString(), _options.Build);
                    }
                    else
                    {
                        await remote.TriggerSubscriptionAsync(subscription.Id.ToString());
                    }
                }
                Console.WriteLine("done");

                return(Constants.SuccessCode);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine(e.Message);
                return(Constants.ErrorCode);
            }
            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);

            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.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>()
                });
            }

            string repository = _options.Repository;
            string branch     = _options.Branch;

            // If in quiet (non-interactive mode), ensure that all options were passed, then
            // just call the remote API
            if (_options.Quiet)
            {
                if (string.IsNullOrEmpty(repository) ||
                    string.IsNullOrEmpty(branch))
                {
                    Logger.LogError($"Missing input parameters for merge policies. Please see command help or remove --quiet/-q for interactive mode");
                    return(Constants.ErrorCode);
                }
            }
            else
            {
                // Look up existing merge policies if the repository and branch were specified, and the user didn't
                // specify policies on the command line. In this case, they typically want to update
                if (!mergePolicies.Any() && !string.IsNullOrEmpty(repository) && !string.IsNullOrEmpty(branch))
                {
                    mergePolicies = (await remote.GetRepositoryMergePoliciesAsync(repository, branch)).ToList();
                }

                // Help the user along with a form.  We'll use the API to gather suggested values
                // from existing subscriptions based on the input parameters.
                SetRepositoryMergePoliciesPopUp initEditorPopUp =
                    new SetRepositoryMergePoliciesPopUp("set-policies/set-policies-todo",
                                                        Logger,
                                                        repository,
                                                        branch,
                                                        mergePolicies,
                                                        Constants.AvailableMergePolicyYamlHelp);

                UxManager uxManager = new UxManager(_options.GitLocation, Logger);
                int       exitCode  = uxManager.PopUp(initEditorPopUp);
                if (exitCode != Constants.SuccessCode)
                {
                    return(exitCode);
                }
                repository    = initEditorPopUp.Repository;
                branch        = initEditorPopUp.Branch;
                mergePolicies = initEditorPopUp.MergePolicies;
            }

            IRemote verifyRemote = RemoteFactory.GetRemote(_options, repository, Logger);
            IEnumerable <RepositoryBranch> targetRepository = await verifyRemote.GetRepositoriesAsync(repository);

            if (targetRepository == null || !targetRepository.Any())
            {
                Console.WriteLine($"The target repository '{repository}' doesn't have a Maestro installation. Aborting merge policy creation.");
                return(Constants.ErrorCode);
            }

            if (!(await UxHelpers.VerifyAndConfirmBranchExistsAsync(verifyRemote, repository, branch, !_options.Quiet)))
            {
                Console.WriteLine("Aborting merge policy creation.");
                return(Constants.ErrorCode);
            }

            try
            {
                await remote.SetRepositoryMergePoliciesAsync(
                    repository, branch, mergePolicies);

                Console.WriteLine($"Successfully updated merge policies for {repository}@{branch}.");
                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)
            {
                Logger.LogError($"Failed to set repository auto merge policies: {e.Response.Content}");
                return(Constants.ErrorCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Failed to set merge policies.");
                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.Status == (int)HttpStatusCode.NotFound)
                    {
                        Console.WriteLine($"Subscription with id '{_options.Id}' was not found.");
                        return(Constants.ErrorCode);
                    }
                }
                else
                {
                    if (!_options.HasAnyFilters())
                    {
                        Console.WriteLine($"Please specify one or more filters to select which subscriptions should be triggered (see help).");
                        return(Constants.ErrorCode);
                    }

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

                    if (!subscriptions.Any())
                    {
                        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)}");
                    }

                    if (!UxHelpers.PromptForYesNo("Continue?"))
                    {
                        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);
            }
        }