public static bool ChannelsAreEquals(this ChannelResource chr1, ChannelResource chr2) { var x1 = chr1.Description == chr2.Description; var x2 = chr1.IsDefault == chr2.IsDefault; var x3 = chr1.Name == chr2.Name; var x4 = chr1.LifecycleId == chr2.LifecycleId; return x1 && x2 && x3 && x4; }
public async Task<ReleaseResource> GetReleaseByVersion(string versionNumber, ProjectResource project, ChannelResource channel) { string message; ReleaseResource releaseToPromote = null; if (string.Equals("latest", versionNumber, StringComparison.CurrentCultureIgnoreCase)) { message = channel == null ? "latest release for project" : $"latest release in channel '{channel.Name}'"; log.Debug("Finding {Message:l}", message); var releases = await repository .Projects .GetReleases(project) .ConfigureAwait(false); if (channel == null) { releaseToPromote = releases .Items // We only need the first page .OrderByDescending(r => SemanticVersion.Parse(r.Version)) .FirstOrDefault(); } else { await releases.Paginate(repository, page => { releaseToPromote = page.Items .OrderByDescending(r => SemanticVersion.Parse(r.Version)) .FirstOrDefault(r => r.ChannelId == channel.Id); // If we haven't found one yet, keep paginating return releaseToPromote == null; }) .ConfigureAwait(false); } } else { message = $"release {versionNumber}"; log.Debug("Finding {Message:l}", message); releaseToPromote = await repository.Projects.GetReleaseByVersion(project, versionNumber).ConfigureAwait(false); } if (releaseToPromote == null) { throw new CouldNotFindException($"the {message}", project.Name); } return releaseToPromote; }
public ReleasePlan(ProjectResource project, ChannelResource channel, ReleaseTemplateResource releaseTemplate, IPackageVersionResolver versionResolver) { Project = project; Channel = channel; ReleaseTemplate = releaseTemplate; steps = releaseTemplate.Packages.Select( p => new ReleasePlanItem( p.StepName, p.PackageId, p.FeedId, p.IsResolvable, versionResolver.ResolveVersion(p.StepName) ?? versionResolver.ResolveVersion(p.PackageId))) .ToArray(); }
protected override async Task Execute() { if (!Repository.SupportsChannels()) throw new CommandException("Your Octopus server does not support channels, which was introduced in Octopus 3.2. Please upgrade your Octopus server to start using channels."); if (string.IsNullOrWhiteSpace(projectName)) throw new CommandException("Please specify a project using the parameter: --project=ProjectXYZ"); if (string.IsNullOrWhiteSpace(channelName)) throw new CommandException("Please specify a channel name using the parameter: --channel=ChannelXYZ"); Log.Debug("Loading project {Project:l}...", projectName); var project = await Repository.Projects.FindByName(projectName).ConfigureAwait(false); if (project == null) throw new CouldNotFindException("project named", projectName); LifecycleResource lifecycle = null; if (string.IsNullOrWhiteSpace(lifecycleName)) { Log.Debug("No lifecycle specified. Going to inherit the project lifecycle..."); } else { Log.Debug("Loading lifecycle {Lifecycle:l}...", lifecycleName); lifecycle = await Repository.Lifecycles.FindOne(l => string.Compare(l.Name, lifecycleName, StringComparison.OrdinalIgnoreCase) == 0).ConfigureAwait(false); if (lifecycle == null) throw new CouldNotFindException("lifecycle named", lifecycleName); } var channels = await Repository.Projects.GetChannels(project).ConfigureAwait(false); var channel = await channels .FindOne(Repository, ch => string.Equals(ch.Name, channelName, StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false); if (channel == null) { channel = new ChannelResource { ProjectId = project.Id, Name = channelName, IsDefault = makeDefaultChannel ?? false, Description = channelDescription ?? string.Empty, LifecycleId = lifecycle?.Id, // Allow for the default lifeycle by propagating null Rules = new List<ChannelVersionRuleResource>(), }; Log.Debug("Creating channel {Channel:l}", channelName); await Repository.Channels.Create(channel).ConfigureAwait(false); Log.Information("Channel {Channel:l} created", channelName); return; } if (!updateExisting) throw new CommandException("This channel already exists. If you would like to update it, please use the parameter: --update-existing"); var updateRequired = false; if (channel.LifecycleId != lifecycle?.Id) { if(lifecycle == null) Log.Information("Updating this channel to inherit the project lifecycle for promoting releases"); else Log.Information("Updating this channel to use lifecycle {Lifecycle:l} for promoting releases", lifecycle.Name); channel.LifecycleId = lifecycle?.Id; updateRequired = true; } if (!channel.IsDefault && makeDefaultChannel == true) { Log.Information("Making this the default channel for {Project:l}", project.Name); channel.IsDefault = makeDefaultChannel ?? channel.IsDefault; updateRequired = true; } if (!string.IsNullOrWhiteSpace(channelDescription) && channel.Description != channelDescription) { Log.Information("Updating channel description to '{Description:l}'", channelDescription); channel.Description = channelDescription ?? channel.Description; updateRequired = true; } if (!updateRequired) { Log.Information("The channel already looks exactly the way it should, no need to update it."); return; } Log.Debug("Updating channel {Channel:l}", channelName); await Repository.Channels.Modify(channel).ConfigureAwait(false); Log.Information("Channel {Channel:l} updated", channelName); }
IDictionary<string, object> BuildChannelVersionFilters(string stepName, ChannelResource channel) { var filters = new Dictionary<string, object>(); if (channel == null) return filters; var rule = channel.Rules.FirstOrDefault(r => r.Actions.Contains(stepName)); if (rule == null) return filters; if (!string.IsNullOrWhiteSpace(rule.VersionRange)) filters["versionRange"] = rule.VersionRange; if (!string.IsNullOrWhiteSpace(rule.Tag)) filters["preReleaseTag"] = rule.Tag; return filters; }
public async Task<ReleasePlan> Build(IOctopusAsyncRepository repository, ProjectResource project, ChannelResource channel, string versionPreReleaseTag) { if (repository == null) throw new ArgumentNullException(nameof(repository)); if (project == null) throw new ArgumentNullException(nameof(project)); log.Debug("Finding deployment process..."); var deploymentProcess = await repository.DeploymentProcesses.Get(project.DeploymentProcessId).ConfigureAwait(false); log.Debug("Finding release template..."); var releaseTemplate = await repository.DeploymentProcesses.GetTemplate(deploymentProcess, channel).ConfigureAwait(false); var plan = new ReleasePlan(project, channel, releaseTemplate, versionResolver); if (plan.UnresolvedSteps.Any()) { log.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) { log.Error("The version number for step '{Step:l}' cannot be automatically resolved because the feed or package ID is dynamic.", unresolved.StepName); continue; } if (!string.IsNullOrEmpty(versionPreReleaseTag)) log.Debug("Finding latest package with pre-release '{Tag:l}' for step: {StepName:l}", versionPreReleaseTag, unresolved.StepName); else log.Debug("Finding latest package for step: {StepName:l}", unresolved.StepName); 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.StepName, unresolved.PackageFeedId)); var filters = BuildChannelVersionFilters(unresolved.StepName, 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) { log.Error("Could not find any packages with ID '{PackageId:l}' in the feed '{FeedUri:l}'", unresolved.PackageId, feed.FeedUri); } else { log.Debug("Selected '{PackageId:l}' version '{Version:l}' for '{StepName:l}'", latestPackage.PackageId, latestPackage.Version, unresolved.StepName); unresolved.SetVersionFromLatest(latestPackage.Version); } } } // Test each step in this plan satisfies the channel version rules if (channel != null) { foreach (var step in plan.Steps) { // Note the rule can be null, meaning: anything goes var rule = channel.Rules.SingleOrDefault(r => r.Actions.Any(s => s.Equals(step.StepName, StringComparison.OrdinalIgnoreCase))); var result = await versionRuleTester.Test(repository, rule, step.Version).ConfigureAwait(false); step.SetChannelVersionRuleTestResult(result); } } return plan; }
protected override async Task Export(Dictionary<string, string> parameters) { if (string.IsNullOrWhiteSpace(parameters["Name"])) { throw new CommandException("Please specify the name of the project to export using the paramater: --name=XYZ"); } var projectName = parameters["Name"]; Log.Debug("Finding project: {Project:l}", projectName); var project = await Repository.Projects.FindByName(projectName).ConfigureAwait(false); if (project == null) throw new CouldNotFindException("a project named", projectName); Log.Debug("Finding project group for project"); var projectGroup = await Repository.ProjectGroups.Get(project.ProjectGroupId).ConfigureAwait(false); if (projectGroup == null) throw new CouldNotFindException("project group for project", project.Name); Log.Debug("Finding variable set for project"); var variables = await Repository.VariableSets.Get(project.VariableSetId).ConfigureAwait(false); if (variables == null) throw new CouldNotFindException("variable set for project", project.Name); var channelLifecycles = new List<ReferenceDataItem>(); var channels = new ChannelResource[0]; if (Repository.SupportsChannels()) { Log.Debug("Finding channels for project"); var firstChannelPage = await Repository.Projects.GetChannels(project).ConfigureAwait(false); channels = (await firstChannelPage.GetAllPages(Repository).ConfigureAwait(false)).ToArray(); foreach (var channel in channels.ToArray()) { if (channel.LifecycleId != null) { var channelLifecycle = await Repository.Lifecycles.Get(channel.LifecycleId).ConfigureAwait(false); if (channelLifecycle == null) throw new CouldNotFindException("Lifecycle for channel", channel.Name); if (channelLifecycles.All(cl => cl.Id != channelLifecycle.Id)) { channelLifecycles.Add(new ReferenceDataItem(channelLifecycle.Id, channelLifecycle.Name)); } } } } Log.Debug("Finding deployment process for project"); var deploymentProcess = await Repository.DeploymentProcesses.Get(project.DeploymentProcessId).ConfigureAwait(false); if (deploymentProcess == null) throw new CouldNotFindException("deployment process for project",project.Name); Log.Debug("Finding NuGet feed for deployment process..."); var nugetFeeds = new List<ReferenceDataItem>(); foreach (var step in deploymentProcess.Steps) { foreach (var action in step.Actions) { PropertyValueResource nugetFeedId; if (action.Properties.TryGetValue("Octopus.Action.Package.NuGetFeedId", out nugetFeedId)) { Log.Debug("Finding NuGet feed for step {StepName:l}", step.Name); FeedResource feed = null; if (FeedCustomExpressionHelper.IsRealFeedId(nugetFeedId.Value)) feed = await Repository.Feeds.Get(nugetFeedId.Value).ConfigureAwait(false); else feed = FeedCustomExpressionHelper.CustomExpressionFeedWithId(nugetFeedId.Value); if (feed == null) throw new CouldNotFindException("NuGet feed for step", step.Name); if (nugetFeeds.All(f => f.Id != nugetFeedId.Value)) { nugetFeeds.Add(new ReferenceDataItem(feed.Id, feed.Name)); } } } } Log.Debug("Finding action templates for project"); var actionTemplates = new List<ReferenceDataItem>(); foreach (var step in deploymentProcess.Steps) { foreach (var action in step.Actions) { PropertyValueResource templateId; if (action.Properties.TryGetValue("Octopus.Action.Template.Id", out templateId)) { Log.Debug("Finding action template for step {StepName:l}", step.Name); var template = await actionTemplateRepository.Get(templateId.Value).ConfigureAwait(false); if (template == null) throw new CouldNotFindException("action template for step", step.Name); if (actionTemplates.All(t => t.Id != templateId.Value)) { actionTemplates.Add(new ReferenceDataItem(template.Id, template.Name)); } } } } var libraryVariableSets = new List<ReferenceDataItem>(); foreach (var libraryVariableSetId in project.IncludedLibraryVariableSetIds) { var libraryVariableSet = await Repository.LibraryVariableSets.Get(libraryVariableSetId).ConfigureAwait(false); if (libraryVariableSet == null) { throw new CouldNotFindException("library variable set with Id", libraryVariableSetId); } libraryVariableSets.Add(new ReferenceDataItem(libraryVariableSet.Id, libraryVariableSet.Name)); } LifecycleResource lifecycle = null; if (project.LifecycleId != null) { lifecycle = await Repository.Lifecycles.Get(project.LifecycleId).ConfigureAwait(false); if (lifecycle == null) { throw new CouldNotFindException($"lifecycle with Id {project.LifecycleId} for project ", project.Name); } } var export = new ProjectExport { Project = project, ProjectGroup = new ReferenceDataItem(projectGroup.Id, projectGroup.Name), VariableSet = variables, DeploymentProcess = deploymentProcess, NuGetFeeds = nugetFeeds, ActionTemplates = actionTemplates, LibraryVariableSets = libraryVariableSets, Lifecycle = lifecycle != null ? new ReferenceDataItem(lifecycle.Id, lifecycle.Name) : null, Channels = channels.ToList(), ChannelLifecycles = channelLifecycles, }; var metadata = new ExportMetadata { ExportedAt = DateTime.Now, OctopusVersion = Repository.Client.RootDocument.Version, Type = typeof (ProjectExporter).GetAttributeValue((ExporterAttribute ea) => ea.Name), ContainerType = typeof (ProjectExporter).GetAttributeValue((ExporterAttribute ea) => ea.EntityType) }; FileSystemExporter.Export(FilePath, metadata, export); }
public async Task ShouldUpdateExistingChannel() { Repository.Client.RootDocument.Returns(new RootResource { Links = new LinkCollection().Add("Channels", "DOES_NOT_MATTER") }); var projectName = $"Project-{Guid.NewGuid()}"; var project = new ProjectResource() { Links = new LinkCollection() }; project.Links.Add("Channels", "DOES_NOT_MATTER"); Repository.Projects.FindByName(projectName).Returns(project); var lifecycleName = $"Lifecycle-{Guid.NewGuid()}"; Repository.Lifecycles.FindOne(Arg.Any<Func<LifecycleResource, bool>>()).Returns(new LifecycleResource { Id = lifecycleName }); var channelName = $"Channel-{Guid.NewGuid()}"; var channel = new ChannelResource() { Id = Guid.NewGuid().ToString(), Name = channelName }; Repository.Projects.GetChannels(Arg.Any<ProjectResource>()) .Returns(new ResourceCollection<ChannelResource>(new[] { channel }, new LinkCollection())); CommandLineArgs.Add($"--channel={channelName}"); CommandLineArgs.Add($"--project={projectName}"); CommandLineArgs.Add($"--lifecycle={lifecycleName}"); CommandLineArgs.Add("--update-existing"); await createChannelCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); LogLines.Should().Contain($"[Information] Channel {channelName} updated"); }