Inheritance: Resource, INamedResource
        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;
        }
Example #3
0
 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");
        }