private async Task <ReleasePlan> Build(IOctopusAsyncRepository repository, ProjectResource project, ChannelResource channel, string versionPreReleaseTag, ReleaseTemplateResource releaseTemplate, DeploymentProcessResource deploymentProcess) { var plan = new ReleasePlan(project, channel, releaseTemplate, deploymentProcess, versionResolver); if (plan.UnresolvedSteps.Any()) { commandOutputProvider.Debug( "The package version for some steps was not specified. Going to try and resolve those automatically..."); var allRelevantFeeds = await LoadFeedsForSteps(repository, project, plan.UnresolvedSteps); foreach (var unresolved in plan.UnresolvedSteps) { if (!unresolved.IsResolveable) { commandOutputProvider.Error( "The version number for step '{Step:l}' cannot be automatically resolved because the feed or package ID is dynamic.", unresolved.ActionName); continue; } if (!string.IsNullOrEmpty(versionPreReleaseTag)) { commandOutputProvider.Debug("Finding latest package with pre-release '{Tag:l}' for step: {StepName:l}", versionPreReleaseTag, unresolved.ActionName); } else { commandOutputProvider.Debug("Finding latest package for step: {StepName:l}", unresolved.ActionName); } if (!allRelevantFeeds.TryGetValue(unresolved.PackageFeedId, out var feed)) { throw new CommandException(string.Format( "Could not find a feed with ID {0}, which is used by step: " + unresolved.ActionName, unresolved.PackageFeedId)); } var filters = BuildChannelVersionFilters(unresolved.ActionName, unresolved.PackageReferenceName, channel); filters["packageId"] = unresolved.PackageId; if (!string.IsNullOrWhiteSpace(versionPreReleaseTag)) { filters["preReleaseTag"] = versionPreReleaseTag; } var packages = await repository.Client.Get <List <PackageResource> >(feed.Link("SearchTemplate"), filters) .ConfigureAwait(false); var latestPackage = packages.FirstOrDefault(); if (latestPackage == null) { commandOutputProvider.Error( "Could not find any packages with ID '{PackageId:l}' in the feed '{FeedUri:l}'", unresolved.PackageId, feed.Name); } else { commandOutputProvider.Debug("Selected '{PackageId:l}' version '{Version:l}' for '{StepName:l}'", latestPackage.PackageId, latestPackage.Version, unresolved.ActionName); unresolved.SetVersionFromLatest(latestPackage.Version); } } } // Test each step in this plan satisfies the channel version rules if (channel != null) { foreach (var step in plan.PackageSteps) { // Note the rule can be null, meaning: anything goes var rule = channel.Rules.SingleOrDefault(r => r.ActionPackages.Any(pkg => pkg.DeploymentActionNameMatches(step.ActionName) && pkg.PackageReferenceNameMatches(step.PackageReferenceName))); var result = await versionRuleTester.Test(repository, rule, step.Version, step.PackageFeedId).ConfigureAwait(false); step.SetChannelVersionRuleTestResult(result); } } return(plan); }
public async Task Request() { var serverSupportsChannels = ServerSupportsChannels(); commandOutputProvider.Debug(serverSupportsChannels ? "This Octopus Server supports channels" : "This Octopus Server does not support channels"); commandOutputProvider.Debug("Finding project: {Project:l}", ProjectName); project = await Repository.Projects.FindByName(ProjectName).ConfigureAwait(false); if (project == null) { throw new CouldNotFindException("a project named", ProjectName); } plan = await BuildReleasePlan(project).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(VersionNumber)) { versionNumber = VersionNumber; commandOutputProvider.Debug("Using version number provided on command-line: {Version:l}", versionNumber); } else if (!string.IsNullOrWhiteSpace(plan.ReleaseTemplate.NextVersionIncrement)) { versionNumber = plan.ReleaseTemplate.NextVersionIncrement; commandOutputProvider.Debug("Using version number from release template: {Version:l}", versionNumber); } else if (!string.IsNullOrWhiteSpace(plan.ReleaseTemplate.VersioningPackageStepName)) { versionNumber = plan.GetActionVersionNumber(plan.ReleaseTemplate.VersioningPackageStepName); commandOutputProvider.Debug("Using version number from package step: {Version:l}", versionNumber); } else { throw new CommandException( "A version number was not specified and could not be automatically selected."); } commandOutputProvider.Write( plan.IsViableReleasePlan() ? LogEventLevel.Information : LogEventLevel.Warning, "Release plan for {Project:l} {Version:l}" + System.Environment.NewLine + "{Plan:l}", ProjectName, versionNumber, plan.FormatAsTable() ); if (plan.HasUnresolvedSteps()) { throw new CommandException( "Package versions could not be resolved for one or more of the package packageSteps in this release. See the errors above for details. Either ensure the latest version of the package can be automatically resolved, or set the version to use specifically by using the --package argument."); } if (plan.ChannelHasAnyEnabledSteps() == false) { if (serverSupportsChannels) { throw new CommandException($"Channel {plan.Channel.Name} has no available steps"); } else { throw new CommandException($"Plan has no available steps"); } } if (plan.HasStepsViolatingChannelVersionRules()) { if (IgnoreChannelRules) { commandOutputProvider.Warning("At least one step violates the package version rules for the Channel '{Channel:l}'. Forcing the release to be created ignoring these rules...", plan.Channel.Name); } else { throw new CommandException( $"At least one step violates the package version rules for the Channel '{plan.Channel.Name}'. Either correct the package versions for this release, let Octopus select the best channel by omitting the --channel argument, select a different channel using --channel=MyChannel argument, or ignore these version rules altogether by using the --ignoreChannelRules argument."); } } if (IgnoreIfAlreadyExists) { commandOutputProvider.Debug("Checking for existing release for {Project:l} {Version:l} because you specified --ignoreexisting...", ProjectName, versionNumber); try { var found = await Repository.Projects.GetReleaseByVersion(project, versionNumber) .ConfigureAwait(false); if (found != null) { commandOutputProvider.Information("A release of {Project:l} with the number {Version:l} already exists, and you specified --ignoreexisting, so we won't even attempt to create the release.", ProjectName, versionNumber); return; } } catch (OctopusResourceNotFoundException) { // Expected commandOutputProvider.Debug("No release exists - the coast is clear!"); } } if (WhatIf) { // We were just doing a dry run - bail out here if (DeployToEnvironmentNames.Any()) { commandOutputProvider.Information("[WhatIf] This release would have been created using the release plan and deployed to {Environments:l}", DeployToEnvironmentNames.CommaSeperate()); } else { commandOutputProvider.Information("[WhatIf] This release would have been created using the release plan"); } } else { // Actually create the release! commandOutputProvider.Debug("Creating release..."); release = await Repository.Releases.Create(new ReleaseResource(versionNumber, project.Id, plan.Channel?.Id) { ReleaseNotes = ReleaseNotes, SelectedPackages = plan.GetSelections() }, ignoreChannelRules : IgnoreChannelRules) .ConfigureAwait(false); commandOutputProvider.Information("Release {Version:l} created successfully!", release.Version); commandOutputProvider.ServiceMessage("setParameter", new { name = "octo.releaseNumber", value = release.Version }); commandOutputProvider.TfsServiceMessage(ServerBaseUrl, project, release); await DeployRelease(project, release).ConfigureAwait(false); } }
public async Task <ReleasePlan> Build(IOctopusAsyncRepository repository, ProjectResource project, ChannelResource channel, string versionPreReleaseTag, string versionPreReleaseTagFallBacks, string softDefaultPackageVersion, bool LatestByPublishDate) { if (repository == null) { throw new ArgumentNullException(nameof(repository)); } if (project == null) { throw new ArgumentNullException(nameof(project)); } commandOutputProvider.Debug("Finding deployment process..."); var deploymentProcess = await repository.DeploymentProcesses.Get(project.DeploymentProcessId).ConfigureAwait(false); commandOutputProvider.Debug("Finding release template..."); var releaseTemplate = await repository.DeploymentProcesses.GetTemplate(deploymentProcess, channel).ConfigureAwait(false); var plan = new ReleasePlan(project, channel, releaseTemplate, deploymentProcess, versionResolver); if (plan.UnresolvedSteps.Any()) { commandOutputProvider.Debug("The package version for some steps was not specified. Going to try and resolve those automatically..."); foreach (var unresolved in plan.UnresolvedSteps) { if (!unresolved.IsResolveable) { commandOutputProvider.Error("The version number for step '{Step:l}' cannot be automatically resolved because the feed or package ID is dynamic.", unresolved.ActionName); continue; } var feed = await repository.Feeds.Get(unresolved.PackageFeedId).ConfigureAwait(false); if (feed == null) { throw new CommandException(string.Format("Could not find a feed with ID {0}, which is used by step: " + unresolved.ActionName, unresolved.PackageFeedId)); } var packages = new System.Collections.Generic.List <Octopus.Client.Model.PackageResource>(); PackageResource latestPackage; String versionSource = "Latest available"; var filters = BuildChannelVersionFilters(unresolved.ActionName, unresolved.PackageReferenceName, channel); filters["packageId"] = unresolved.PackageId; commandOutputProvider.Debug("------------------------------------------\r\n"); commandOutputProvider.Debug("Step: '{StepName:l}', Package: '{PackageId:l}':\r\n", unresolved.ActionName, unresolved.PackageId); //look for exact version of package specified softDefaultPackageVersion, bypass all further version-seeking heurstics if succeed if (!string.IsNullOrWhiteSpace(softDefaultPackageVersion)) { filters["versionRange"] = "[" + softDefaultPackageVersion + "]"; packages = await repository.Client.Get <List <PackageResource> >(feed.Link("SearchTemplate"), filters).ConfigureAwait(false); latestPackage = packages.FirstOrDefault(); if (latestPackage != null) { commandOutputProvider.Debug("Version '{Version:l}' was found using softDefaultPackageVersion specified. Any further version-seeking heurstics will be bypassed.", latestPackage.Version); versionSource = "softDefaultPackageVersion"; unresolved.SetVersionFromLatest(latestPackage.Version, versionSource); continue; } else { filters.Remove("versionRange"); commandOutputProvider.Debug("Could not find package with softDefaultPackageVersion: '{softDefaultPackageVersion:l}', falling back to search with another specified methods (versionPreReleaseTag,versionPreReleaseTagFallBacks)", softDefaultPackageVersion); } } if (!string.IsNullOrWhiteSpace(versionPreReleaseTag)) { filters["preReleaseTag"] = versionPreReleaseTag; versionSource = "versionPreReleaseTag"; commandOutputProvider.Debug("versionPreReleaseTag: '{Tag:l}' was specified. Looking for latest package with this tag.'", versionPreReleaseTag); } bool ResolverLooksForPreReleasePackage = !(string.IsNullOrWhiteSpace(versionPreReleaseTag) || versionPreReleaseTag == "^$"); //As we can't sort by publishing date on the server side, we have to take all packages and sort them on the client side if (LatestByPublishDate && ResolverLooksForPreReleasePackage) { filters["take"] = 10000; } else { //AIR-1533. Speed-up package choosing filters["take"] = 1; } packages = await repository.Client.Get <List <PackageResource> >(feed.Link("SearchTemplate"), filters).ConfigureAwait(false); //Get the latest published package for release instead of the package has the biggest SemVer //Only for pre-release packages and only if LatestByPublishDate prop specified //Using latest published package is inappropriate for release packages, because hotfix releases for old versions may be pushed after main major versions. if (LatestByPublishDate && ResolverLooksForPreReleasePackage) { latestPackage = packages.OrderByDescending(o => o.Published).FirstOrDefault(); if (latestPackage != null) { commandOutputProvider.Debug("'--latestbypublishdate' flag was specified. Package resolver will choose version of package by the latest publishing date instead of the higest SemVer version."); } } else { latestPackage = packages.FirstOrDefault(); } if (latestPackage == null && !string.IsNullOrWhiteSpace(versionPreReleaseTag) && !string.IsNullOrWhiteSpace(versionPreReleaseTagFallBacks)) { commandOutputProvider.Debug("Could not find latest package with pre-release '{Tag:l}', falling back to search with pre-release tags '{FallBackTags:l}' ", versionPreReleaseTag, versionPreReleaseTagFallBacks); //trim values and remove empty ones List <string> versionPreReleaseTagFallBacksList = versionPreReleaseTagFallBacks.Split(',').ToList().Select(s => s.Trim()).ToList(); versionPreReleaseTagFallBacksList = versionPreReleaseTagFallBacksList.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList(); foreach (string versionPreReleaseTagFallBack in versionPreReleaseTagFallBacksList) { //similar beahaviour as for general versionPreReleaseTag //Get the latest published package for release instead of the package has the biggest SemVer filters.Remove("take"); filters["preReleaseTag"] = versionPreReleaseTagFallBack; ResolverLooksForPreReleasePackage = !(versionPreReleaseTagFallBack == "^$"); //As we can't sort by publishing date on the server side, we have to take all packages and sort them on the client side if (LatestByPublishDate && ResolverLooksForPreReleasePackage) { filters["take"] = 10000; } else { //AIR-1533. Speed-up package choosing filters["take"] = 1; } packages = await repository.Client.Get <List <PackageResource> >(feed.Link("SearchTemplate"), filters).ConfigureAwait(false); if (LatestByPublishDate && ResolverLooksForPreReleasePackage) { latestPackage = packages.OrderByDescending(o => o.Published).FirstOrDefault(); if (latestPackage != null) { commandOutputProvider.Debug("'--latestbypublishdate' flag was specified. Package resolver will choose version by the latest publishing date instead of the higest SemVer version."); } } else { latestPackage = packages.FirstOrDefault(); } if (latestPackage != null) { versionSource = "versionPreReleaseTagFallBacks"; break; } } } if (latestPackage == null) { commandOutputProvider.Error("Could not find any packages with this ID in the feed '{FeedUri:l}'", feed.Name); } else { commandOutputProvider.Debug("Selected version '{Version:l}'", latestPackage.Version); unresolved.SetVersionFromLatest(latestPackage.Version, versionSource); } } } // Test each step in this plan satisfies the channel version rules if (channel != null) { foreach (var step in plan.PackageSteps) { // Note the rule can be null, meaning: anything goes var rule = channel.Rules.SingleOrDefault(r => r.ActionPackages.Any(pkg => pkg.DeploymentActionNameMatches(step.ActionName) && pkg.PackageReferenceNameMatches(step.PackageReferenceName))); var result = await versionRuleTester.Test(repository, rule, step.Version).ConfigureAwait(false); step.SetChannelVersionRuleTestResult(result); } } return(plan); }