/// <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();
                }
            }
        }
Пример #2
0
        /// <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();
                }
            }
        }