/// <summary> /// Retrieve information about a particular subscription's history. /// </summary> /// <param name="options">Command line options</param> /// <returns>Process exit code.</returns> public override async Task <int> ExecuteAsync() { try { DarcSettings darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // No need to set up a git type or PAT here. Remote remote = new Remote(darcSettings, Logger); // Given the ID, grab the subscription history items. var history = await remote.GetSubscriptionHistoryAsync(_options.SubscriptionId); foreach (var historyItem in history) { Console.WriteLine($"{historyItem.Timestamp.Value.LocalDateTime}: ({(historyItem.Success.Value ? "Success" : "Failure")}) - {historyItem.Action}"); if (!historyItem.Success.Value) { Console.WriteLine($" Error Message: {historyItem.ErrorMessage}"); Console.WriteLine($" Retry Command: darc retry-subscription-update --id {_options.SubscriptionId} --update {historyItem.Timestamp.Value.ToUnixTimeSeconds()}"); } } return(Constants.SuccessCode); } catch (Exception e) { Logger.LogError(e, "Error: Failed to retrieve subscription history"); return(Constants.ErrorCode); } }
/// <summary> /// Deletes a channel by name /// </summary> /// <returns></returns> public override async Task <int> ExecuteAsync() { try { DarcSettings darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // No need to set up a git type or PAT here. Remote remote = new Remote(darcSettings, Logger); // Get the ID of the channel with the specified name. Channel existingChannel = (await remote.GetChannelsAsync()).Where(channel => channel.Name.Equals(_options.Name, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); if (existingChannel == null) { Logger.LogError($"Could not find channel with name '{_options.Name}'"); return(Constants.ErrorCode); } await remote.DeleteChannelAsync(existingChannel.Id.Value); Console.WriteLine($"Successfully deleted channel '{existingChannel.Name}'."); return(Constants.SuccessCode); } catch (Exception e) { Logger.LogError(e, "Error: Failed to delete channel."); return(Constants.ErrorCode); } }
public override async Task <int> ExecuteAsync() { DarcSettings darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // No need to set up a git type or PAT here. Remote remote = new Remote(darcSettings, Logger); try { Subscription deletedSubscription = await remote.DeleteSubscriptionAsync(_options.Id); Console.WriteLine($"Successfully deleted subscription with id '{_options.Id}'"); return(Constants.SuccessCode); } catch (ApiErrorException e) when(e.Response.StatusCode == HttpStatusCode.NotFound) { // Not found is fine to ignore. If we get this, it will be an aggregate exception with an inner API exception // that has a response message code of NotFound. Return success. Console.WriteLine($"Subscription with id '{_options.Id}' does not exist."); return(Constants.SuccessCode); } catch (Exception e) { Logger.LogError(e, $"Failed to delete subscription with id '{_options.Id}'"); return(Constants.ErrorCode); } }
/// <summary> /// Retrieve the settings from the combination of the command line /// options and the user's darc settings file. /// </summary> /// <param name="options">Command line options</param> /// <returns>Darc settings for use in remote commands</returns> /// <remarks>The command line takes precedence over the darc settings file.</remarks> public static DarcSettings GetDarcSettings(CommandLineOptions options, ILogger logger) { DarcSettings darcSettings = new DarcSettings(); darcSettings.GitType = GitRepoType.None; try { LocalSettings localSettings = LoadSettingsFile(); darcSettings.BuildAssetRegistryBaseUri = localSettings.BuildAssetRegistryBaseUri; darcSettings.BuildAssetRegistryPassword = localSettings.BuildAssetRegistryPassword; } catch (FileNotFoundException) { // Doesn't have a settings file, which is not an error } catch (Exception e) { logger.LogWarning(e, $"Failed to load the darc settings file, may be corrupted"); } // Override if non-empty on command line darcSettings.BuildAssetRegistryBaseUri = OverrideIfSet(darcSettings.BuildAssetRegistryBaseUri, options.BuildAssetRegistryBaseUri); darcSettings.BuildAssetRegistryPassword = OverrideIfSet(darcSettings.BuildAssetRegistryPassword, options.BuildAssetRegistryPassword); // Currently the darc settings only has one PAT type which is interpreted differently based // on the git type (Azure DevOps vs. GitHub). For now, leave this setting empty until // we know what we are talking to. return(darcSettings); }
/// <summary> /// Retry a specified subscription update. /// </summary> /// <param name="options">Command line options</param> /// <returns>Process exit code.</returns> public override async Task <int> ExecuteAsync() { try { DarcSettings darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // No need to set up a git type or PAT here. Remote remote = new Remote(darcSettings, Logger); Console.WriteLine("Attempting to retry update..."); // Attempt to retry the update. // TODO: Would be great if the controller returned the update result here. await remote.RetrySubscriptionUpdateAsync(_options.SubscriptionId, _options.UpdateId); Console.WriteLine("Update retry queued, please check subscription history in a few moments."); return(Constants.SuccessCode); } catch (ApiErrorException e) when(e.Response.StatusCode == HttpStatusCode.NotFound) { Console.WriteLine($"Subscription with id '{_options.SubscriptionId}' or subscription update with id '{_options.UpdateId}' was not found."); return(Constants.ErrorCode); } catch (ApiErrorException e) when(e.Response.StatusCode == HttpStatusCode.NotAcceptable) { Console.WriteLine($"Update '{_options.UpdateId}' did not fail and cannot be retried"); return(Constants.ErrorCode); } catch (Exception e) { Logger.LogError(e, "Error: Failed to retry subscription update."); return(Constants.ErrorCode); } }
/// <summary> /// Adds a new channel with the specified name. /// </summary> /// <returns>Process exit code.</returns> public override async Task <int> ExecuteAsync() { try { DarcSettings darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // No need to set up a git type or PAT here. Remote remote = new Remote(darcSettings, Logger); // If the user tried to mark as internal, indicate that this is currently // unsupported. if (_options.Internal) { Logger.LogError("Cannot currently mark channels as internal."); return(Constants.ErrorCode); } Channel newChannelInfo = await remote.CreateChannelAsync(_options.Name, _options.Classification); Console.WriteLine($"Successfully created new channel with name '{_options.Name}'."); return(Constants.SuccessCode); } catch (ApiErrorException e) when(e.Response.StatusCode == HttpStatusCode.Conflict) { Logger.LogError($"An existing channel with name '{_options.Name}' already exists"); return(Constants.ErrorCode); } catch (Exception e) { Logger.LogError(e, "Error: Failed to create new channel."); return(Constants.ErrorCode); } }
/// <summary> /// Create the graph or graphs dependending on the amount of base dependencies /// passed in as an input. /// </summary> /// <param name="dependencies">Input dependencies.</param> /// <param name="darcSettings">The Darc settings.</param> /// <returns>Collection of graph nodes.</returns> private async Task <List <DependencyGraph> > CreateGraphAsync( IEnumerable <DependencyDetail> dependencies, DarcSettings darcSettings) { List <DependencyGraph> graph = new List <DependencyGraph>(); if (string.IsNullOrEmpty(_options.AssetName)) { await Task.WhenAll(dependencies.Select( dependency => AddNodeToGraphAsync( darcSettings, dependency, graph))); } else { DependencyDetail dependency = dependencies.FirstOrDefault(); if (dependency == null) { Logger.LogError($"Dependency '{_options.AssetName}' was not found."); return(null); } await AddNodeToGraphAsync(darcSettings, dependency, graph); } return(graph); }
/// <summary> /// Retrieve the settings from the combination of the command line /// options and the user's darc settings file. /// </summary> /// <param name="options">Command line options</param> /// <returns>Darc settings for use in remote commands</returns> /// <remarks>The command line takes precedence over the darc settings file.</remarks> public static DarcSettings GetDarcSettings(CommandLineOptions options, ILogger logger, string repoUri = null) { LocalSettings localSettings = null; DarcSettings darcSettings = new DarcSettings(); darcSettings.GitType = GitRepoType.None; try { localSettings = LoadSettingsFile(options); if (localSettings != null) { darcSettings.BuildAssetRegistryBaseUri = localSettings.BuildAssetRegistryBaseUri; darcSettings.BuildAssetRegistryPassword = localSettings.BuildAssetRegistryPassword; } else { darcSettings.BuildAssetRegistryBaseUri = _defaultBuildAssetRegistryBaseUri; darcSettings.BuildAssetRegistryPassword = options.BuildAssetRegistryPassword; } if (!string.IsNullOrEmpty(repoUri)) { if (repoUri.Contains("github")) { darcSettings.GitType = GitRepoType.GitHub; darcSettings.PersonalAccessToken = localSettings.GitHubToken; } else if (repoUri.Contains("dev.azure.com")) { darcSettings.GitType = GitRepoType.AzureDevOps; darcSettings.PersonalAccessToken = localSettings.AzureDevOpsToken; } else { logger.LogWarning($"Unknown repository '{repoUri}'"); } } } catch (Exception e) { logger.LogWarning(e, $"Failed to load the darc settings file, may be corrupted"); } // Override if non-empty on command line darcSettings.BuildAssetRegistryBaseUri = OverrideIfSet(darcSettings.BuildAssetRegistryBaseUri, options.BuildAssetRegistryBaseUri); darcSettings.BuildAssetRegistryPassword = OverrideIfSet(darcSettings.BuildAssetRegistryPassword, options.BuildAssetRegistryPassword); // Currently the darc settings only has one PAT type which is interpreted differently based // on the git type (Azure DevOps vs. GitHub). For now, leave this setting empty until // we know what we are talking to. return(darcSettings); }
public override async Task <int> ExecuteAsync() { try { Local local = new Local(LocalHelpers.GetGitDir(Logger), Logger); IEnumerable <DependencyDetail> dependencies = await local.GetDependenciesAsync( _options.AssetName); DarcSettings darcSettings = null; if (_options.Remote) { if (string.IsNullOrEmpty(_options.RepoUri)) { Logger.LogError("If '--remote' is set '--repo-uri' is required."); return(Constants.ErrorCode); } darcSettings = LocalSettings.GetDarcSettings( _options, Logger, _options.RepoUri); Remote remote = new Remote(darcSettings, Logger); dependencies = await remote.GetDependenciesAsync( _options.RepoUri, _options.Branch, _options.AssetName); } List <DependencyGraph> graph = await CreateGraphAsync(dependencies, darcSettings); if (graph == null) { return(Constants.ErrorCode); } if (_options.Flat) { LogFlatDependencyGraph(graph); } else { LogDependencyGraph(graph); } return(Constants.SuccessCode); } catch (Exception exc) { Logger.LogError(exc, "Something failed while getting the dependency graph."); return(Constants.ErrorCode); } }
private async Task AddNodeToGraphAsync( DarcSettings darcSettings, DependencyDetail dependency, List <DependencyGraph> graph) { DependencyGraph dependencyGraph = await DependencyGraph.GetDependencyGraphAsync( darcSettings, dependency, _options.Remote, Logger, _options.ReposFolder, _options.RemotesMap); graph.Add(dependencyGraph); }
public async Task <IRemote> CreateAsync(string repoUrl, long installationId) { var settings = new DarcSettings(); if (repoUrl.Contains("github.com")) { settings.GitType = GitRepoType.GitHub; settings.PersonalAccessToken = await GitHubTokenProvider.GetTokenForInstallation(installationId); } else { settings.GitType = GitRepoType.Vsts; settings.PersonalAccessToken = ""; // TODO: get this } return(new Remote(settings, LoggerFactory.CreateLogger <Remote>())); }
/// <summary> /// Get a remote for a specific repo. /// </summary> /// <param name="options">Command line options</param> /// <param name="repoUrl">Repository url</param> /// <param name="logger">Logger</param> /// <returns>New remote</returns> public static IRemote GetRemote(CommandLineOptions options, string repoUrl, ILogger logger) { DarcSettings darcSettings = LocalSettings.GetDarcSettings(options, logger, repoUrl); if (darcSettings.GitType != GitRepoType.None && string.IsNullOrEmpty(darcSettings.GitRepoPersonalAccessToken)) { throw new DarcException($"No personal access token was provided for repo type '{darcSettings.GitType}'"); } // If a temporary repository root was not provided, use the environment // provided temp directory. string temporaryRepositoryRoot = darcSettings.TemporaryRepositoryRoot; if (string.IsNullOrEmpty(temporaryRepositoryRoot)) { temporaryRepositoryRoot = Path.GetTempPath(); } IGitRepo gitClient = null; if (darcSettings.GitType == GitRepoType.GitHub) { gitClient = new GitHubClient(options.GitLocation, darcSettings.GitRepoPersonalAccessToken, logger, temporaryRepositoryRoot, // Caching not in use for Darc local client. null); } else if (darcSettings.GitType == GitRepoType.AzureDevOps) { gitClient = new AzureDevOpsClient(options.GitLocation, darcSettings.GitRepoPersonalAccessToken, logger, temporaryRepositoryRoot); } IBarClient barClient = null; if (!string.IsNullOrEmpty(darcSettings.BuildAssetRegistryPassword)) { barClient = new MaestroApiBarClient(darcSettings.BuildAssetRegistryPassword, darcSettings.BuildAssetRegistryBaseUri); } return(new Remote(gitClient, barClient, logger)); }
public override async Task <int> ExecuteAsync() { try { DarcSettings darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // No need to set up a git type or PAT here. Remote remote = new Remote(darcSettings, Logger); await remote.DeleteDefaultChannelAsync(_options.Repository, _options.Branch, _options.Channel); return(Constants.SuccessCode); } catch (Exception e) { Logger.LogError(e, "Error: Failed remove the default channel association."); return(Constants.ErrorCode); } }
public async Task <IRemote> CreateAsync(string repoUrl, long installationId) { var settings = new DarcSettings(); if (repoUrl.Contains("github.com")) { if (installationId == default) { throw new SubscriptionException($"No installation is avaliable for repository '{repoUrl}'"); } settings.GitType = GitRepoType.GitHub; settings.PersonalAccessToken = await GitHubTokenProvider.GetTokenForInstallation(installationId); } else { settings.GitType = GitRepoType.AzureDevOps; settings.PersonalAccessToken = ""; // TODO: get this } return(new Remote(settings, LoggerFactory.CreateLogger <Remote>())); }
/// <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 { DarcSettings darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // No need to set up a git type or PAT here. Remote remote = new Remote(darcSettings, Logger); var 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))); }); if (defaultChannels.Count() == 0) { Console.WriteLine("No matching channels were found."); } // Write out a simple list of each channel's name foreach (var defaultChannel in defaultChannels) { Console.WriteLine($"{defaultChannel.Repository} @ {defaultChannel.Branch} -> {defaultChannel.Channel.Name}"); } return(Constants.SuccessCode); } catch (Exception e) { Logger.LogError(e, "Error: Failed to retrieve default channel information."); return(Constants.ErrorCode); } }
/// <summary> /// Retrieve information about channels /// </summary> /// <param name="options">Command line options</param> /// <returns>Process exit code.</returns> public override async Task <int> ExecuteAsync() { try { DarcSettings darcSettings = LocalCommands.GetSettings(_options, Logger); // No need to set up a git type or PAT here. Remote remote = new Remote(darcSettings, Logger); var allChannels = await remote.GetChannelsAsync(); // Write out a simple list of each channel's name foreach (var channel in allChannels) { Console.WriteLine(channel.Name); } return(Constants.SuccessCode); } catch (Exception e) { Logger.LogError(e, "Error: Failed to retrieve channels"); return(Constants.ErrorCode); } }
/// <summary> /// Update local dependencies based on a specific channel. /// </summary> /// <param name="options">Command line options</param> /// <returns>Process exit code.</returns> public override async Task <int> ExecuteAsync() { try { DarcSettings darcSettings = darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // TODO: PAT only used for pulling the arcade eng/common dir, // so hardcoded to GitHub PAT right now. Must be more generic in the future. darcSettings.GitType = GitRepoType.GitHub; LocalSettings localSettings = LocalSettings.LoadSettingsFile(_options); darcSettings.PersonalAccessToken = localSettings != null && !string.IsNullOrEmpty(localSettings.GitHubToken) ? localSettings.GitHubToken : _options.GitHubPat; Remote remote = new Remote(darcSettings, Logger); Local local = new Local(LocalHelpers.GetGitDir(Logger), Logger); List <DependencyDetail> dependenciesToUpdate = new List <DependencyDetail>(); bool someUpToDate = false; string finalMessage = $"Local dependencies updated from channel '{_options.Channel}'."; // First we need to figure out what to query for. Load Version.Details.xml and // find all repository uris, optionally restricted by the input dependency parameter. IEnumerable <DependencyDetail> dependencies = await local.GetDependenciesAsync(_options.Name); if (!dependencies.Any()) { Console.WriteLine("Found no dependencies to update."); return(Constants.ErrorCode); } if (!string.IsNullOrEmpty(_options.Name) && !string.IsNullOrEmpty(_options.Version)) { DependencyDetail dependency = dependencies.First(); dependency.Version = _options.Version; dependenciesToUpdate.Add(dependency); Console.WriteLine($"Updating '{dependency.Name}': '{dependency.Version}' => '{_options.Version}'"); finalMessage = $"Local dependency {_options.Name} updated to version '{_options.Version}'."; } else if (!string.IsNullOrEmpty(_options.PackagesFolder)) { try { dependenciesToUpdate.AddRange(GetDependenciesFromPackagesFolder(_options.PackagesFolder, dependencies)); } catch (DarcException exc) { Logger.LogError(exc, $"Error: Failed to update dependencies based on folder '{_options.PackagesFolder}'"); return(Constants.ErrorCode); } finalMessage = $"Local dependencies updated based on packages folder {_options.PackagesFolder}."; } else { // Start channel query. var channel = remote.GetChannelAsync(_options.Channel); // Limit the number of BAR queries by grabbing the repo URIs and making a hash set. var repositoryUrisForQuery = dependencies.Select(dependency => dependency.RepoUri).ToHashSet(); ConcurrentDictionary <string, Task <Build> > getLatestBuildTaskDictionary = new ConcurrentDictionary <string, Task <Build> >(); Channel channelInfo = await channel; if (channelInfo == null) { Console.WriteLine($"Could not find a channel named '{_options.Channel}'."); return(Constants.ErrorCode); } foreach (string repoToQuery in repositoryUrisForQuery) { var latestBuild = remote.GetLatestBuildAsync(repoToQuery, channelInfo.Id.Value); getLatestBuildTaskDictionary.TryAdd(repoToQuery, latestBuild); } // Now walk dependencies again and attempt the update foreach (DependencyDetail dependency in dependencies) { Build build; try { build = await getLatestBuildTaskDictionary[dependency.RepoUri]; } catch (ApiErrorException e) when(e.Response.StatusCode == System.Net.HttpStatusCode.NotFound) { Logger.LogTrace($"No build of '{dependency.RepoUri}' found on channel '{_options.Channel}'."); continue; } Asset buildAsset = build.Assets.Where(asset => asset.Name.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); if (buildAsset == null) { Logger.LogTrace($"Dependency '{dependency.Name}' not found in latest build of '{dependency.RepoUri}' on '{_options.Channel}', skipping."); continue; } if (buildAsset.Version == dependency.Version && buildAsset.Name == dependency.Name && build.Repository == dependency.RepoUri && build.Commit == dependency.Commit) { // No changes someUpToDate = true; continue; } DependencyDetail updatedDependency = new DependencyDetail { // TODO: Not needed, but not currently provided in Build info. Will be available on next rollout. Branch = null, Commit = build.Commit, // If casing changes, ensure that the dependency name gets updated. Name = buildAsset.Name, RepoUri = build.Repository, Version = buildAsset.Version }; dependenciesToUpdate.Add(updatedDependency); // Print out what we are going to do. Console.WriteLine($"Updating '{dependency.Name}': '{dependency.Version}' => '{updatedDependency.Version}'" + $" (from build '{build.BuildNumber}' of '{build.Repository}')"); // Notify on casing changes. if (buildAsset.Name != dependency.Name) { Console.WriteLine($" Dependency name normalized to '{updatedDependency.Name}'"); } dependenciesToUpdate.Add(updatedDependency); } } if (!dependenciesToUpdate.Any()) { // If we found some dependencies already up to date, // then we consider this a success. Otherwise, we didn't even // find matching dependencies so we should let the user know. if (someUpToDate) { Console.WriteLine($"All dependencies are up to date."); return(Constants.SuccessCode); } else { Console.WriteLine($"Found no dependencies to update."); return(Constants.ErrorCode); } } if (_options.DryRun) { return(Constants.SuccessCode); } // Now call the local updater to run the update await local.UpdateDependenciesAsync(dependenciesToUpdate, remote); Console.WriteLine(finalMessage); return(Constants.SuccessCode); } catch (Exception e) { Logger.LogError(e, $"Error: Failed to update dependencies to channel {_options.Channel}"); return(Constants.ErrorCode); } }
/// <summary> /// Update local dependencies based on a specific channel. /// </summary> /// <param name="options">Command line options</param> /// <returns>Process exit code.</returns> public override async Task <int> ExecuteAsync() { try { DarcSettings darcSettings = darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // TODO: PAT only used for pulling the arcade eng/common dir, // so hardcoded to GitHub PAT right now. Must be more generic in the future. darcSettings.GitType = GitRepoType.GitHub; LocalSettings localSettings = LocalSettings.LoadSettingsFile(_options); darcSettings.GitRepoPersonalAccessToken = localSettings != null && !string.IsNullOrEmpty(localSettings.GitHubToken) ? localSettings.GitHubToken : _options.GitHubPat; IRemoteFactory remoteFactory = new RemoteFactory(_options); IRemote barOnlyRemote = await remoteFactory.GetBarOnlyRemoteAsync(Logger); Local local = new Local(Logger); List <DependencyDetail> dependenciesToUpdate = new List <DependencyDetail>(); bool someUpToDate = false; string finalMessage = $"Local dependencies updated from channel '{_options.Channel}'."; // First we need to figure out what to query for. Load Version.Details.xml and // find all repository uris, optionally restricted by the input dependency parameter. IEnumerable <DependencyDetail> localDependencies = await local.GetDependenciesAsync(_options.Name, false); // If the source repository was specified, filter away any local dependencies not from that // source repository. if (!string.IsNullOrEmpty(_options.SourceRepository)) { localDependencies = localDependencies.Where( dependency => dependency.RepoUri.Contains(_options.SourceRepository, StringComparison.OrdinalIgnoreCase)); } if (!localDependencies.Any()) { Console.WriteLine("Found no dependencies to update."); return(Constants.ErrorCode); } List <DependencyDetail> currentDependencies = localDependencies.ToList(); if (!string.IsNullOrEmpty(_options.Name) && !string.IsNullOrEmpty(_options.Version)) { DependencyDetail dependency = currentDependencies.First(); dependency.Version = _options.Version; dependenciesToUpdate.Add(dependency); Console.WriteLine($"Updating '{dependency.Name}': '{dependency.Version}' => '{_options.Version}'"); finalMessage = $"Local dependency {_options.Name} updated to version '{_options.Version}'."; } else if (!string.IsNullOrEmpty(_options.PackagesFolder)) { try { dependenciesToUpdate.AddRange(GetDependenciesFromPackagesFolder(_options.PackagesFolder, currentDependencies)); } catch (DarcException exc) { Logger.LogError(exc, $"Error: Failed to update dependencies based on folder '{_options.PackagesFolder}'"); return(Constants.ErrorCode); } finalMessage = $"Local dependencies updated based on packages folder {_options.PackagesFolder}."; } else { if (!_options.CoherencyOnly) { if (string.IsNullOrEmpty(_options.Channel)) { Console.WriteLine($"Please supply either a channel name (--channel), a packages folder (--packages-folder) " + $"or a specific dependency name and version (--name and --version)."); return(Constants.ErrorCode); } // Start channel query. Task <Channel> channel = barOnlyRemote.GetChannelAsync(_options.Channel); // Limit the number of BAR queries by grabbing the repo URIs and making a hash set. // We gather the latest build for any dependencies that aren't marked with coherent parent // dependencies, as those will be updated based on additional queries. HashSet <string> repositoryUrisForQuery = currentDependencies .Where(dependency => string.IsNullOrEmpty(dependency.CoherentParentDependencyName)) .Select(dependency => dependency.RepoUri) .ToHashSet(); ConcurrentDictionary <string, Task <Build> > getLatestBuildTaskDictionary = new ConcurrentDictionary <string, Task <Build> >(); Channel channelInfo = await channel; if (channelInfo == null) { Console.WriteLine($"Could not find a channel named '{_options.Channel}'."); return(Constants.ErrorCode); } foreach (string repoToQuery in repositoryUrisForQuery) { Console.WriteLine($"Looking up latest build of {repoToQuery} on {_options.Channel}"); var latestBuild = barOnlyRemote.GetLatestBuildAsync(repoToQuery, channelInfo.Id); getLatestBuildTaskDictionary.TryAdd(repoToQuery, latestBuild); } // For each build, first go through and determine the required updates, // updating the "live" dependency information as we go. // Then run a second pass where we update any assets based on coherency information. foreach (KeyValuePair <string, Task <Build> > buildKvPair in getLatestBuildTaskDictionary) { string repoUri = buildKvPair.Key; Build build = await buildKvPair.Value; if (build == null) { Logger.LogTrace($"No build of '{repoUri}' found on channel '{_options.Channel}'."); continue; } IEnumerable <AssetData> assetData = build.Assets.Select( a => new AssetData(a.NonShipping) { Name = a.Name, Version = a.Version }); // Now determine what needs to be updated. List <DependencyUpdate> updates = await barOnlyRemote.GetRequiredNonCoherencyUpdatesAsync( repoUri, build.Commit, assetData, currentDependencies); foreach (DependencyUpdate update in updates) { DependencyDetail from = update.From; DependencyDetail to = update.To; // Print out what we are going to do. Console.WriteLine($"Updating '{from.Name}': '{from.Version}' => '{to.Version}'" + $" (from build '{build.AzureDevOpsBuildNumber}' of '{repoUri}')"); // Final list of dependencies to update dependenciesToUpdate.Add(to); // Replace in the current dependencies list so the correct data is fed into the coherency pass. currentDependencies.Remove(from); currentDependencies.Add(to); } } } Console.WriteLine("Checking for coherency updates..."); // Now run a coherency update based on the current set of dependencies updated // from the previous pass. List <DependencyUpdate> coherencyUpdates = await barOnlyRemote.GetRequiredCoherencyUpdatesAsync(currentDependencies, remoteFactory); foreach (DependencyUpdate dependencyUpdate in coherencyUpdates) { DependencyDetail from = dependencyUpdate.From; DependencyDetail to = dependencyUpdate.To; DependencyDetail coherencyParent = currentDependencies.First(d => d.Name.Equals(from.CoherentParentDependencyName, StringComparison.OrdinalIgnoreCase)); // Print out what we are going to do. Console.WriteLine($"Updating '{from.Name}': '{from.Version}' => '{to.Version}' " + $"to ensure coherency with {from.CoherentParentDependencyName}@{coherencyParent.Version}"); // Final list of dependencies to update dependenciesToUpdate.Add(to); } } if (!dependenciesToUpdate.Any()) { // If we found some dependencies already up to date, // then we consider this a success. Otherwise, we didn't even // find matching dependencies so we should let the user know. if (someUpToDate) { Console.WriteLine($"All dependencies are up to date."); return(Constants.SuccessCode); } else { Console.WriteLine($"Found no dependencies to update."); return(Constants.ErrorCode); } } if (_options.DryRun) { return(Constants.SuccessCode); } // Now call the local updater to run the update. await local.UpdateDependenciesAsync(dependenciesToUpdate, remoteFactory); Console.WriteLine(finalMessage); return(Constants.SuccessCode); } catch (Exception e) { Logger.LogError(e, $"Error: Failed to update dependencies to channel {_options.Channel}"); return(Constants.ErrorCode); } }
/// <summary> /// Retrieve the settings from the combination of the command line /// options and the user's darc settings file. /// </summary> /// <param name="options">Command line options</param> /// <returns>Darc settings for use in remote commands</returns> /// <remarks>The command line takes precedence over the darc settings file.</remarks> public static DarcSettings GetDarcSettings(CommandLineOptions options, ILogger logger, string repoUri = null) { LocalSettings localSettings = null; DarcSettings darcSettings = new DarcSettings(); darcSettings.GitType = GitRepoType.None; try { localSettings = LoadSettingsFile(options); if (localSettings != null) { darcSettings.BuildAssetRegistryBaseUri = localSettings.BuildAssetRegistryBaseUri; darcSettings.BuildAssetRegistryPassword = localSettings.BuildAssetRegistryPassword; } else { darcSettings.BuildAssetRegistryBaseUri = _defaultBuildAssetRegistryBaseUri; darcSettings.BuildAssetRegistryPassword = options.BuildAssetRegistryPassword; } if (!string.IsNullOrEmpty(repoUri)) { if (Uri.TryCreate(repoUri, UriKind.Absolute, out Uri parsedUri)) { if (parsedUri.Host == "github.com") { darcSettings.GitType = GitRepoType.GitHub; darcSettings.GitRepoPersonalAccessToken = localSettings != null ? (string.IsNullOrEmpty(localSettings.GitHubToken) ? options.GitHubPat : localSettings.GitHubToken) : options.GitHubPat; } else if (parsedUri.Host == "dev.azure.com" || parsedUri.Host.EndsWith("visualstudio.com")) { darcSettings.GitType = GitRepoType.AzureDevOps; darcSettings.GitRepoPersonalAccessToken = localSettings != null ? (string.IsNullOrEmpty(localSettings.AzureDevOpsToken) ? options.AzureDevOpsPat : localSettings.AzureDevOpsToken) : options.AzureDevOpsPat; } } if (darcSettings.GitType == GitRepoType.None) { throw new DarcException($"Unknown repository '{repoUri}', repo type set to 'None'."); } } } catch (Exception e) { logger.LogWarning(e, $"Failed to load the darc settings file, may be corrupted"); } // Override if non-empty on command line darcSettings.BuildAssetRegistryBaseUri = OverrideIfSet(darcSettings.BuildAssetRegistryBaseUri, options.BuildAssetRegistryBaseUri); darcSettings.BuildAssetRegistryPassword = OverrideIfSet(darcSettings.BuildAssetRegistryPassword, options.BuildAssetRegistryPassword); // Currently the darc settings only has one PAT type which is interpreted differently based // on the git type (Azure DevOps vs. GitHub). For now, leave this setting empty until // we know what we are talking to. return(darcSettings); }
/// <summary> /// Implements the 'add-subscription' operation /// </summary> /// <param name="options"></param> public override async Task <int> ExecuteAsync() { DarcSettings darcSettings = LocalCommands.GetSettings(_options, Logger); // No need to set up a git type or PAT here. Remote remote = new Remote(darcSettings, Logger); if (_options.IgnoreChecks.Count() > 0 && !_options.AllChecksSuccessfulMergePolicy) { Logger.LogError($"--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("NoExtraCommits", null)); } if (_options.AllChecksSuccessfulMergePolicy) { mergePolicies.Add(new MergePolicy("AllChecksSuccessful", new Dictionary <string, object> { { "ignoreChecks", _options.IgnoreChecks } })); } if (_options.RequireChecksMergePolicy.Count() > 0) { mergePolicies.Add(new MergePolicy("RequireChecks", new Dictionary <string, object> { { "checks", _options.RequireChecksMergePolicy } })); } string channel = _options.Channel; string sourceRepository = _options.SourceRepository; string targetRepository = _options.TargetRepository; string targetBranch = _options.TargetBranch; string updateFrequency = _options.UpdateFrequency; // If in quiet (non-interactive mode), ensure that all options were passed, then // just call the remote API if (_options.Quiet) { if (string.IsNullOrEmpty(channel) || string.IsNullOrEmpty(sourceRepository) || string.IsNullOrEmpty(targetRepository) || string.IsNullOrEmpty(targetBranch) || string.IsNullOrEmpty(updateFrequency) || !Constants.AvailableFrequencies.Contains(updateFrequency, StringComparer.OrdinalIgnoreCase)) { Logger.LogError($"Missing input parameters for the subscription. Please see command help or remove --quiet/-q for interactive mode"); return(Constants.ErrorCode); } } else { // Grab existing subscriptions to get suggested values. // TODO: When this becomes paged, set a max number of results to avoid // pulling too much. var suggestedRepos = remote.GetSubscriptionsAsync(); var suggestedChannels = remote.GetChannelsAsync(); // Help the user along with a form. We'll use the API to gather suggested values // from existing subscriptions based on the input parameters. AddSubscriptionPopUp initEditorPopUp = new AddSubscriptionPopUp("add-subscription/add-subscription-todo", Logger, channel, sourceRepository, targetRepository, targetBranch, updateFrequency, mergePolicies, (await suggestedChannels).Select(suggestedChannel => suggestedChannel.Name), (await suggestedRepos).SelectMany(subscription => new List <string> { subscription.SourceRepository, subscription.TargetRepository }), Constants.AvailableFrequencies, Constants.AvailableMergePolicyYamlHelp); UxManager uxManager = new UxManager(Logger); int exitCode = uxManager.PopUp(initEditorPopUp); if (exitCode != Constants.SuccessCode) { return(exitCode); } channel = initEditorPopUp.Channel; sourceRepository = initEditorPopUp.SourceRepository; targetRepository = initEditorPopUp.TargetRepository; targetBranch = initEditorPopUp.TargetBranch; updateFrequency = initEditorPopUp.UpdateFrequency; mergePolicies = initEditorPopUp.MergePolicies; } try { var newSubscription = await remote.CreateSubscriptionAsync(channel, sourceRepository, targetRepository, targetBranch, updateFrequency, mergePolicies); Console.WriteLine($"Successfully created new subscription with id '{newSubscription.Id}'."); return(Constants.SuccessCode); } catch (Exception e) { Logger.LogError(e, $"Failed to create subscription."); return(Constants.ErrorCode); } }
/// <summary> /// Update local dependencies based on a specific channel. /// </summary> /// <param name="options">Command line options</param> /// <returns>Process exit code.</returns> public override async Task <int> ExecuteAsync() { try { DarcSettings darcSettings = darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // TODO: PAT only used for pulling the Arcade eng/common dir, // so hardcoded to GitHub PAT right now. Must be more generic in the future. darcSettings.GitType = GitRepoType.GitHub; LocalSettings localSettings = LocalSettings.LoadSettingsFile(_options); darcSettings.GitRepoPersonalAccessToken = localSettings != null && !string.IsNullOrEmpty(localSettings.GitHubToken) ? localSettings.GitHubToken : _options.GitHubPat; IRemoteFactory remoteFactory = new RemoteFactory(_options); IRemote barOnlyRemote = await remoteFactory.GetBarOnlyRemoteAsync(Logger); Local local = new Local(Logger); List <DependencyDetail> dependenciesToUpdate = new List <DependencyDetail>(); bool someUpToDate = false; string finalMessage = $"Local dependencies updated from channel '{_options.Channel}'."; // First we need to figure out what to query for. Load Version.Details.xml and // find all repository uris, optionally restricted by the input dependency parameter. IEnumerable <DependencyDetail> localDependencies = await local.GetDependenciesAsync(_options.Name, false); // If the source repository was specified, filter away any local dependencies not from that // source repository. if (!string.IsNullOrEmpty(_options.SourceRepository)) { localDependencies = localDependencies.Where( dependency => dependency.RepoUri.Contains(_options.SourceRepository, StringComparison.OrdinalIgnoreCase)); } if (!localDependencies.Any()) { Console.WriteLine("Found no dependencies to update."); return(Constants.ErrorCode); } List <DependencyDetail> currentDependencies = localDependencies.ToList(); if (!string.IsNullOrEmpty(_options.Name) && !string.IsNullOrEmpty(_options.Version)) { DependencyDetail dependency = currentDependencies.First(); dependency.Version = _options.Version; dependenciesToUpdate.Add(dependency); Console.WriteLine($"Updating '{dependency.Name}': '{dependency.Version}' => '{_options.Version}'"); finalMessage = $"Local dependency {_options.Name} updated to version '{_options.Version}'."; } else if (!string.IsNullOrEmpty(_options.PackagesFolder)) { try { dependenciesToUpdate.AddRange(GetDependenciesFromPackagesFolder(_options.PackagesFolder, currentDependencies)); } catch (DarcException exc) { Logger.LogError(exc, $"Error: Failed to update dependencies based on folder '{_options.PackagesFolder}'"); return(Constants.ErrorCode); } finalMessage = $"Local dependencies updated based on packages folder {_options.PackagesFolder}."; } else if (_options.BARBuildId > 0) { try { if (!_options.CoherencyOnly) { Console.WriteLine($"Looking up build with BAR id {_options.BARBuildId}"); var specificBuild = await barOnlyRemote.GetBuildAsync(_options.BARBuildId); int nonCoherencyResult = await NonCoherencyUpdatesForBuildAsync(specificBuild, barOnlyRemote, currentDependencies, dependenciesToUpdate) .ConfigureAwait(false); if (nonCoherencyResult != Constants.SuccessCode) { Console.WriteLine("Error: Failed to update non-coherent parent tied dependencies."); return(nonCoherencyResult); } string sourceRepo = specificBuild.GitHubRepository ?? specificBuild.AzureDevOpsRepository; string sourceBranch = specificBuild.GitHubBranch ?? specificBuild.AzureDevOpsBranch; finalMessage = $"Local dependencies updated based on build with BAR id {_options.BARBuildId} " + $"({specificBuild.AzureDevOpsBuildNumber} from {sourceRepo}@{sourceBranch})"; } int coherencyResult = await CoherencyUpdatesAsync(barOnlyRemote, remoteFactory, currentDependencies, dependenciesToUpdate) .ConfigureAwait(false); if (coherencyResult != Constants.SuccessCode) { Console.WriteLine("Error: Failed to update coherent parent tied dependencies."); return(coherencyResult); } finalMessage = string.IsNullOrEmpty(finalMessage) ? "Local dependencies successfully updated." : finalMessage; } catch (RestApiException e) when(e.Response.Status == 404) { Console.WriteLine($"Could not find build with BAR id '{_options.BARBuildId}'."); return(Constants.ErrorCode); } } else { if (!_options.CoherencyOnly) { if (string.IsNullOrEmpty(_options.Channel)) { Console.WriteLine($"Please supply either a channel name (--channel), a packages folder (--packages-folder) " + "a BAR build id (--id), or a specific dependency name and version (--name and --version)."); return(Constants.ErrorCode); } // Start channel query. Task <Channel> channel = barOnlyRemote.GetChannelAsync(_options.Channel); // Limit the number of BAR queries by grabbing the repo URIs and making a hash set. // We gather the latest build for any dependencies that aren't marked with coherent parent // dependencies, as those will be updated based on additional queries. HashSet <string> repositoryUrisForQuery = currentDependencies .Where(dependency => string.IsNullOrEmpty(dependency.CoherentParentDependencyName)) .Select(dependency => dependency.RepoUri) .ToHashSet(); ConcurrentDictionary <string, Task <Build> > getLatestBuildTaskDictionary = new ConcurrentDictionary <string, Task <Build> >(); Channel channelInfo = await channel; if (channelInfo == null) { Console.WriteLine($"Could not find a channel named '{_options.Channel}'."); return(Constants.ErrorCode); } foreach (string repoToQuery in repositoryUrisForQuery) { Console.WriteLine($"Looking up latest build of {repoToQuery} on {_options.Channel}"); var latestBuild = barOnlyRemote.GetLatestBuildAsync(repoToQuery, channelInfo.Id); getLatestBuildTaskDictionary.TryAdd(repoToQuery, latestBuild); } // For each build, first go through and determine the required updates, // updating the "live" dependency information as we go. // Then run a second pass where we update any assets based on coherency information. foreach (KeyValuePair <string, Task <Build> > buildKvPair in getLatestBuildTaskDictionary) { string repoUri = buildKvPair.Key; Build build = await buildKvPair.Value; if (build == null) { Logger.LogTrace($"No build of '{repoUri}' found on channel '{_options.Channel}'."); continue; } int nonCoherencyResult = await NonCoherencyUpdatesForBuildAsync(build, barOnlyRemote, currentDependencies, dependenciesToUpdate) .ConfigureAwait(false); if (nonCoherencyResult != Constants.SuccessCode) { Console.WriteLine("Error: Failed to update non-coherent parent tied dependencies."); return(nonCoherencyResult); } } } int coherencyResult = await CoherencyUpdatesAsync(barOnlyRemote, remoteFactory, currentDependencies, dependenciesToUpdate) .ConfigureAwait(false); if (coherencyResult != Constants.SuccessCode) { Console.WriteLine("Error: Failed to update coherent parent tied dependencies."); return(coherencyResult); } } if (!dependenciesToUpdate.Any()) { // If we found some dependencies already up to date, // then we consider this a success. Otherwise, we didn't even // find matching dependencies so we should let the user know. if (someUpToDate) { Console.WriteLine($"All dependencies are up to date."); return(Constants.SuccessCode); } else { Console.WriteLine($"Found no dependencies to update."); return(Constants.ErrorCode); } } if (_options.DryRun) { return(Constants.SuccessCode); } // Now call the local updater to run the update. await local.UpdateDependenciesAsync(dependenciesToUpdate, remoteFactory); Console.WriteLine(finalMessage); return(Constants.SuccessCode); } catch (AuthenticationException e) { Console.WriteLine(e.Message); return(Constants.ErrorCode); } catch (Exception e) { Logger.LogError(e, "Error: Failed to update dependencies."); return(Constants.ErrorCode); } }
public override async Task <int> ExecuteAsync() { try { DarcSettings darcSettings = LocalSettings.GetDarcSettings(_options, Logger); // No need to set up a git type or PAT here. Remote remote = new Remote(darcSettings, Logger); var subscriptions = (await remote.GetSubscriptionsAsync()).Where(subscription => { return((string.IsNullOrEmpty(_options.TargetRepository) || subscription.TargetRepository.Contains(_options.TargetRepository, StringComparison.OrdinalIgnoreCase)) && (string.IsNullOrEmpty(_options.TargetBranch) || subscription.TargetBranch.Contains(_options.TargetBranch, StringComparison.OrdinalIgnoreCase)) && (string.IsNullOrEmpty(_options.SourceRepository) || subscription.SourceRepository.Contains(_options.SourceRepository, StringComparison.OrdinalIgnoreCase)) && (string.IsNullOrEmpty(_options.Channel) || subscription.Channel.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase))); }); if (subscriptions.Count() == 0) { Console.WriteLine("No subscriptions found matching the specified criteria."); } // 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($" - Merge Policies:"); foreach (var mergePolicy in subscription.Policy.MergePolicies) { Console.WriteLine($" {mergePolicy.Name}"); foreach (var mergePolicyProperty in mergePolicy.Properties) { // The merge policy property is a key value pair. For formatting, turn it into a string. // It's often a JToken, so handle appropriately // 1. If the number of lines in the string is 1, write on same line as key // 2. If the number of lines in the string is more than one, start on new // line and indent. string valueString = mergePolicyProperty.Value.ToString(); string[] valueLines = valueString.Split(System.Environment.NewLine); string keyString = $" {mergePolicyProperty.Key} = "; Console.Write(keyString); if (valueLines.Length == 1) { Console.WriteLine(valueString); } else { string indentString = new string(' ', keyString.Length); Console.WriteLine(); foreach (string line in valueLines) { Console.WriteLine($"{indentString}{line}"); } } } } Console.WriteLine($" - Last Build: {(subscription.LastAppliedBuild != null ? subscription.LastAppliedBuild.BuildNumber : "N/A")}"); } return(Constants.SuccessCode); } catch (Exception e) { Logger.LogError(e, "Error: Failed to retrieve subscriptions"); return(Constants.ErrorCode); } }
/// <summary> /// Retrieve the settings from the combination of the command line /// options and the user's darc settings file. /// </summary> /// <param name="options">Command line options</param> /// <returns>Darc settings for use in remote commands</returns> /// <remarks>The command line takes precedence over the darc settings file.</remarks> public static DarcSettings GetDarcSettings(CommandLineOptions options, ILogger logger, string repoUri = null) { LocalSettings localSettings = null; DarcSettings darcSettings = new DarcSettings(); darcSettings.GitType = GitRepoType.None; try { try { localSettings = LoadSettingsFile(); } catch (Exception exc) when(exc is DirectoryNotFoundException || exc is FileNotFoundException) { if (string.IsNullOrEmpty(options.AzureDevOpsPat) && string.IsNullOrEmpty(options.GitHubPat) && string.IsNullOrEmpty(options.BuildAssetRegistryPassword)) { throw new DarcException("Please make sure to run darc authenticate and set" + " 'bar_password' and 'github_token' or 'azure_devops_token' or append" + "'-p <bar_password>' [--github-pat <github_token> | " + "--azdev-pat <azure_devops_token>] to your command"); } } darcSettings.BuildAssetRegistryBaseUri = localSettings.BuildAssetRegistryBaseUri; darcSettings.BuildAssetRegistryPassword = localSettings.BuildAssetRegistryPassword; if (!string.IsNullOrEmpty(repoUri)) { if (repoUri.Contains("github")) { darcSettings.GitType = GitRepoType.GitHub; darcSettings.PersonalAccessToken = localSettings.GitHubToken; } else if (repoUri.Contains("dev.azure.com")) { darcSettings.GitType = GitRepoType.AzureDevOps; darcSettings.PersonalAccessToken = localSettings.AzureDevOpsToken; } else { logger.LogWarning($"Unknown repository '{repoUri}'"); } } } catch (Exception e) { logger.LogWarning(e, $"Failed to load the darc settings file, may be corrupted"); } // Override if non-empty on command line darcSettings.BuildAssetRegistryBaseUri = OverrideIfSet(darcSettings.BuildAssetRegistryBaseUri, options.BuildAssetRegistryBaseUri); darcSettings.BuildAssetRegistryPassword = OverrideIfSet(darcSettings.BuildAssetRegistryPassword, options.BuildAssetRegistryPassword); // Currently the darc settings only has one PAT type which is interpreted differently based // on the git type (Azure DevOps vs. GitHub). For now, leave this setting empty until // we know what we are talking to. return(darcSettings); }