/// <summary> /// Start release pipeline associated with a channel. /// </summary> /// <param name="buildId">Maestro build id.</param> /// <param name="channelId">Maestro channel id.</param> /// <returns></returns> public async Task RunAssociatedReleasePipelinesAsync(int buildId, int channelId, CancellationToken cancellationToken) { Logger.LogInformation($"Starting release pipeline for {buildId} in {channelId}"); Build build = await Context.Builds .Where(b => b.Id == buildId).FirstOrDefaultAsync(); if (build == null) { Logger.LogError($"Could not find the specified BAR Build {buildId} to run a release pipeline."); return; } // If something uses the old API version we won't have this information available. // This will also be the case if something adds an existing build (created using // the old API version) to a channel if (build.AzureDevOpsBuildId == null) { Logger.LogInformation($"barBuildInfo.AzureDevOpsBuildId is null for BAR Build.Id {build.Id}."); return; } Channel channel = await Context.Channels .Where(ch => ch.Id == channelId) .Include(ch => ch.ChannelReleasePipelines) .ThenInclude(crp => crp.ReleasePipeline) .FirstOrDefaultAsync(); if (channel == null) { Logger.LogInformation($"Could not find the specified channel {channelId} to run a release pipeline on."); return; } if (channel.ChannelReleasePipelines?.Any() != true) { Logger.LogInformation($"Channel {channel.Id}, which build with BAR ID {build.Id} is attached to, doesn't have an associated publishing pipeline."); return; } AzureDevOpsClient azdoClient = await GetAzureDevOpsClientForAccount(build.AzureDevOpsAccount); var azdoBuild = await azdoClient.GetBuildAsync( build.AzureDevOpsAccount, build.AzureDevOpsProject, build.AzureDevOpsBuildId.Value); var runningPipelines = await StateManager.GetOrAddAsync <IReliableDictionary <int, IList <ReleasePipelineStatusItem> > >(RunningPipelineDictionaryName); List <ReleasePipelineStatusItem> releaseList = new List <ReleasePipelineStatusItem>(); Logger.LogInformation($"Found {channel.ChannelReleasePipelines.Count} pipeline(s) for channel {channelId}"); foreach (ChannelReleasePipeline pipeline in channel.ChannelReleasePipelines) { try { string organization = pipeline.ReleasePipeline.Organization; string project = pipeline.ReleasePipeline.Project; int pipelineId = pipeline.ReleasePipeline.PipelineIdentifier; Logger.LogInformation($"Going to create a release using pipeline {organization}/{project}/{pipelineId}"); AzureDevOpsReleaseDefinition pipeDef = await azdoClient.GetReleaseDefinitionAsync(organization, project, pipelineId); pipeDef = await azdoClient.AdjustReleasePipelineArtifactSourceAsync(organization, project, pipeDef, azdoBuild); int releaseId = await azdoClient.StartNewReleaseAsync(organization, project, pipeDef, build.Id); var item = new ReleasePipelineStatusItem(releaseId, channelId, organization, project); releaseList.Add(item); Logger.LogInformation($"Created release {releaseId} using pipeline {organization}/{project}/{pipelineId}"); } catch (Exception e) { Logger.LogError($"Some problem happened while starting publishing pipeline " + $"{pipeline.ReleasePipeline.PipelineIdentifier} for build " + $"{build.AzureDevOpsBuildId}: {e.Message}", e); throw; } } if (releaseList.Count > 0) { using (ITransaction tx = StateManager.CreateTransaction()) { var runningPipelinesForBuild = await runningPipelines.TryGetValueAsync(tx, buildId); if (runningPipelinesForBuild.HasValue) { // Some channel already triggered release pipelines for this build. Need to update with the releases for the new channel. releaseList.AddRange(runningPipelinesForBuild.Value); await runningPipelines.TryUpdateAsync(tx, buildId, releaseList, runningPipelinesForBuild.Value); } else { await runningPipelines.AddAsync(tx, buildId, releaseList); } await tx.CommitAsync(); } } }
/// <summary> /// Start release pipeline associated with a channel. /// </summary> /// <param name="buildId">Maestro build id.</param> /// <param name="channelId">Maestro channel id.</param> /// <returns></returns> public async Task RunAssociatedReleasePipelinesAsync(int buildId, int channelId, CancellationToken cancellationToken) { Logger.LogInformation($"Starting release pipeline for {buildId} in {channelId}"); Build build = await Context.Builds .Include(b => b.BuildChannels) .Where(b => b.Id == buildId).FirstOrDefaultAsync(); if (build == null) { Logger.LogError($"Could not find the specified BAR Build {buildId} to run a release pipeline."); return; } if (build.BuildChannels.Any(c => c.ChannelId == channelId)) { Logger.LogInformation($"BAR build {buildId} is already in channel {channelId}. Skipping running releases for it."); return; } // Check if we're already processing releases for this build in this channel var runningPipelines = await StateManager.GetOrAddAsync <IReliableDictionary <int, IList <ReleasePipelineStatusItem> > >(RunningPipelineDictionaryName); using (ITransaction tx = StateManager.CreateTransaction()) { var runningPipelinesForBuild = await runningPipelines.TryGetValueAsync(tx, buildId); if (runningPipelinesForBuild.HasValue) { if (runningPipelinesForBuild.Value.Any(i => i.ChannelId == channelId)) { Logger.LogInformation($"Releases already in progress for build {buildId} and channel {channelId}. Skipping running new releases."); return; } } } // If something uses the old API version we won't have this information available. // This will also be the case if something adds an existing build (created using // the old API version) to a channel if (build.AzureDevOpsBuildId == null) { Logger.LogInformation($"barBuildInfo.AzureDevOpsBuildId is null for BAR Build.Id {build.Id}."); return; } Channel channel = await Context.Channels .Where(ch => ch.Id == channelId) .Include(ch => ch.ChannelReleasePipelines) .ThenInclude(crp => crp.ReleasePipeline) .FirstOrDefaultAsync(); if (channel == null) { Logger.LogInformation($"Could not find the specified channel {channelId} to run a release pipeline on."); return; } if (channel.ChannelReleasePipelines?.Any() != true) { Logger.LogInformation($"Channel {channel.Id}, which build with BAR ID {build.Id} is attached to, doesn't have an associated publishing pipeline."); return; } AzureDevOpsClient azdoClient = await GetAzureDevOpsClientForAccount(build.AzureDevOpsAccount); var azdoBuild = await azdoClient.GetBuildAsync( build.AzureDevOpsAccount, build.AzureDevOpsProject, build.AzureDevOpsBuildId.Value); List <ReleasePipelineStatusItem> releaseList = new List <ReleasePipelineStatusItem>(); Logger.LogInformation($"Found {channel.ChannelReleasePipelines.Count} pipeline(s) for channel {channelId}"); if (channel.ChannelReleasePipelines.Select(pipeline => pipeline.ReleasePipeline.Organization).Distinct(StringComparer.OrdinalIgnoreCase).Count() > 1) { Logger.LogError($"Multiple pipelines in different organizations are not supported (channel {channel.Id})."); return; } foreach (ChannelReleasePipeline pipeline in channel.ChannelReleasePipelines) { try { string organization = pipeline.ReleasePipeline.Organization; string project = pipeline.ReleasePipeline.Project; int pipelineId = pipeline.ReleasePipeline.PipelineIdentifier; // If the release definition is in a separate organization or project than the // build, running the release won't work. This is not that interesting anymore as the repos that have // this issue are on stages. So we can just skip them. if (!organization.Equals(build.AzureDevOpsAccount, StringComparison.OrdinalIgnoreCase) || !project.Equals(build.AzureDevOpsProject, StringComparison.OrdinalIgnoreCase)) { Logger.LogWarning($"Skipping release of build {build.Id} because it is not in the same organzation or project as the release definition."); await AddFinishedBuildChannelsIfNotPresent(new HashSet <BuildChannel> { new BuildChannel { BuildId = buildId, ChannelId = channelId, DateTimeAdded = DateTimeOffset.UtcNow } }); break; } Logger.LogInformation($"Going to create a release using pipeline {organization}/{project}/{pipelineId}"); AzureDevOpsReleaseDefinition pipeDef = await azdoClient.GetReleaseDefinitionAsync(organization, project, pipelineId); pipeDef = await azdoClient.AdjustReleasePipelineArtifactSourceAsync(organization, project, pipeDef, azdoBuild); int releaseId = await azdoClient.StartNewReleaseAsync(organization, project, pipeDef, build.Id); var item = new ReleasePipelineStatusItem(releaseId, channelId, organization, project); releaseList.Add(item); Logger.LogInformation($"Created release {releaseId} using pipeline {organization}/{project}/{pipelineId}"); } catch (Exception e) { Logger.LogError($"Some problem happened while starting publishing pipeline " + $"{pipeline.ReleasePipeline.PipelineIdentifier} for build " + $"{build.AzureDevOpsBuildId}: {e.Message}", e); throw; } } if (releaseList.Count > 0) { using (ITransaction tx = StateManager.CreateTransaction()) { var runningPipelinesForBuild = await runningPipelines.TryGetValueAsync(tx, buildId); if (runningPipelinesForBuild.HasValue) { // Some channel already triggered release pipelines for this build. Need to update with the releases for the new channel. releaseList.AddRange(runningPipelinesForBuild.Value); await runningPipelines.TryUpdateAsync(tx, buildId, releaseList, runningPipelinesForBuild.Value); } else { await runningPipelines.AddAsync(tx, buildId, releaseList); } await tx.CommitAsync(); } } }