diff --git a/.gitignore b/.gitignore index a9c430d..3c2629a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ make/psmake.* *.nupkg reports/ wiki/ -Manual.md \ No newline at end of file +Manual.md +.idea/ +launchSettings.json diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..590815b --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,28 @@ +pipeline { + agent any + environment { + dotnet = '"C:\\Program Files\\dotnet\\dotnet.exe"' + } + stages { + stage('Restore Packages') { + steps { + bat "$dotnet restore --configfile NuGet.Config" + } + } + stage('Clean') { + steps { + bat "$dotnet clean" + } + } + stage('Build') { + steps { + bat "$dotnet build --configuration Release" + } + } + stage('Publish') { + steps { + bat "$dotnet publish --configuration Release --runtime win-x64 --output \"E:/OctopusProjectBuilder/$env.BRANCH_NAME\"" + } + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Console/OctopusProjectBuilder.Console.csproj b/OctopusProjectBuilder.Console/OctopusProjectBuilder.Console.csproj index 8f196be..28399fb 100644 --- a/OctopusProjectBuilder.Console/OctopusProjectBuilder.Console.csproj +++ b/OctopusProjectBuilder.Console/OctopusProjectBuilder.Console.csproj @@ -22,7 +22,7 @@ - + diff --git a/OctopusProjectBuilder.Console/Options.cs b/OctopusProjectBuilder.Console/Options.cs index 5fc9865..1d28308 100644 --- a/OctopusProjectBuilder.Console/Options.cs +++ b/OctopusProjectBuilder.Console/Options.cs @@ -2,10 +2,12 @@ { internal class Options { - public enum Verb { Upload, Download, CleanupConfig } + public enum Verb { Upload, Download, CleanupConfig, Validate } public string OctopusUrl { get; set; } public string OctopusApiKey { get; set; } + public string ProjectName { get; set; } public string DefinitionsDir { get; set; } + public bool Normalize { get; set; } public Verb Action { get; set; } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Console/Program.cs b/OctopusProjectBuilder.Console/Program.cs index cbbfd29..e7cf95c 100644 --- a/OctopusProjectBuilder.Console/Program.cs +++ b/OctopusProjectBuilder.Console/Program.cs @@ -1,12 +1,16 @@ using System; +using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; using Fclp; using Microsoft.Extensions.Logging; using Octopus.Client; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; using OctopusProjectBuilder.Uploader; using OctopusProjectBuilder.YamlReader; +using OctopusProjectBuilder.YamlReader.Model; namespace OctopusProjectBuilder.Console { @@ -33,6 +37,8 @@ static int Main(string[] args) DownloadDefinitions(options).GetAwaiter().GetResult(); else if (options.Action == Options.Verb.CleanupConfig) CleanupConfig(options); + else if (options.Action == Options.Verb.Validate) + ValidateConfig(options); } catch (Exception e) { @@ -42,6 +48,11 @@ static int Main(string[] args) return 0; } + private static SystemModel ValidateConfig(Options options) + { + return new YamlSystemModelRepository(_loggerFactory).Load(options.DefinitionsDir); + } + private static void CleanupConfig(Options options) { new YamlSystemModelRepository(_loggerFactory).CleanupConfig(options.DefinitionsDir); @@ -55,10 +66,167 @@ private static async Task UploadDefinitions(Options options) private static async Task DownloadDefinitions(Options options) { - var model = await new ModelDownloader(await BuildRepository(options), _loggerFactory).DownloadModel(); - new YamlSystemModelRepository(_loggerFactory).Save(model, options.DefinitionsDir); + var repository = await BuildRepository(options); + var model = await new ModelDownloader(repository, _loggerFactory) + .DownloadModel(options.ProjectName); + + await new YamlSystemModelRepository(_loggerFactory).Save(model, options.DefinitionsDir, async yaml => + { + if (options.Normalize && yaml.LibraryVariableSets != null) + { + foreach (var libraryVariableSet in yaml.LibraryVariableSets + .Where(x => model.Projects + .Any(p => p.IncludedLibraryVariableSetRefs.Any(r => r.Name == x.Name)))) + { + if (libraryVariableSet.ContentType != LibraryVariableSet.VariableSetContentType.ScriptModule) + { + continue; + } + + YamlVariable contentType = libraryVariableSet.Variables + .Where(v => v.Name == "Octopus.Script.Module.Language[" + libraryVariableSet.Name + "]") + .FirstOrDefault(); + string extension; + if (contentType == null) + { + extension = "script"; + } + else + { + switch (contentType.Value) + { + case "PowerShell": + extension = "ps1"; + break; + default: + extension = "script"; + break; + } + } + + YamlVariable content = libraryVariableSet.Variables + .Where(v => v.Name == "Octopus.Script.Module[" + libraryVariableSet.Name + "]") + .FirstOrDefault(); + if (contentType == null) + { + continue; + } + + string path = Path.Combine(options.DefinitionsDir, + "LibraryVariableSet_" + libraryVariableSet.Name + "." + extension); + File.WriteAllText(path, content.Value); + content.Value = null; + content.File = path; + } + } + + // Massage model: reduce identification + if (options.Normalize && yaml.Projects != null) + { + foreach (var project in yaml.Projects) + { + // Normalize IDs on project variable templates + foreach (var variable in project.Templates) + { + variable.Id = null; + } + + // Normalize deployment step templates + foreach (var step in project.DeploymentProcess.Steps) + { + foreach (var action in step.Actions.Where(action => + action.Properties.Any(x => x.Key == "Octopus.Action.Template.Id"))) + { + var actionTemplateId = action.Properties + .FirstOrDefault(property => property.Key == "Octopus.Action.Template.Id"); + var template = + await repository.ActionTemplates.Get(actionTemplateId.Value); + var actionTemplateVersion = action.Properties + .FirstOrDefault(property => property.Key == "Octopus.Action.Template.Version"); + + if (actionTemplateVersion != null) + { + var templateVersion = + int.Parse(actionTemplateVersion.Value); + var versionedTemplate = + await repository.ActionTemplates.GetVersion(template, templateVersion); + + action.Properties = action.Properties.Where(property => + versionedTemplate.Properties.All(property2 => + property2.Key != property.Key) && + property.Key != "Octopus.Action.Template.Version") + .ToArray(); + } + + actionTemplateId.ValueType = "StepTemplateNameToId"; + actionTemplateId.Value = template.Name; + } + + foreach (var action in step.Actions) + { + HandleSplitActionToFile(project.Name, action, options.DefinitionsDir); + } + } + } + } + }); } + private static void HandleSplitActionToFile(string projectName, YamlDeploymentAction action, string directory) + { + if (action.ActionType == "Octopus.Script") + { + var scriptSource = action.Properties + .FirstOrDefault(property => property.Key == "Octopus.Action.Script.ScriptSource"); + if (scriptSource == null) + { + return; + } + + var syntax = action.Properties + .FirstOrDefault(property => property.Key == "Octopus.Action.Script.Syntax"); + if (syntax == null) + { + return; + } + + string extension; + switch (syntax.Value) + { + case "PowerShell": + extension = "ps1"; + break; + default: + extension = "script"; + break; + } + + var scriptBody = action.Properties + .FirstOrDefault(property => property.Key == "Octopus.Action.Script.ScriptBody"); + if (scriptBody == null) + { + return; + } + + string path = Path.Combine(directory, "Script_" + projectName + "_" + action.Name + "." + extension); + File.WriteAllText(path, scriptBody.Value); + scriptBody.Value = null; + scriptBody.File = path; + } + else if (action.ActionType == "Octopus.TentaclePackage") + { + foreach (var postDeploy in action.Properties + .Where(property => property.Key.StartsWith("Octopus.Action.CustomScripts."))) + { + string path = Path.Combine(directory, "Script_" + projectName + "_" + action.Name + "." + + postDeploy.Key.Substring("Octopus.Action.CustomScripts.".Length)); + File.WriteAllText(path, postDeploy.Value); + postDeploy.Value = null; + postDeploy.File = path; + } + } + } + private static async Task BuildRepository(Options options) { return new OctopusAsyncRepository( @@ -71,8 +239,10 @@ public static Options ReadOptions(string[] args) var parser = new FluentCommandLineParser(); parser.Setup(o => o.Action).As('a', "action").Required().WithDescription($"Action to perform: {string.Join(", ", Enum.GetValues(typeof(Options.Verb)).Cast())}"); parser.Setup(o => o.DefinitionsDir).As('d', "definitions").Required().WithDescription("Definitions directory"); - parser.Setup(o => o.OctopusUrl).As('u', "octopusUrl").Required().WithDescription("Octopus Url"); - parser.Setup(o => o.OctopusApiKey).As('k', "octopusApiKey").Required().WithDescription("Octopus API key"); + parser.Setup(o => o.OctopusUrl).As('u', "octopusUrl").WithDescription("Octopus Url"); + parser.Setup(o => o.OctopusApiKey).As('k', "octopusApiKey").WithDescription("Octopus API key"); + parser.Setup(o => o.ProjectName).As('p', "projectName").WithDescription("Project Name"); + parser.Setup(o => o.Normalize).As('n', "normalize").SetDefault(true).WithDescription("Project Name"); parser.SetupHelp("?", "help").Callback(text => System.Console.WriteLine(text)); var result = parser.Parse(args); diff --git a/OctopusProjectBuilder.Console/Properties/launchSettings.json b/OctopusProjectBuilder.Console/Properties/launchSettings.json deleted file mode 100644 index 3bad164..0000000 --- a/OctopusProjectBuilder.Console/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "OctopusProjectBuilder.Console": { - "commandName": "Project", - "commandLineArgs": "--action upload -octopusUrl \\\"http://localhost:xxxx\\\" --octopusApiKey \\\"octopus api key\\\" --definitions \\\"path to definitions\\\"" - } - } -} \ No newline at end of file diff --git a/OctopusProjectBuilder.DocGen/DocGenerator.cs b/OctopusProjectBuilder.DocGen/DocGenerator.cs index 218612a..90746a8 100644 --- a/OctopusProjectBuilder.DocGen/DocGenerator.cs +++ b/OctopusProjectBuilder.DocGen/DocGenerator.cs @@ -94,7 +94,14 @@ private static string GetPropertyDefaultValueText(PropertyInfo propertyInfo) private static string GetDefaultValueText(Type type) { - return type.IsValueType ? Activator.CreateInstance(type).ToString() : "null"; + if (Nullable.GetUnderlyingType(type) == null) + { + return type.IsValueType ? Activator.CreateInstance(type).ToString() : "null"; + } + else + { + return "null"; + } } private static string GetDescription(MemberInfo memberInfo) diff --git a/OctopusProjectBuilder.Model/Channel.cs b/OctopusProjectBuilder.Model/Channel.cs new file mode 100644 index 0000000..ee45ec0 --- /dev/null +++ b/OctopusProjectBuilder.Model/Channel.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace OctopusProjectBuilder.Model +{ + public class Channel + { + public Channel(ElementIdentifier identifier, string description, string projectName, bool? isDefault, + string lifecycleName, IEnumerable versionRules, IEnumerable tenantTags) + { + if (identifier == null) + throw new ArgumentNullException(nameof(identifier)); + + Identifier = identifier; + Description = description; + ProjectName = projectName; + IsDefault = isDefault; + LifecycleName = lifecycleName; + VersionRules = versionRules; + TenantTags = tenantTags.ToArray(); + } + + public ElementIdentifier Identifier { get; } + public string Description { get; set; } + public string ProjectName { get; set; } + public bool? IsDefault { get; set; } + public string LifecycleName { get; set; } + public IEnumerable VersionRules { get; set; } + public IEnumerable TenantTags { get; } + + public override string ToString() + { + return Identifier.ToString(); + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/ChannelVersionRule.cs b/OctopusProjectBuilder.Model/ChannelVersionRule.cs new file mode 100644 index 0000000..4574993 --- /dev/null +++ b/OctopusProjectBuilder.Model/ChannelVersionRule.cs @@ -0,0 +1,19 @@ +using System.Collections; +using System.Collections.Generic; + +namespace OctopusProjectBuilder.Model +{ + public class ChannelVersionRule + { + public ChannelVersionRule(string tag, string versionRange, IEnumerable actionPackages) + { + Tag = tag; + VersionRange = versionRange; + ActionPackages = actionPackages; + } + + public string Tag { get; set; } + public string VersionRange { get; set; } + public IEnumerable ActionPackages { get; set; } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/ChannelVersionRulePackage.cs b/OctopusProjectBuilder.Model/ChannelVersionRulePackage.cs new file mode 100644 index 0000000..0bf7019 --- /dev/null +++ b/OctopusProjectBuilder.Model/ChannelVersionRulePackage.cs @@ -0,0 +1,16 @@ +using Octopus.Client.Model; + +namespace OctopusProjectBuilder.Model +{ + public class ChannelVersionRulePackage + { + public string DeploymentAction { get; set; } + public string PackageReference { get; set; } + + public ChannelVersionRulePackage(string deploymentAction, string packageReference) + { + DeploymentAction = deploymentAction; + PackageReference = packageReference; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/DeploymentAction.cs b/OctopusProjectBuilder.Model/DeploymentAction.cs index 60710ca..c4228d4 100644 --- a/OctopusProjectBuilder.Model/DeploymentAction.cs +++ b/OctopusProjectBuilder.Model/DeploymentAction.cs @@ -6,16 +6,31 @@ namespace OctopusProjectBuilder.Model public class DeploymentAction { public string Name { get; } + public bool IsDisabled { get; } + public ActionCondition Condition { get; } public string ActionType { get; } - public IReadOnlyDictionary Properties { get; } + public IDictionary Properties { get; } public IEnumerable EnvironmentRefs { get; } + public IEnumerable Packages { get; } - public DeploymentAction(string name, string actionType, IReadOnlyDictionary properties, IEnumerable environmentRefs) + public DeploymentAction(string name, bool isDisabled, ActionCondition condition, + string actionType, IDictionary properties, + IEnumerable environmentRefs, + IEnumerable packages) { Name = name; + IsDisabled = isDisabled; + Condition = condition; ActionType = actionType; Properties = properties; EnvironmentRefs = environmentRefs.ToArray(); + Packages = packages.ToArray(); + } + + public enum ActionCondition + { + Success, + Variable } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/DeploymentActionPackage.cs b/OctopusProjectBuilder.Model/DeploymentActionPackage.cs new file mode 100644 index 0000000..222bb12 --- /dev/null +++ b/OctopusProjectBuilder.Model/DeploymentActionPackage.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace OctopusProjectBuilder.Model +{ + public class DeploymentActionPackage + { + public string Name { get; set; } + public string PackageId { get; set; } + public string FeedId { get; set; } + public string AcquisitionLocation { get; set; } + public IDictionary Properties { get; set; } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/DeploymentConnectivityPolicy.cs b/OctopusProjectBuilder.Model/DeploymentConnectivityPolicy.cs new file mode 100644 index 0000000..c189c3d --- /dev/null +++ b/OctopusProjectBuilder.Model/DeploymentConnectivityPolicy.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Octopus.Client.Model; + +namespace OctopusProjectBuilder.Model +{ + public class DeploymentConnectivityPolicy + { + public Octopus.Client.Model.SkipMachineBehavior SkipMachineBehavior { get; set; } + + public IEnumerable TargetRoles { get; set; } + + public bool AllowDeploymentsToNoTargets { get; set; } + + public bool ExcludeUnhealthyTargets { get; set; } + + public DeploymentConnectivityPolicy() => this.TargetRoles = new List(); + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/DeploymentStep.cs b/OctopusProjectBuilder.Model/DeploymentStep.cs index b150ed7..537b05c 100644 --- a/OctopusProjectBuilder.Model/DeploymentStep.cs +++ b/OctopusProjectBuilder.Model/DeploymentStep.cs @@ -10,14 +10,15 @@ public class DeploymentStep public StepCondition Condition { get; } public bool RequiresPackagesToBeAcquired { get; } public StepStartTrigger StartTrigger { get; } - public IReadOnlyDictionary Properties { get; } + public IDictionary Properties { get; } public IEnumerable Actions { get; } public enum StepCondition { Success, Failure, - Always + Always, + Variable } public enum StepStartTrigger @@ -26,7 +27,9 @@ public enum StepStartTrigger StartWithPrevious } - public DeploymentStep(string name, StepCondition condition, bool requiresPackagesToBeAcquired, StepStartTrigger startTrigger, IReadOnlyDictionary properties, IEnumerable actions) + public DeploymentStep(string name, StepCondition condition, bool requiresPackagesToBeAcquired, + StepStartTrigger startTrigger, IDictionary properties, + IEnumerable actions) { Name = name; Condition = condition; diff --git a/OctopusProjectBuilder.Model/GuidedFailureMode.cs b/OctopusProjectBuilder.Model/GuidedFailureMode.cs new file mode 100644 index 0000000..803a8ca --- /dev/null +++ b/OctopusProjectBuilder.Model/GuidedFailureMode.cs @@ -0,0 +1,9 @@ +namespace OctopusProjectBuilder.Model +{ + public enum GuidedFailureMode + { + EnvironmentDefault, + Off, + On, + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/OctopusProjectBuilder.Model.csproj b/OctopusProjectBuilder.Model/OctopusProjectBuilder.Model.csproj index 34fe025..1d32083 100644 --- a/OctopusProjectBuilder.Model/OctopusProjectBuilder.Model.csproj +++ b/OctopusProjectBuilder.Model/OctopusProjectBuilder.Model.csproj @@ -5,7 +5,7 @@ - + diff --git a/OctopusProjectBuilder.Model/Project.cs b/OctopusProjectBuilder.Model/Project.cs index 701aa87..50b00a7 100644 --- a/OctopusProjectBuilder.Model/Project.cs +++ b/OctopusProjectBuilder.Model/Project.cs @@ -1,50 +1,52 @@ using System; using System.Collections.Generic; using System.Linq; +using Octopus.Client.Model; namespace OctopusProjectBuilder.Model { public class Project : IVariableSet { - public Project(ElementIdentifier identifier, string description, bool isDisabled, bool autoCreateRelease, bool defaultToSkipIfAlreadyInstalled, DeploymentProcess deploymentProcess, IEnumerable variables, IEnumerable libraryVariableSetRefs, ElementReference lifecycleRef, ElementReference projectGroupRef, VersioningStrategy versioningStrategy, IEnumerable triggers, TenantedDeploymentMode tenantedDeploymentMode) + public Project(ElementIdentifier identifier, string description, bool? isDisabled, bool? autoCreateRelease, + bool? defaultToSkipIfAlreadyInstalled, DeploymentProcess deploymentProcess, IEnumerable variables, + IEnumerable libraryVariableSetRefs, ElementReference lifecycleRef, + ElementReference projectGroupRef, VersioningStrategy versioningStrategy, + IEnumerable triggers, TenantedDeploymentMode? tenantedDeploymentMode, + IEnumerable templates) { if (identifier == null) throw new ArgumentNullException(nameof(identifier)); - if (deploymentProcess == null) - throw new ArgumentNullException(nameof(deploymentProcess)); - if (libraryVariableSetRefs == null) - throw new ArgumentNullException(nameof(libraryVariableSetRefs)); - if (triggers == null) - throw new ArgumentNullException(nameof(triggers)); + Identifier = identifier; Description = description; IsDisabled = isDisabled; AutoCreateRelease = autoCreateRelease; DefaultToSkipIfAlreadyInstalled = defaultToSkipIfAlreadyInstalled; DeploymentProcess = deploymentProcess; - IncludedLibraryVariableSetRefs = libraryVariableSetRefs.ToArray(); + IncludedLibraryVariableSetRefs = libraryVariableSetRefs?.ToArray(); Variables = variables.ToArray(); LifecycleRef = lifecycleRef; ProjectGroupRef = projectGroupRef; VersioningStrategy = versioningStrategy; - Triggers = triggers.ToArray(); + Triggers = triggers?.ToArray(); TenantedDeploymentMode = tenantedDeploymentMode; + Templates = templates; } public ElementIdentifier Identifier { get; } public string Description { get; } - public bool IsDisabled { get; } - public bool AutoCreateRelease { get; } - public bool DefaultToSkipIfAlreadyInstalled { get; } + public bool? IsDisabled { get; } + public bool? AutoCreateRelease { get; } + public bool? DefaultToSkipIfAlreadyInstalled { get; } public DeploymentProcess DeploymentProcess { get; } public IEnumerable IncludedLibraryVariableSetRefs { get; } public ElementReference LifecycleRef { get; } public ElementReference ProjectGroupRef { get; } public IEnumerable Variables { get; } + public IEnumerable Templates { get; } public VersioningStrategy VersioningStrategy { get; } public IEnumerable Triggers { get; } - public TenantedDeploymentMode TenantedDeploymentMode { get; } - + public TenantedDeploymentMode? TenantedDeploymentMode { get; } public override string ToString() { diff --git a/OctopusProjectBuilder.Model/PropertyValue.cs b/OctopusProjectBuilder.Model/PropertyValue.cs index 637ba8c..ed7fbcf 100644 --- a/OctopusProjectBuilder.Model/PropertyValue.cs +++ b/OctopusProjectBuilder.Model/PropertyValue.cs @@ -3,6 +3,7 @@ namespace OctopusProjectBuilder.Model public class PropertyValue { public bool IsSensitive { get; } + public string ValueType { get; } public string Value { get; } public PropertyValue(bool isSensitive, string value) @@ -10,5 +11,12 @@ public PropertyValue(bool isSensitive, string value) IsSensitive = isSensitive; Value = value; } + + public PropertyValue(bool isSensitive, string value, string valueType) + { + IsSensitive = isSensitive; + Value = value; + ValueType = valueType; + } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/Runbook.cs b/OctopusProjectBuilder.Model/Runbook.cs new file mode 100644 index 0000000..c1cc1b1 --- /dev/null +++ b/OctopusProjectBuilder.Model/Runbook.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace OctopusProjectBuilder.Model +{ + public class Runbook + { + public ElementIdentifier Identifier { get; set; } + public string Description { get; set; } + public string ProjectName { get; set; } + public RunbookProcess Process { get; set; } + public Model.DeploymentConnectivityPolicy ConnectivityPolicy { get; set; } + public TenantedDeploymentMode? MultiTenancyMode { get; set; } + public RunbookEnvironmentScope? EnvironmentScope { get; set; } + public RunbookRetentionPeriod RunRetentionPolicy { get; set; } + public GuidedFailureMode? GuidedFailureMode { get; set; } + public IEnumerable EnvironmentRefs { get; set; } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/RunbookEnvironmentScope.cs b/OctopusProjectBuilder.Model/RunbookEnvironmentScope.cs new file mode 100644 index 0000000..2b75bf0 --- /dev/null +++ b/OctopusProjectBuilder.Model/RunbookEnvironmentScope.cs @@ -0,0 +1,9 @@ +namespace OctopusProjectBuilder.Model +{ + public enum RunbookEnvironmentScope + { + All, + Specified, + FromProjectLifecycles, + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/RunbookProcess.cs b/OctopusProjectBuilder.Model/RunbookProcess.cs new file mode 100644 index 0000000..0d63309 --- /dev/null +++ b/OctopusProjectBuilder.Model/RunbookProcess.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OctopusProjectBuilder.Model +{ + public class RunbookProcess + { + public IEnumerable DeploymentSteps { get; } + + public RunbookProcess(IEnumerable deploymentSteps) + { + DeploymentSteps = deploymentSteps.ToArray(); + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/RunbookRetentionPeriod.cs b/OctopusProjectBuilder.Model/RunbookRetentionPeriod.cs new file mode 100644 index 0000000..1a85675 --- /dev/null +++ b/OctopusProjectBuilder.Model/RunbookRetentionPeriod.cs @@ -0,0 +1,9 @@ +namespace OctopusProjectBuilder.Model +{ + public class RunbookRetentionPeriod + { + public int QuantityToKeep { get; set; } + + public bool ShouldKeepForever { get; set; } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/SkipMachineBehavior.cs b/OctopusProjectBuilder.Model/SkipMachineBehavior.cs new file mode 100644 index 0000000..ac9b8cd --- /dev/null +++ b/OctopusProjectBuilder.Model/SkipMachineBehavior.cs @@ -0,0 +1,8 @@ +namespace OctopusProjectBuilder.Model +{ + public enum SkipMachineBehavior + { + None, + SkipUnavailableMachines, + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Model/SystemModel.cs b/OctopusProjectBuilder.Model/SystemModel.cs index 114e3bb..ae4f4a4 100644 --- a/OctopusProjectBuilder.Model/SystemModel.cs +++ b/OctopusProjectBuilder.Model/SystemModel.cs @@ -11,12 +11,14 @@ public class SystemModel public IEnumerable Lifecycles { get; } public IEnumerable ProjectGroups { get; } public IEnumerable Projects { get; } + public IEnumerable Channels { get; } public IEnumerable UserRoles { get; } public IEnumerable Teams { get; } public IEnumerable Tenants { get; } public IEnumerable TagSets { get; } + public IEnumerable Runbooks { get; } - public SystemModel(IEnumerable machinePolicies, IEnumerable lifecycles, IEnumerable projectGroups, IEnumerable libraryVariableSets, IEnumerable projects, IEnumerable environments, IEnumerable userRoles, IEnumerable teams, IEnumerable tenants, IEnumerable tagSets) + public SystemModel(IEnumerable machinePolicies, IEnumerable lifecycles, IEnumerable projectGroups, IEnumerable libraryVariableSets, IEnumerable projects, IEnumerable channels, IEnumerable environments, IEnumerable userRoles, IEnumerable teams, IEnumerable tenants, IEnumerable tagSets, IEnumerable runbooks) { MachinePolicies = machinePolicies.OrderBy(s => s.Identifier.Name).ToArray(); Environments = environments.OrderBy(s => s.Identifier.Name).ToArray(); @@ -24,24 +26,28 @@ public SystemModel(IEnumerable machinePolicies, IEnumerable s.Identifier.Name).ToArray(); ProjectGroups = projectGroups.OrderBy(s => s.Identifier.Name).ToArray(); Projects = projects.OrderBy(s => s.Identifier.Name).ToArray(); + Channels = channels.OrderBy(s => s.Identifier.Name).ToArray(); UserRoles = userRoles.OrderBy(s => s.Identifier.Name).ToArray(); Teams = teams.OrderBy(s => s.Identifier.Name).ToArray(); Tenants = tenants.OrderBy(s => s.Identifier.Name).ToArray(); TagSets = tagSets.OrderBy(s => s.Identifier.Name).ToArray(); + Runbooks = runbooks.OrderBy(s => s.Identifier.Name).ToArray(); } public IEnumerable SplitModel() { - return MachinePolicies.Select(t => new SystemModel(Enumerable.Repeat(t, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty())) - .Concat(Environments.Select(e => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(e, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) - .Concat(ProjectGroups.Select(grp => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(grp, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) - .Concat(Projects.Select(prj => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(prj, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) - .Concat(Lifecycles.Select(lf => new SystemModel(Enumerable.Empty(), Enumerable.Repeat(lf, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) - .Concat(LibraryVariableSets.Select(lvs => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(lvs, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) - .Concat(UserRoles.Select(ur => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(ur, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) - .Concat(Teams.Select(t => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(t, 1), Enumerable.Empty(), Enumerable.Empty()))) - .Concat(Tenants.Select(t => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(t, 1), Enumerable.Empty()))) - .Concat(TagSets.Select(t => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(t, 1)))); + return MachinePolicies.Select(t => new SystemModel(Enumerable.Repeat(t, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty())) + .Concat(Environments.Select(e => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(e, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) + .Concat(ProjectGroups.Select(grp => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(grp, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) + .Concat(Projects.Select(prj => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(prj, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) + .Concat(Channels.Select(ch => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(ch, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) + .Concat(Lifecycles.Select(lf => new SystemModel(Enumerable.Empty(), Enumerable.Repeat(lf, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) + .Concat(LibraryVariableSets.Select(lvs => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(lvs, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) + .Concat(UserRoles.Select(ur => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(ur, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) + .Concat(Teams.Select(t => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(t, 1), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))) + .Concat(Tenants.Select(t => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(t, 1), Enumerable.Empty(), Enumerable.Empty()))) + .Concat(TagSets.Select(t => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(t, 1), Enumerable.Empty()))) + .Concat(Runbooks.Select(t => new SystemModel(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Repeat(t, 1)))); } } } diff --git a/OctopusProjectBuilder.Model/SystemModelBuilder.cs b/OctopusProjectBuilder.Model/SystemModelBuilder.cs index 2b1d2e2..dc594b9 100644 --- a/OctopusProjectBuilder.Model/SystemModelBuilder.cs +++ b/OctopusProjectBuilder.Model/SystemModelBuilder.cs @@ -8,12 +8,14 @@ public class SystemModelBuilder private readonly List _environments = new List(); private readonly List _projectGroups = new List(); private readonly List _projects = new List(); + private readonly List _channels = new List(); private readonly List _lifecycles = new List(); private readonly List _libraryVariableSets = new List(); private readonly List _userRoles = new List(); private readonly List _teams = new List(); private readonly List _tenants = new List(); private readonly List _tagSets = new List(); + private readonly List _runbooks = new List(); public SystemModelBuilder AddProjectGroup(ProjectGroup group) { @@ -33,6 +35,12 @@ public SystemModelBuilder AddProject(Project project) return this; } + public SystemModelBuilder AddChannel(Channel channel) + { + _channels.Add(channel); + return this; + } + public SystemModelBuilder AddLifecycle(Lifecycle lifecycle) { _lifecycles.Add(lifecycle); @@ -75,6 +83,12 @@ public SystemModelBuilder AddTagSet(TagSet tagSet) return this; } + public SystemModelBuilder AddRunbook(Runbook runbook) + { + _runbooks.Add(runbook); + return this; + } + public SystemModel Build() { return new SystemModel( @@ -83,11 +97,13 @@ public SystemModel Build() _projectGroups, _libraryVariableSets, _projects, + _channels, _environments, _userRoles, _teams, _tenants, - _tagSets); + _tagSets, + _runbooks); } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeTeamsRepository.cs b/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeTeamsRepository.cs deleted file mode 100644 index cb6dd2b..0000000 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeTeamsRepository.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Octopus.Client.Model; -using Octopus.Client.Repositories.Async; - -namespace OctopusProjectBuilder.Uploader.Tests.Helpers -{ - internal class FakeTeamsRepository : FakeNamedRepository, ITeamsRepository - { - } -} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeVariableSetRepository.cs b/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeVariableSetRepository.cs deleted file mode 100644 index 4705827..0000000 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeVariableSetRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Threading.Tasks; -using Octopus.Client.Model; -using Octopus.Client.Repositories.Async; - -namespace OctopusProjectBuilder.Uploader.Tests.Helpers -{ - internal class FakeVariableSetRepository : FakeRepository, IVariableSetRepository - { - public Task GetVariableNames(string projects, string[] environments) - { - throw new System.NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/ModelDownloaderTests.cs b/OctopusProjectBuilder.Uploader.Tests/ModelDownloaderTests.cs index 37afd1f..5b27fa8 100644 --- a/OctopusProjectBuilder.Uploader.Tests/ModelDownloaderTests.cs +++ b/OctopusProjectBuilder.Uploader.Tests/ModelDownloaderTests.cs @@ -7,7 +7,7 @@ using Octopus.Client.Model; using OctopusProjectBuilder.Model; using OctopusProjectBuilder.TestUtils; -using OctopusProjectBuilder.Uploader.Tests.Helpers; +using OctopusProjectBuilder.Uploader; using Environment = OctopusProjectBuilder.Model.Environment; using Permission = OctopusProjectBuilder.Model.Permission; using TenantedDeploymentMode = OctopusProjectBuilder.Model.TenantedDeploymentMode; @@ -157,7 +157,8 @@ public void It_should_rename_project() Enumerable.Empty(), new ElementReference("lifecycle1"), new ElementReference("group1"), null, Enumerable.Empty(), - TenantedDeploymentMode.TenantedOrUntenanted)) + TenantedDeploymentMode.TenantedOrUntenanted, + Enumerable.Empty())) .Build(); _repository.Lifecycles.Create(new LifecycleResource { Name = "lifecycle1" }); @@ -174,7 +175,8 @@ public void It_should_rename_project() Enumerable.Empty(), new ElementReference("lifecycle1"), new ElementReference("group1"), null, Enumerable.Empty(), - TenantedDeploymentMode.TenantedOrUntenanted)) + TenantedDeploymentMode.TenantedOrUntenanted, + Enumerable.Empty())) .Build(); _uploader.UploadModel(model2).GetAwaiter(); @@ -204,22 +206,28 @@ public void It_should_upload_and_download_projects() { new DeploymentStep(CreateItem(), CreateItem(), CreateItem(), CreateItem(), - CreateItem>(), new[] + CreateItem>(), new[] { - new DeploymentAction(CreateItem(), CreateItem(), - CreateItem>(), - new[] {new ElementReference("env1")}), - new DeploymentAction(CreateItem(), CreateItem(), - CreateItem>(), - new[] {new ElementReference("env2")}) + new DeploymentAction(CreateItem(), CreateItem(), + CreateItem(), CreateItem(), + CreateItem>(), + new[] {new ElementReference("env1")}, + Enumerable.Empty()), + new DeploymentAction(CreateItem(), CreateItem(), + CreateItem(), CreateItem(), + CreateItem>(), + new[] {new ElementReference("env2")}, + Enumerable.Empty()) }), new DeploymentStep(CreateItem(), CreateItem(), CreateItem(), CreateItem(), - CreateItem>(), new[] + CreateItem>(), new[] { - new DeploymentAction(CreateItem(), CreateItem(), - CreateItem>(), - new[] {new ElementReference("env1")}) + new DeploymentAction(CreateItem(), CreateItem(), + CreateItem(), CreateItem(), + CreateItem>(), + new[] {new ElementReference("env1")}, + Enumerable.Empty()) }) }); var scope = new Dictionary> @@ -248,13 +256,13 @@ public void It_should_upload_and_download_projects() new[] { new ElementReference(libraryVariableSet.Identifier.Name) }, new ElementReference(lifecycle.Identifier.Name), new ElementReference(projectGroup.Identifier.Name), CreateItem(), Enumerable.Empty(), - TenantedDeploymentMode.TenantedOrUntenanted); + TenantedDeploymentMode.TenantedOrUntenanted, Enumerable.Empty()); var project2 = new Project(CreateItemWithRename(false), CreateItem(), CreateItem(), CreateItem(), CreateItem(), deploymentProcess, variables, new[] { new ElementReference(libraryVariableSet.Identifier.Name) }, new ElementReference(lifecycle.Identifier.Name), new ElementReference(projectGroup.Identifier.Name), null, new[] { CreateProjectTrigger("m1", "env1"), CreateProjectTrigger("m2", "env2") }, - TenantedDeploymentMode.TenantedOrUntenanted); + TenantedDeploymentMode.TenantedOrUntenanted, Enumerable.Empty()); var expected = new SystemModelBuilder() .AddProject(project1) @@ -532,11 +540,13 @@ public void It_should_upload_and_download_tenant() { new DeploymentStep(CreateItem(), CreateItem(), CreateItem(), CreateItem(), - CreateItem>(), new[] + CreateItem>(), new[] { - new DeploymentAction(CreateItem(), CreateItem(), - CreateItem>(), - new[] {new ElementReference("env1")}), + new DeploymentAction(CreateItem(), CreateItem(), + CreateItem(), CreateItem(), + CreateItem>(), + new[] {new ElementReference("env1")}, + Enumerable.Empty()), }) }); var scope = new Dictionary> @@ -556,7 +566,7 @@ public void It_should_upload_and_download_tenant() new[] { new ElementReference(libraryVariableSet.Identifier.Name) }, new ElementReference(lifecycle.Identifier.Name), new ElementReference(projectGroup.Identifier.Name), CreateItem(), Enumerable.Empty(), - TenantedDeploymentMode.TenantedOrUntenanted); + TenantedDeploymentMode.TenantedOrUntenanted, Enumerable.Empty()); var tenant = new Tenant(new ElementIdentifier("t1"), new[] { new ElementReference(tagset.Identifier.Name) }, @@ -596,7 +606,7 @@ public void It_should_upload_and_download_teams() Enumerable.Empty(), new ElementReference("lifecycle1"), new ElementReference("group1"), null, Enumerable.Empty(), - TenantedDeploymentMode.TenantedOrUntenanted); + TenantedDeploymentMode.TenantedOrUntenanted, Enumerable.Empty()); var environment1 = new Environment(new ElementIdentifier("env1"), CreateItem()); var team = new Team( diff --git a/OctopusProjectBuilder.Uploader.Tests/ModelUploaderValidationTests.cs b/OctopusProjectBuilder.Uploader.Tests/ModelUploaderValidationTests.cs index 77196af..fa7a2d2 100644 --- a/OctopusProjectBuilder.Uploader.Tests/ModelUploaderValidationTests.cs +++ b/OctopusProjectBuilder.Uploader.Tests/ModelUploaderValidationTests.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using Octopus.Client.Model; using OctopusProjectBuilder.Model; -using OctopusProjectBuilder.Uploader.Tests.Helpers; +using OctopusProjectBuilder.Uploader; using TenantedDeploymentMode = OctopusProjectBuilder.Model.TenantedDeploymentMode; namespace OctopusProjectBuilder.Uploader.Tests @@ -63,7 +63,7 @@ public void Uploader_should_throw_meaningful_exception_if_cannot_find_project_gr private static SystemModel CreateProjectModel(string lifecycleRef, string projectGroupRef, params Variable[] variables) { var deploymentProcess = new DeploymentProcess(new DeploymentStep[0]); - var project = new Project(new ElementIdentifier("prj"), string.Empty, false, false, false, deploymentProcess, variables, new ElementReference[0], new ElementReference(lifecycleRef), new ElementReference(projectGroupRef), null, Enumerable.Empty(), TenantedDeploymentMode.TenantedOrUntenanted); + var project = new Project(new ElementIdentifier("prj"), string.Empty, false, false, false, deploymentProcess, variables, new ElementReference[0], new ElementReference(lifecycleRef), new ElementReference(projectGroupRef), null, Enumerable.Empty(), TenantedDeploymentMode.TenantedOrUntenanted, Enumerable.Empty()); return new SystemModelBuilder().AddProject(project).Build(); } diff --git a/OctopusProjectBuilder.Uploader.Tests/OctopusProjectBuilder.Uploader.Tests.csproj b/OctopusProjectBuilder.Uploader.Tests/OctopusProjectBuilder.Uploader.Tests.csproj index b3e8354..fac2779 100644 --- a/OctopusProjectBuilder.Uploader.Tests/OctopusProjectBuilder.Uploader.Tests.csproj +++ b/OctopusProjectBuilder.Uploader.Tests/OctopusProjectBuilder.Uploader.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/OctopusProjectBuilder.Uploader/Converters/ChannelConverter.cs b/OctopusProjectBuilder.Uploader/Converters/ChannelConverter.cs new file mode 100644 index 0000000..2276227 --- /dev/null +++ b/OctopusProjectBuilder.Uploader/Converters/ChannelConverter.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; +using TenantedDeploymentMode = Octopus.Client.Model.TenantedDeploymentMode; + +namespace OctopusProjectBuilder.Uploader.Converters +{ + public static class ChannelConverter + { + + public static async Task UpdateWith(this ChannelResource resource, Channel model, IOctopusAsyncRepository repository) + { + if (model.Identifier != null) + { + resource.Name = model.Identifier.Name; + } + + resource.IsDefault = model.IsDefault ?? false; + if (model.LifecycleName != null) + { + resource.LifecycleId = (await repository.Lifecycles.FindByName(model.LifecycleName)).Id; + } + else + { + resource.LifecycleId = String.Empty; + } + + resource.ProjectId = (await repository.Projects.FindByName(model.ProjectName)).Id; + + resource.Rules = (await Task.WhenAll(model.VersionRules.Select(rule => rule.ToResource()))).ToList(); + + resource.TenantTags = new ReferenceCollection(model.TenantTags.Select(x => x.Name)); + + return resource; + } + + public static async Task ToModel(this ChannelResource resource, IOctopusAsyncRepository repository) + { + var projectResource = await repository.Projects.Get(resource.ProjectId); + var lifecycleName = resource.LifecycleId != null ? + (await repository.Lifecycles.Get(resource.LifecycleId)).Name : null; + + return new Channel( + new ElementIdentifier(resource.Name), + resource.Description, + projectResource.Name, + resource.IsDefault, + lifecycleName, + await Task.WhenAll(resource.Rules.Select(rule => rule.ToModel(repository))), + resource.TenantTags.Select(x => new ElementReference(x)).ToArray()); + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader/Converters/ChannelVersionRuleConverter.cs b/OctopusProjectBuilder.Uploader/Converters/ChannelVersionRuleConverter.cs new file mode 100644 index 0000000..7a0719f --- /dev/null +++ b/OctopusProjectBuilder.Uploader/Converters/ChannelVersionRuleConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; +using TenantedDeploymentMode = Octopus.Client.Model.TenantedDeploymentMode; + +namespace OctopusProjectBuilder.Uploader.Converters +{ + public static class ChannelVersionRuleConverter + { + public static async Task ToResource(this ChannelVersionRule model) + { + return new ChannelVersionRuleResource() + { + Tag = model.Tag ?? "", + VersionRange = model.VersionRange ?? "", + ActionPackages = await Task.WhenAll(model.ActionPackages.Select(package => package.ToResource())) + }; + } + + public static async Task ToModel(this ChannelVersionRuleResource resource, IOctopusAsyncRepository repository) + { + return new ChannelVersionRule( + resource.Tag, + resource.VersionRange, + await Task.WhenAll(resource.ActionPackages.Select(package => package.ToModel(repository)))); + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader/Converters/ChannelVersionRulePackageConveter.cs b/OctopusProjectBuilder.Uploader/Converters/ChannelVersionRulePackageConveter.cs new file mode 100644 index 0000000..7d14132 --- /dev/null +++ b/OctopusProjectBuilder.Uploader/Converters/ChannelVersionRulePackageConveter.cs @@ -0,0 +1,21 @@ +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; + +namespace OctopusProjectBuilder.Uploader.Converters +{ + public static class ChannelVersionRulePackageConveter + { + public static async Task ToModel(this DeploymentActionPackageResource resource, IOctopusAsyncRepository repository) + { + return new ChannelVersionRulePackage(resource.DeploymentAction, resource.PackageReference); + } + + public static async Task ToResource(this ChannelVersionRulePackage model) + { + return new DeploymentActionPackageResource(model.DeploymentAction ?? "", model.PackageReference ?? ""); + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader/Converters/DeploymentActionConverter.cs b/OctopusProjectBuilder.Uploader/Converters/DeploymentActionConverter.cs index f0384ad..bfcffb7 100644 --- a/OctopusProjectBuilder.Uploader/Converters/DeploymentActionConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/DeploymentActionConverter.cs @@ -1,6 +1,11 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; using System.Linq; using System.Threading.Tasks; +using Newtonsoft.Json; using Octopus.Client; +using Octopus.Client.Exceptions; using Octopus.Client.Model; using OctopusProjectBuilder.Model; @@ -12,17 +17,82 @@ public static async Task ToModel(this DeploymentActionResource { return new DeploymentAction( resource.Name, + resource.IsDisabled, + (DeploymentAction.ActionCondition)resource.Condition, resource.ActionType, resource.Properties.ToModel(), - await Task.WhenAll(resource.Environments.ToModel(repository.Environments))); + await Task.WhenAll(resource.Environments.ToModel(repository.Environments)), + await Task.WhenAll(resource.Packages.Select(reference => reference.ToModel()))); } - public static async Task UpdateWith(this DeploymentActionResource resource, DeploymentAction model, IOctopusAsyncRepository repository) + public static async Task UpdateWith(this DeploymentActionResource resource, + DeploymentAction model, IOctopusAsyncRepository repository, + DeploymentActionResource oldAction) { resource.Name = model.Name; + resource.IsDisabled = model.IsDisabled; + resource.Condition = (DeploymentActionCondition) model.Condition; resource.ActionType = model.ActionType; - resource.Properties.UpdateWith(model.Properties); - resource.Environments.UpdateWith(await Task.WhenAll(model.EnvironmentRefs.Select(r => repository.Environments.ResolveResourceId(r)))); + resource.Environments.UpdateWith(await Task.WhenAll(model.EnvironmentRefs + .Select(r => repository.Environments.ResolveResourceId(r)))); + + List newReferences = new List(); + foreach (var reference in model.Packages) + { + PackageReference oldReference = resource.Packages.FirstOrDefault(x => x.Name == model.Name); + newReferences.Add(await new PackageReference().UpdateWith(reference, repository, oldReference)); + } + + // Replace package references + IDictionary properties = + model.Properties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + resource.Packages.Clear(); + foreach (PackageReference reference in newReferences) + { + resource.Packages.Add(reference); + + // Attempt to auto-generate the property for this if it's not supplied + if (reference.Properties.ContainsKey("PackageParameterName")) + { + string parameterName = reference.Properties["PackageParameterName"]; + if (!model.Properties.ContainsKey(parameterName)) + { + properties.Add(parameterName, new PropertyValue(false, JsonConvert.SerializeObject(new + {reference.PackageId, reference.FeedId}))); + } + } + } + + if (resource.Packages.Any()) + { + resource.CanBeUsedForProjectVersioning = true; + } + + await resource.Properties.UpdateWith(repository, new ReadOnlyDictionary(properties), + oldAction != null ? oldAction.Properties : new Dictionary()); + + switch (resource.ActionType) + { + case "Octopus.TentaclePackage": + if (!resource.Properties.ContainsKey("Octopus.Action.Package.PackageId")) + { + throw new ConstraintException("No package ID specified for package action" + resource.Name); + } + break; + case "Octopus.Script": + if (!resource.Properties.ContainsKey("Octopus.Action.Script.ScriptBody")) + { + throw new ConstraintException("No script body specified for script action in " + resource.Name); + } + break; + case "Octopus.DeployRelease": + if (!resource.Properties.ContainsKey("Octopus.Action.DeployRelease.ProjectId")) + { + throw new ConstraintException("No project ID specified for release action in " + resource.Name); + } + break; + } + return resource; } } diff --git a/OctopusProjectBuilder.Uploader/Converters/DeploymentActionPackageConverter.cs b/OctopusProjectBuilder.Uploader/Converters/DeploymentActionPackageConverter.cs new file mode 100644 index 0000000..002eb84 --- /dev/null +++ b/OctopusProjectBuilder.Uploader/Converters/DeploymentActionPackageConverter.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client; +using Octopus.Client.Exceptions; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; + +namespace OctopusProjectBuilder.Uploader.Converters +{ + public static class DeploymentActionPackageConverter + { + public static async Task ToModel(this PackageReference resource) + { + return new DeploymentActionPackage() + { + Name = resource.Name, + PackageId = resource.PackageId, + FeedId = resource.FeedId, + AcquisitionLocation = resource.AcquisitionLocation, + Properties = resource.Properties + }; + } + + public static async Task UpdateWith(this PackageReference resource, + DeploymentActionPackage model, IOctopusAsyncRepository repository, + PackageReference oldReference) + { + resource.Name = model.Name; + resource.PackageId = model.PackageId; + resource.FeedId = model.FeedId; + resource.AcquisitionLocation = model.AcquisitionLocation; + resource.Properties.Clear(); + foreach (var kvp in model.Properties) + { + resource.Properties.Add(kvp.Key, kvp.Value); + } + return resource; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader/Converters/DeploymentConnectivityPolicyConverter.cs b/OctopusProjectBuilder.Uploader/Converters/DeploymentConnectivityPolicyConverter.cs new file mode 100644 index 0000000..683d2d7 --- /dev/null +++ b/OctopusProjectBuilder.Uploader/Converters/DeploymentConnectivityPolicyConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; +using DeploymentConnectivityPolicy = Octopus.Client.Model.DeploymentConnectivityPolicy; +using SkipMachineBehavior = Octopus.Client.Model.SkipMachineBehavior; + +namespace OctopusProjectBuilder.Uploader.Converters +{ + public static class DeploymentConnectivityPolicyConverter + { + + public static async Task UpdateWith(this DeploymentConnectivityPolicy resource, + Model.DeploymentConnectivityPolicy model, IOctopusAsyncRepository repository) + { + resource.TargetRoles = new ReferenceCollection(model.TargetRoles.Select(x => x.Name)); + resource.ExcludeUnhealthyTargets = model.ExcludeUnhealthyTargets; + resource.SkipMachineBehavior = (SkipMachineBehavior) model.SkipMachineBehavior; + resource.AllowDeploymentsToNoTargets = model.AllowDeploymentsToNoTargets; + + return resource; + } + + public static async Task ToModel + (this DeploymentConnectivityPolicy resource, IOctopusAsyncRepository repository) + { + return new Model.DeploymentConnectivityPolicy + { + TargetRoles = resource.TargetRoles.Select(x => new ElementReference(x)).ToArray(), + ExcludeUnhealthyTargets = resource.ExcludeUnhealthyTargets, + SkipMachineBehavior = resource.SkipMachineBehavior, + AllowDeploymentsToNoTargets = resource.AllowDeploymentsToNoTargets + }; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader/Converters/DeploymentProcessConverter.cs b/OctopusProjectBuilder.Uploader/Converters/DeploymentProcessConverter.cs index bde4d1b..dbbdc17 100644 --- a/OctopusProjectBuilder.Uploader/Converters/DeploymentProcessConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/DeploymentProcessConverter.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Octopus.Client; @@ -15,10 +16,25 @@ public static async Task ToModel(this DeploymentProcessResour public static async Task UpdateWith(this DeploymentProcessResource resource, DeploymentProcess model, IOctopusAsyncRepository repository) { - resource.Steps.Clear(); - foreach (var step in model.DeploymentSteps.Select(s => new DeploymentStepResource().UpdateWith(s, repository))) - resource.Steps.Add(await step); + if (model == null) + { + return resource; + } + + List newSteps = new List(); + foreach (var step in model.DeploymentSteps) + { + DeploymentStepResource oldStep = resource.FindStep(step.Name); + newSteps.Add(await new DeploymentStepResource().UpdateWith(step, repository, oldStep)); + } + // Replace deployment steps + resource.Steps.Clear(); + foreach (DeploymentStepResource deploymentStep in newSteps) + { + resource.Steps.Add(deploymentStep); + } + return resource; } } diff --git a/OctopusProjectBuilder.Uploader/Converters/DeploymentStepConverter.cs b/OctopusProjectBuilder.Uploader/Converters/DeploymentStepConverter.cs index 8dc3430..ba86128 100644 --- a/OctopusProjectBuilder.Uploader/Converters/DeploymentStepConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/DeploymentStepConverter.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Octopus.Client; @@ -19,16 +20,37 @@ public static async Task ToModel(this DeploymentStepResource res await Task.WhenAll(resource.Actions.Select(a => a.ToModel(repository)))); } - public static async Task UpdateWith(this DeploymentStepResource resource, DeploymentStep model, IOctopusAsyncRepository repository) + public static async Task UpdateWith(this DeploymentStepResource resource, + DeploymentStep model, IOctopusAsyncRepository repository, DeploymentStepResource oldStep) { + // Preserve the old Id + if (oldStep != null) + { + resource.Id = oldStep.Id; + } + resource.Name = model.Name; resource.Condition = (DeploymentStepCondition)model.Condition; resource.RequiresPackagesToBeAcquired = model.RequiresPackagesToBeAcquired; resource.StartTrigger = (DeploymentStepStartTrigger)model.StartTrigger; - resource.Properties.UpdateWith(model.Properties); + PropertyValueConverter.UpdateWith(resource.Properties, repository, model.Properties, + oldStep != null ? oldStep.Properties : new Dictionary()); + resource.Actions.Clear(); - foreach (var action in model.Actions.Select(a => new DeploymentActionResource().UpdateWith(a, repository))) - resource.Actions.Add(await action); + foreach (var action in model.Actions) + { + DeploymentActionResource oldAction; + if (oldStep != null) + { + oldAction = oldStep.FindAction(action.Name); + } + else + { + oldAction = null; + } + resource.Actions.Add(await new DeploymentActionResource().UpdateWith(action, repository, oldAction)); + } + return resource; } } diff --git a/OctopusProjectBuilder.Uploader/Converters/MachineHealthCheckPolicyConverter.cs b/OctopusProjectBuilder.Uploader/Converters/MachineHealthCheckPolicyConverter.cs index 1f1ba15..c736c1e 100644 --- a/OctopusProjectBuilder.Uploader/Converters/MachineHealthCheckPolicyConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/MachineHealthCheckPolicyConverter.cs @@ -10,7 +10,7 @@ public static class MachineHealthCheckPolicyConverter public static MachineHealthCheckPolicy ToModel(this Octopus.Client.Model.MachineHealthCheckPolicy resource) { return new MachineHealthCheckPolicy( - resource.HealthCheckInterval, + resource.HealthCheckInterval ?? TimeSpan.Zero, ToScriptPolicy(resource.TentacleEndpointHealthCheckPolicy), ToScriptPolicy(resource.SshEndpointHealthCheckPolicy)); } diff --git a/OctopusProjectBuilder.Uploader/Converters/ProjectConverter.cs b/OctopusProjectBuilder.Uploader/Converters/ProjectConverter.cs index 7c31b08..d627eca 100644 --- a/OctopusProjectBuilder.Uploader/Converters/ProjectConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/ProjectConverter.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Octopus.Client; using Octopus.Client.Model; @@ -9,22 +11,102 @@ namespace OctopusProjectBuilder.Uploader.Converters { public static class ProjectConverter { + public static async Task UpdateWith(this ProjectResource resource, Project model, IOctopusAsyncRepository repository) { - var projectGroupResourceId = await repository.ProjectGroups.ResolveResourceId(model.ProjectGroupRef); - var lifecycleResourceId = await repository.Lifecycles.ResolveResourceId(model.LifecycleRef); - resource.Name = model.Identifier.Name; - resource.AutoCreateRelease = model.AutoCreateRelease; - resource.DefaultToSkipIfAlreadyInstalled = model.DefaultToSkipIfAlreadyInstalled; - resource.Description = model.Description; - resource.IsDisabled = model.IsDisabled; - resource.LifecycleId = lifecycleResourceId; - resource.ProjectGroupId = projectGroupResourceId; - - resource.TenantedDeploymentMode = (TenantedDeploymentMode) model.TenantedDeploymentMode; - resource.IncludedLibraryVariableSetIds = (await Task.WhenAll(model.IncludedLibraryVariableSetRefs.Select(async r => await repository.LibraryVariableSets.ResolveResourceId(r)).ToList())).ToList(); + if (model.Identifier != null) + { + resource.Name = model.Identifier.Name; + } + + if (model.AutoCreateRelease.HasValue) + { + resource.AutoCreateRelease = model.AutoCreateRelease.Value; + } + + if (model.DefaultToSkipIfAlreadyInstalled.HasValue) + { + resource.DefaultToSkipIfAlreadyInstalled = model.DefaultToSkipIfAlreadyInstalled.Value; + } + + if (model.Description != null) + { + resource.Description = model.Description; + } + + if (model.IsDisabled.HasValue) + { + resource.IsDisabled = model.IsDisabled.Value; + } + + if (model.LifecycleRef.Name != null) + { + var lifecycleResourceId = await repository.Lifecycles.ResolveResourceId(model.LifecycleRef); + resource.LifecycleId = lifecycleResourceId; + } + + if (model.ProjectGroupRef.Name != null) + { + var projectGroupResourceId = await repository.ProjectGroups.ResolveResourceId(model.ProjectGroupRef); + resource.ProjectGroupId = projectGroupResourceId; + } + + if (model.Templates != null) + { + List resultingTemplates = new List(); + + // Update matched IDs + var templatesMatchingExistingId = model.Templates + .Where(t => !string.IsNullOrEmpty(t.Id) && resource.Templates.Any(r => r.Id == t.Id)) + .ToArray(); + + resultingTemplates.AddRange(templatesMatchingExistingId); + + // Update matched names + var templatesMatchingExistingName = model.Templates + .Where(t => templatesMatchingExistingId.All(r => r.Id != t.Id)) + .Where(t => !string.IsNullOrEmpty(t.Name) && resource.Templates.Any(r => r.Name == t.Name)) + .Select(t => + { + t.Id = resource.Templates.Where(r => r.Name == t.Name).Select(r => r.Id).First(); + return t; + }) + .ToArray(); + + resultingTemplates.AddRange(templatesMatchingExistingName); + + // Update anything else that's new + var templatesToCreate = model.Templates + .Where(t => resultingTemplates.All(r => r.Id != t.Id && r.Name != t.Name)) + .Select(t => + { + if (string.IsNullOrEmpty(t.Id)) + { + t.Id = Guid.NewGuid().ToString(); + } + + return t; + }) + .ToArray(); + resultingTemplates.AddRange(templatesToCreate); + + resource.Templates = resultingTemplates; + } + + if (model.TenantedDeploymentMode.HasValue) + { + resource.TenantedDeploymentMode = (TenantedDeploymentMode) model.TenantedDeploymentMode.Value; + } + + if (model.IncludedLibraryVariableSetRefs != null) + { + resource.IncludedLibraryVariableSetIds = (await Task.WhenAll(model.IncludedLibraryVariableSetRefs + .Select(async r => await repository.LibraryVariableSets.ResolveResourceId(r)).ToList())).ToList(); + } + if (model.VersioningStrategy != null) resource.VersioningStrategy = (resource.VersioningStrategy ?? new VersioningStrategyResource()).UpdateWith(model.VersioningStrategy); + return resource; } @@ -50,7 +132,8 @@ await variableSetResource.ToModel(deploymentProcessResource, repository), new ElementReference(projectGroupResource.Name), resource.VersioningStrategy?.ToModel(), await Task.WhenAll(triggers.Items.Select(t => t.ToModel(repository))), - (OctopusProjectBuilder.Model.TenantedDeploymentMode)resource.TenantedDeploymentMode); + (OctopusProjectBuilder.Model.TenantedDeploymentMode) resource.TenantedDeploymentMode, + resource.Templates); } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerAutoDeployActionConverter.cs b/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerAutoDeployActionConverter.cs index fba590a..7503b9d 100644 --- a/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerAutoDeployActionConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerAutoDeployActionConverter.cs @@ -7,8 +7,16 @@ public static class ProjectTriggerAutoDeployActionConverter { public static ProjectTriggerAutoDeployAction ToModel(this TriggerActionResource resource) { - var autoDeployActionResource = (AutoDeployActionResource)resource; - return new ProjectTriggerAutoDeployAction(autoDeployActionResource.ShouldRedeployWhenMachineHasBeenDeployedTo); + if (resource is AutoDeployActionResource) + { + var autoDeployActionResource = (AutoDeployActionResource) resource; + return new ProjectTriggerAutoDeployAction(autoDeployActionResource + .ShouldRedeployWhenMachineHasBeenDeployedTo); + } + else + { + return new ProjectTriggerAutoDeployAction(false); + } } public static AutoDeployActionResource FromModel(this ProjectTriggerAutoDeployAction model) diff --git a/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerConverter.cs b/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerConverter.cs index 7f18db0..e24cfa0 100644 --- a/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerConverter.cs @@ -1,6 +1,9 @@ +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Octopus.Client; using Octopus.Client.Model; +using Octopus.Client.Model.Triggers; using OctopusProjectBuilder.Model; namespace OctopusProjectBuilder.Uploader.Converters @@ -9,10 +12,25 @@ public static class ProjectTriggerConverter { public static async Task ToModel(this ProjectTriggerResource resource, IOctopusAsyncRepository repository) { - return new ProjectTrigger( - new ElementIdentifier(resource.Name), - await resource.Filter.ToModel(repository), - resource.Action.ToModel()); + if (resource.Filter is MachineFilterResource) + { + var model = await resource.Filter.ToModel(repository); + return new ProjectTrigger( + new ElementIdentifier(resource.Name), + model, + resource.Action.ToModel()); + } + else + { + return new ProjectTrigger( + new ElementIdentifier(resource.Name), + new ProjectTriggerMachineFilter( + new List(), + new List(), + new List(), + new List()), + resource.Action.ToModel()); + } } public static async Task UpdateWith(this ProjectTriggerResource resource, ProjectTrigger model, string projectResourceId, IOctopusAsyncRepository repository) diff --git a/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerMachineFilterConverter.cs b/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerMachineFilterConverter.cs index 9b459b1..8f02944 100644 --- a/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerMachineFilterConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/ProjectTriggerMachineFilterConverter.cs @@ -11,8 +11,8 @@ public static class ProjectTriggerMachineFilterConverter { public static async Task ToModel(this TriggerFilterResource resource, IOctopusAsyncRepository repository) { - var machineFilterResource = (MachineFilterResource)resource; - + var machineFilterResource = (MachineFilterResource) resource; + var environments = await Task.WhenAll(machineFilterResource.EnvironmentIds.Select(async v => new ElementReference((await repository.Environments.Get(v)).Name))); var roles = machineFilterResource.Roles.Select(v => new ElementReference(v)); var eventGroups = machineFilterResource.EventGroups.Select(v => new ElementReference(v)); diff --git a/OctopusProjectBuilder.Uploader/Converters/PropertyValueConverter.cs b/OctopusProjectBuilder.Uploader/Converters/PropertyValueConverter.cs index 76b5fd5..88c7f6c 100644 --- a/OctopusProjectBuilder.Uploader/Converters/PropertyValueConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/PropertyValueConverter.cs @@ -1,17 +1,59 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Octopus.Client; using Octopus.Client.Model; using OctopusProjectBuilder.Model; +using Environment = System.Environment; namespace OctopusProjectBuilder.Uploader.Converters { public static class PropertyValueConverter { + private static readonly string octopusTemplateId = "Octopus.Action.Template.Id"; + private static readonly string octopusTemplateVersion = "Octopus.Action.Template.Version"; + + delegate bool TestProperty(IDictionary model, + IDictionary oldProperties); + + private static readonly ILogger _logger; + private static readonly IDictionary protectedIds; + + static PropertyValueConverter() + { + protectedIds = new Dictionary(); + + // DO NOT attempt to overwrite template version IDs if not specified. Leave this up to Octopus. + protectedIds.Add(octopusTemplateVersion, (model, oldProperties) => + { + if (oldProperties.ContainsKey(octopusTemplateId) && model.ContainsKey(octopusTemplateId)) + { + // We should only keep a template version if the model doesn't specify a changed + // template ID. If it changed, we shouldn't try to keep the old one. + return oldProperties[octopusTemplateId].Value == model[octopusTemplateId].Value; + } + else + { + // In any other case, there is no reason to preserve this old version value. + return false; + } + }); + } + public static PropertyValue ToModel(this PropertyValueResource resource) { return new PropertyValue(resource.IsSensitive,resource.Value); } + + public static Dictionary ToModel(this IDictionary properties) + { + return properties + .Select(kv => Tuple.Create(kv.Key, ToModel(kv.Value))) + .ToDictionary(kv => kv.Item1, kv => kv.Item2); + } public static Dictionary ToModel(this IDictionary properties) { @@ -20,11 +62,160 @@ public static Dictionary ToModel(this IDictionary kv.Item1, kv => kv.Item2); } - public static void UpdateWith(this IDictionary resource, IReadOnlyDictionary model) + public static async Task UpdateWith(this IDictionary resource, + IReadOnlyDictionary model) { resource.Clear(); foreach (var keyValuePair in model) - resource.Add(keyValuePair.Key, new PropertyValueResource(keyValuePair.Value.Value, keyValuePair.Value.IsSensitive)); + { + resource.Add(keyValuePair.Key, keyValuePair.Value); + } + } + + public static async Task UpdateWith(this IDictionary resource, + IOctopusAsyncRepository repository, + IDictionary model, + IDictionary oldProperties) + { + foreach (var keyValuePair in model) + { + string value = keyValuePair.Value.Value; + + try + { + switch (keyValuePair.Value.ValueType) + { + case "Literal": + case "": + case null: + break; + case "PackageIdFromJsonProperty": + case "FeedIdFromJsonProperty": + string json = model[value].Value; + dynamic package = JsonConvert.DeserializeObject(json); + switch (keyValuePair.Value.ValueType) + { + case "PackageIdFromJsonProperty": + value = package.PackageId; + break; + case "FeedIdFromJsonProperty": + value = package.FeedId; + break; + } + break; + case "ProjectNameToId": + value = (await repository.Projects.FindByName(value)).Id; + break; + case "EnvironmentNameToId": + value = (await repository.Environments.FindByName(value)).Id; + break; + case "StepTemplateNameToId": + value = (await repository.ActionTemplates.FindByName(value)).Id; + break; + case "FeedNameToId": + value = (await repository.Feeds.FindByName(value)).Id; + break; + case "MachineNameToId": + value = (await repository.Machines.FindByName(value)).Id; + break; + case "SpaceNameToId": + value = (await repository.Spaces.FindByName(value)).Id; + break; + case "TenantNameToId": + value = (await repository.Tenants.FindByName(value)).Id; + break; + case "LifecycleNameToId": + value = (await repository.Lifecycles.FindByName(value)).Id; + break; + case "CertificateNameToId": + value = (await repository.Certificates.FindByName(value)).Id; + break; + case "ProxyNameToId": + value = (await repository.Proxies.FindByName(value)).Id; + break; + case "TagSetNameToId": + value = (await repository.TagSets.FindByName(value)).Id; + break; + case "UserRoleNameToId": + value = (await repository.UserRoles.FindByName(value)).Id; + break; + case "RunbookNameToId": + value = (await repository.Runbooks.FindByName(value)).Id; + break; + case "EnvironmentVariable": + value = Environment.GetEnvironmentVariable(value); + break; + default: + throw new ArgumentException("ValueType: " + keyValuePair.Value.ValueType); + } + + + } + catch (Exception exception) + { + throw new ArgumentException("Problem transforming value \"" + keyValuePair.Key + "\" of type \"" + + keyValuePair.Value.ValueType + "\" value=\"" + + keyValuePair.Value.Value + "\"", + exception); + } + + resource.Add(keyValuePair.Key, + new PropertyValueResource(value, keyValuePair.Value.IsSensitive)); + } + + if (oldProperties != null) + { + foreach (var propertyToKeep in oldProperties + .Where(old => protectedIds.ContainsKey(old.Key)) + .Where(old => !resource.ContainsKey(old.Key)) + .Where(old => protectedIds[old.Key].Invoke(resource, oldProperties))) + { + resource.Add(propertyToKeep); + } + } + + PropertyValueResource actionTemplateId = resource + .Where(a => a.Key == octopusTemplateId) + .Select(a => a.Value) + .FirstOrDefault(); + + if (actionTemplateId != null && !string.IsNullOrEmpty(actionTemplateId.Value)) + { + ActionTemplateResource actionTemplate = await repository.ActionTemplates.Get(actionTemplateId.Value); + PropertyValueResource actionTemplateVersion = resource + .Where(a => a.Key == octopusTemplateVersion) + .Select(a => a.Value) + .FirstOrDefault(); + int versionNumber = actionTemplateVersion != null ? + int.Parse(actionTemplateVersion.Value) : actionTemplate.Version; + + if (versionNumber != actionTemplate.Version) + { + if (versionNumber < actionTemplate.Version) + { + Console.Error.WriteLine( + $"An old version of step template {actionTemplate.Name} is being referenced by " + + $"a deployment step! You specified (or were defaulted to) #{versionNumber}, but the lat" + + $"est version of this step template is #{actionTemplate.Version}. Consider upgrading the " + + "version of the step template referenced in this step in Octopus."); + } + + actionTemplate = await repository.ActionTemplates.GetVersion(actionTemplate, versionNumber); + } + + foreach (var keyValuePair in actionTemplate.Properties) + { + if (!resource.ContainsKey(keyValuePair.Key)) + { + resource.Add(keyValuePair); + } + } + + if (!resource.ContainsKey(octopusTemplateVersion)) + { + resource.Add(octopusTemplateVersion, new PropertyValueResource(versionNumber.ToString())); + } + } } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader/Converters/ReferenceCollectionConverter.cs b/OctopusProjectBuilder.Uploader/Converters/ReferenceCollectionConverter.cs index 7bded67..c04a4c3 100644 --- a/OctopusProjectBuilder.Uploader/Converters/ReferenceCollectionConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/ReferenceCollectionConverter.cs @@ -25,7 +25,7 @@ public static IEnumerable> ToModel(this Refere return new ElementReference(resource.Name); }); } - + public static void UpdateWith(this IDictionary resource, IReadOnlyDictionary> model) { resource.Clear(); diff --git a/OctopusProjectBuilder.Uploader/Converters/RunbookConverter.cs b/OctopusProjectBuilder.Uploader/Converters/RunbookConverter.cs new file mode 100644 index 0000000..e8f2114 --- /dev/null +++ b/OctopusProjectBuilder.Uploader/Converters/RunbookConverter.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; +using GuidedFailureMode = Octopus.Client.Model.GuidedFailureMode; +using RunbookEnvironmentScope = Octopus.Client.Model.RunbookEnvironmentScope; +using TenantedDeploymentMode = Octopus.Client.Model.TenantedDeploymentMode; + +namespace OctopusProjectBuilder.Uploader.Converters +{ + public static class RunbookConverter + { + + public static async Task UpdateWith(this RunbookResource resource, Runbook model, + IOctopusAsyncRepository repository) + { + if (model.Identifier != null) + { + resource.Name = model.Identifier.Name; + } + + resource.Description = model.Description; + + resource.ProjectId = (await repository.Projects.FindByName(model.ProjectName)).Id; + + resource.Environments.UpdateWith(await Task.WhenAll(model.EnvironmentRefs + .Select(r => repository.Environments.ResolveResourceId(r)))); + + if (model.EnvironmentScope.HasValue) + resource.EnvironmentScope = (RunbookEnvironmentScope) model.EnvironmentScope.Value; + + if (model.MultiTenancyMode.HasValue) + resource.MultiTenancyMode = (TenantedDeploymentMode) model.MultiTenancyMode.Value; + + if (model.RunRetentionPolicy != null) + resource.RunRetentionPolicy = model.RunRetentionPolicy.FromModel(); + + if (model.GuidedFailureMode != null) + resource.DefaultGuidedFailureMode = (GuidedFailureMode) model.GuidedFailureMode; + + if (model.ConnectivityPolicy != null) + await resource.ConnectivityPolicy.UpdateWith(model.ConnectivityPolicy, repository); + + return resource; + } + + public static async Task ToModel(this RunbookResource resource, IOctopusAsyncRepository repository) + { + var projectResource = await repository.Projects.Get(resource.ProjectId); + var runbookProcess = await repository.RunbookProcesses.Get(resource.RunbookProcessId); + + return new Runbook() + { + Description = resource.Description, + EnvironmentScope = (Model.RunbookEnvironmentScope?) resource.EnvironmentScope, + Identifier = new ElementIdentifier(resource.Name), + MultiTenancyMode = (Model.TenantedDeploymentMode?) resource.MultiTenancyMode, + ProjectName = projectResource.Name, + RunRetentionPolicy = resource.RunRetentionPolicy.ToModel(), + Process = await runbookProcess.ToModel(repository), + GuidedFailureMode = (Model.GuidedFailureMode?) resource.DefaultGuidedFailureMode, + EnvironmentRefs = await Task.WhenAll(resource.Environments.ToModel(repository.Environments)) + }; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader/Converters/RunbookProcessConverter.cs b/OctopusProjectBuilder.Uploader/Converters/RunbookProcessConverter.cs new file mode 100644 index 0000000..f5c9687 --- /dev/null +++ b/OctopusProjectBuilder.Uploader/Converters/RunbookProcessConverter.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; + +namespace OctopusProjectBuilder.Uploader.Converters +{ + public static class RunbookProcessConverter + { + public static async Task ToModel(this RunbookProcessResource resource, IOctopusAsyncRepository repository) + { + return new RunbookProcess(await Task.WhenAll(resource.Steps.Select(s => s.ToModel(repository)))); + } + + public static async Task UpdateWith(this RunbookProcessResource resource, RunbookProcess model, IOctopusAsyncRepository repository) + { + if (model == null) + { + return resource; + } + + List newSteps = new List(); + foreach (var step in model.DeploymentSteps) + { + DeploymentStepResource oldStep = resource.FindStep(step.Name); + newSteps.Add(await new DeploymentStepResource().UpdateWith(step, repository, oldStep)); + } + + // Replace deployment steps + resource.Steps.Clear(); + foreach (DeploymentStepResource deploymentStep in newSteps) + { + resource.Steps.Add(deploymentStep); + } + + return resource; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader/Converters/RunbookRetentionPeriodConverter.cs b/OctopusProjectBuilder.Uploader/Converters/RunbookRetentionPeriodConverter.cs new file mode 100644 index 0000000..4da8538 --- /dev/null +++ b/OctopusProjectBuilder.Uploader/Converters/RunbookRetentionPeriodConverter.cs @@ -0,0 +1,26 @@ +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; +using RunbookRetentionPeriod = OctopusProjectBuilder.Model.RunbookRetentionPeriod; + +namespace OctopusProjectBuilder.Uploader.Converters +{ + public static class RunbookRetentionPeriodConverter { + public static RunbookRetentionPeriod ToModel(this Octopus.Client.Model.RunbookRetentionPeriod period) + { + return new RunbookRetentionPeriod() + { + ShouldKeepForever = period.ShouldKeepForever, + QuantityToKeep = period.QuantityToKeep + }; + } + + public static Octopus.Client.Model.RunbookRetentionPeriod FromModel(this RunbookRetentionPeriod model) + { + return new Octopus.Client.Model.RunbookRetentionPeriod() + { + ShouldKeepForever = model.ShouldKeepForever, + QuantityToKeep = model.QuantityToKeep + }; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader/Converters/ScopeSpecificationConverter.cs b/OctopusProjectBuilder.Uploader/Converters/ScopeSpecificationConverter.cs index 81ac137..27668f6 100644 --- a/OctopusProjectBuilder.Uploader/Converters/ScopeSpecificationConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/ScopeSpecificationConverter.cs @@ -70,7 +70,7 @@ private static async Task ResolveReference(ScopeField key, str case ScopeField.TenantTag: return new ElementReference(id); default: - throw new InvalidOperationException($"Unsupported ScopeField: {key}"); + return new ElementReference(id); } } diff --git a/OctopusProjectBuilder.Uploader/Converters/TeamConverter.cs b/OctopusProjectBuilder.Uploader/Converters/TeamConverter.cs index 28b031d..235fd5c 100644 --- a/OctopusProjectBuilder.Uploader/Converters/TeamConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/TeamConverter.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Octopus.Client; using Octopus.Client.Model; @@ -15,9 +16,9 @@ public static async Task UpdateWith(this TeamResource resource, Te resource.ExternalSecurityGroups = new NamedReferenceItemCollection(); foreach (var esg in model.ExternalSecurityGroups) resource.ExternalSecurityGroups.Add(new NamedReferenceItem { Id = esg }); - resource.UserRoleIds = new ReferenceCollection(await Task.WhenAll(model.UserRoles.Select(async ur => await repository.UserRoles.ResolveResourceId(ur)))); - resource.ProjectIds = new ReferenceCollection(await Task.WhenAll(model.Projects.Select(async p => await repository.Projects.ResolveResourceId(p)))); - resource.EnvironmentIds = new ReferenceCollection(await Task.WhenAll(model.Environments.Select(async e => await repository.Environments.ResolveResourceId(e)))); + //resource.UserRoleIds = new ReferenceCollection(await Task.WhenAll(model.UserRoles.Select(async ur => await repository.UserRoles.ResolveResourceId(ur)))); + //resource.ProjectIds = new ReferenceCollection(await Task.WhenAll(model.Projects.Select(async p => await repository.Projects.ResolveResourceId(p)))); + //resource.EnvironmentIds = new ReferenceCollection(await Task.WhenAll(model.Environments.Select(async e => await repository.Environments.ResolveResourceId(e)))); return resource; } @@ -27,9 +28,9 @@ public static async Task ToModel(this TeamResource resource, IOctopusAsync new ElementIdentifier(resource.Name), await Task.WhenAll(resource.MemberUserIds.Select(async mui => new ElementReference((await repository.Users.Get(mui)).Username))), resource.ExternalSecurityGroups.Select(esg => esg.Id), - await Task.WhenAll(resource.UserRoleIds.ToModel(repository.UserRoles)), - await Task.WhenAll(resource.ProjectIds.ToModel(repository.Projects)), - await Task.WhenAll(resource.EnvironmentIds.ToModel(repository.Environments))); + new List(), + new List(), + new List()); } } } diff --git a/OctopusProjectBuilder.Uploader/Converters/UserRoleConverter.cs b/OctopusProjectBuilder.Uploader/Converters/UserRoleConverter.cs index 68b934c..c03f191 100644 --- a/OctopusProjectBuilder.Uploader/Converters/UserRoleConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/UserRoleConverter.cs @@ -10,13 +10,13 @@ public static UserRoleResource UpdateWith(this UserRoleResource resource, UserRo { resource.Name = model.Identifier.Name; resource.Description = model.Description; - resource.GrantedPermissions = model.Permissions.Select(p => (Octopus.Client.Model.Permission)p).ToList(); + resource.GrantedSystemPermissions = model.Permissions.Select(p => (Octopus.Client.Model.Permission)p).ToList(); return resource; } public static UserRole ToModel(this UserRoleResource resource) { - var permissions = resource.GrantedPermissions.Select(PermissionConverter.ToModel); + var permissions = resource.GrantedSystemPermissions.Select(PermissionConverter.ToModel); return new UserRole(new ElementIdentifier(resource.Name), resource.Description, permissions); } } diff --git a/OctopusProjectBuilder.Uploader/Converters/VariableSetConverter.cs b/OctopusProjectBuilder.Uploader/Converters/VariableSetConverter.cs index 9b055af..b9de20f 100644 --- a/OctopusProjectBuilder.Uploader/Converters/VariableSetConverter.cs +++ b/OctopusProjectBuilder.Uploader/Converters/VariableSetConverter.cs @@ -16,7 +16,12 @@ public static async Task> ToModel(this VariableSetResource public static async Task UpdateWith(this VariableSetResource resource, IVariableSet model, IOctopusAsyncRepository repository, DeploymentProcessResource deploymentProcess, ProjectResource project) { - resource.Variables = (await Task.WhenAll(model.Variables.Select(v => new VariableResource().UpdateWith(v, repository, deploymentProcess, project)))).ToList(); + if (model.Variables != null) + { + resource.Variables = (await Task.WhenAll(model.Variables.Select(v => + new VariableResource().UpdateWith(v, repository, deploymentProcess, project)))).ToList(); + } + return resource; } } diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeChannelRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeChannelRepository.cs similarity index 51% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeChannelRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeChannelRepository.cs index 6336834..aa68163 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeChannelRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeChannelRepository.cs @@ -1,25 +1,41 @@ -using System.Threading.Tasks; -using Octopus.Client.Editors.Async; -using Octopus.Client.Model; -using Octopus.Client.Repositories.Async; - -namespace OctopusProjectBuilder.Uploader.Tests.Helpers -{ - internal class FakeChannelRepository : FakeNamedRepository, IChannelRepository - { - public Task FindByName(ProjectResource project, string name) - { - return FindByName(name); - } - - public Task CreateOrModify(ProjectResource project, string name) - { - throw new System.NotImplementedException(); - } - - public Task CreateOrModify(ProjectResource project, string name, string description) - { - throw new System.NotImplementedException(); - } - } +using System.Collections.Generic; +using System.Threading.Tasks; +using Octopus.Client.Editors.Async; +using Octopus.Client.Model; +using Octopus.Client.Repositories.Async; + +namespace OctopusProjectBuilder.Uploader +{ + internal class FakeChannelRepository : FakeNamedRepository, IChannelRepository + { + public Task FindByName(ProjectResource project, string name) + { + return FindByName(name); + } + + public Task CreateOrModify(ProjectResource project, string name) + { + throw new System.NotImplementedException(); + } + + public Task CreateOrModify(ProjectResource project, string name, string description) + { + throw new System.NotImplementedException(); + } + + public Task> GetReleases(ChannelResource channel, int skip = 0, int? take = null, string searchByVersion = null) + { + throw new System.NotImplementedException(); + } + + public Task> GetAllReleases(ChannelResource channel) + { + throw new System.NotImplementedException(); + } + + public Task GetReleaseByVersion(ChannelResource channel, string version) + { + throw new System.NotImplementedException(); + } + } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeDeploymentProcessRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeDeploymentProcessRepository.cs similarity index 88% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeDeploymentProcessRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeDeploymentProcessRepository.cs index 72575af..58df927 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeDeploymentProcessRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeDeploymentProcessRepository.cs @@ -4,7 +4,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeDeploymentProcessRepository : FakeRepository, IDeploymentProcessRepository { @@ -22,6 +22,11 @@ protected override Task OnCreate(DeploymentProcessResource resource) return Task.CompletedTask; } + public IDeploymentProcessBetaRepository Beta() + { + throw new NotImplementedException(); + } + public Task GetTemplate(DeploymentProcessResource deploymentProcess, ChannelResource channel) { throw new NotImplementedException(); diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeEnvironmentRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeEnvironmentRepository.cs similarity index 97% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeEnvironmentRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeEnvironmentRepository.cs index c1e0340..a02c375 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeEnvironmentRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeEnvironmentRepository.cs @@ -4,7 +4,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeEnvironmentRepository : FakeNamedRepository, IEnvironmentRepository { diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeLibraryVariableSetRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeLibraryVariableSetRepository.cs similarity index 95% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeLibraryVariableSetRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeLibraryVariableSetRepository.cs index fd33e7a..5f09016 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeLibraryVariableSetRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeLibraryVariableSetRepository.cs @@ -3,7 +3,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeLibraryVariableSetRepository : FakeNamedRepository, ILibraryVariableSetRepository { diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeLifecycleRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeLifecycleRepository.cs similarity index 91% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeLifecycleRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeLifecycleRepository.cs index 6a63a82..8e8dbe4 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeLifecycleRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeLifecycleRepository.cs @@ -3,7 +3,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeLifecycleRepository : FakeNamedRepository, ILifecyclesRepository { diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeMachinePolicyRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeMachinePolicyRepository.cs similarity index 71% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeMachinePolicyRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeMachinePolicyRepository.cs index 472f502..d52336a 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeMachinePolicyRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeMachinePolicyRepository.cs @@ -3,7 +3,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeMachinePolicyRepository : FakeNamedRepository, IMachinePolicyRepository { @@ -11,5 +11,10 @@ public Task> GetMachines(MachinePolicyResource machinePoli { throw new System.NotImplementedException(); } + + public Task GetTemplate() + { + throw new System.NotImplementedException(); + } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeMachineRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeMachineRepository.cs similarity index 97% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeMachineRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeMachineRepository.cs index 20d6e6d..0a0a3a0 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeMachineRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeMachineRepository.cs @@ -5,7 +5,7 @@ using Octopus.Client.Model.Endpoints; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeMachineRepository : FakeNamedRepository, IMachineRepository { diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeMachineRoleRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeMachineRoleRepository.cs similarity index 85% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeMachineRoleRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeMachineRoleRepository.cs index 20e225f..875343e 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeMachineRoleRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeMachineRoleRepository.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeMachineRoleRepository : IMachineRoleRepository { diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeNamedRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeNamedRepository.cs similarity index 96% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeNamedRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeNamedRepository.cs index 5995965..7075ff6 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeNamedRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeNamedRepository.cs @@ -6,7 +6,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { public class FakeNamedRepository : FakeRepository, IFindByName where T : Resource, INamedResource { diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeOctopusClient.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeOctopusClient.cs similarity index 79% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeOctopusClient.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeOctopusClient.cs index 9fae1f2..8d7fa29 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeOctopusClient.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeOctopusClient.cs @@ -1,111 +1,141 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Threading.Tasks; -using Octopus.Client; -using Octopus.Client.Model; - -namespace OctopusProjectBuilder.Uploader.Tests.Helpers -{ - internal class FakeOctopusClient : IOctopusAsyncClient - { - public void Dispose() - { - throw new NotImplementedException(); - } - - public Task> List(string path, object pathParameters = null) - { - throw new NotImplementedException(); - } - - public Task> ListAll(string path, object pathParameters = null) - { - throw new NotImplementedException(); - } - - public Task Paginate(string path, Func, bool> getNextPage) - { - throw new NotImplementedException(); - } - - public Task Paginate(string path, object pathParameters, Func, bool> getNextPage) - { - throw new NotImplementedException(); - } - - public Task Get(string path, object pathParameters = null) - { - throw new NotImplementedException(); - } - - public Task Create(string path, TResource resource, object pathParameters = null) - { - throw new NotImplementedException(); - } - - public Task Post(string path, TResource resource, object pathParameters = null) - { - throw new NotImplementedException(); - } - - public Task Post(string path, TResource resource, object pathParameters = null) - { - throw new NotImplementedException(); - } - - public Task Post(string path) - { - throw new NotImplementedException(); - } - - public Task Put(string path, TResource resource) - { - throw new NotImplementedException(); - } - - public Task Put(string path) - { - throw new NotImplementedException(); - } - - public Task Update(string path, TResource resource, object pathParameters = null) - { - throw new NotImplementedException(); - } - - public Task Delete(string path, object pathParameters = null) - { - throw new NotImplementedException(); - } - - public Task GetContent(string path, object pathParameters = null) - { - throw new NotImplementedException(); - } - - public Task PutContent(string path, Stream contentStream) - { - throw new NotImplementedException(); - } - - public Uri QualifyUri(string path, object parameters = null) - { - throw new NotImplementedException(); - } - - public Task RefreshRootDocument() - { - throw new NotImplementedException(); - } - - public RootResource RootDocument { get; } - public IOctopusAsyncRepository Repository { get; } - public bool IsUsingSecureConnection { get; } - public event Action SendingOctopusRequest; - public event Action ReceivedOctopusResponse; - public event Action BeforeSendingHttpRequest; - public event Action AfterReceivedHttpResponse; - } +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Octopus.Client; +using Octopus.Client.Model; + +namespace OctopusProjectBuilder.Uploader +{ + internal class FakeOctopusClient : IOctopusAsyncClient + { + public void Dispose() + { + throw new NotImplementedException(); + } + + public Task> List(string path, object pathParameters = null) + { + throw new NotImplementedException(); + } + + public Task> ListAll(string path, object pathParameters = null) + { + throw new NotImplementedException(); + } + + public Task Paginate(string path, Func, bool> getNextPage) + { + throw new NotImplementedException(); + } + + public Task Paginate(string path, object pathParameters, Func, bool> getNextPage) + { + throw new NotImplementedException(); + } + + public Task Get(string path, object pathParameters = null) + { + throw new NotImplementedException(); + } + + public Task Create(string path, TResource resource, object pathParameters = null) + { + throw new NotImplementedException(); + } + + public Task Post(string path, TResource resource, object pathParameters = null) + { + throw new NotImplementedException(); + } + + public Task Post(string path, TResource resource, object pathParameters = null) + { + throw new NotImplementedException(); + } + + public Task Post(string path) + { + throw new NotImplementedException(); + } + + public Task Put(string path, TResource resource) + { + throw new NotImplementedException(); + } + + public Task Put(string path) + { + throw new NotImplementedException(); + } + + public Task Put(string path, TResource resource, object pathParameters = null) + { + throw new NotImplementedException(); + } + + public Task Update(string path, TResource resource, object pathParameters = null) + { + throw new NotImplementedException(); + } + + public Task Delete(string path, object pathParameters = null, object resource = null) + { + throw new NotImplementedException(); + } + + public Task Delete(string path, object pathParameters = null) + { + throw new NotImplementedException(); + } + + public Task GetContent(string path, object pathParameters = null) + { + throw new NotImplementedException(); + } + + public Task PutContent(string path, Stream contentStream) + { + throw new NotImplementedException(); + } + + public Uri QualifyUri(string path, object parameters = null) + { + throw new NotImplementedException(); + } + + public Task SignIn(LoginCommand loginCommand) + { + throw new NotImplementedException(); + } + + public Task SignOut() + { + throw new NotImplementedException(); + } + + public IOctopusSpaceAsyncRepository ForSpace(SpaceResource space) + { + throw new NotImplementedException(); + } + + public IOctopusSystemAsyncRepository ForSystem() + { + throw new NotImplementedException(); + } + + public Task RefreshRootDocument() + { + throw new NotImplementedException(); + } + + public RootResource RootDocument { get; } + public IOctopusAsyncRepository Repository { get; } + public bool IsUsingSecureConnection { get; } + public event Action SendingOctopusRequest; + public event Action ReceivedOctopusResponse; + public event Action BeforeSendingHttpRequest; + public event Action AfterReceivedHttpResponse; + } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeOctopusRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeOctopusRepository.cs similarity index 70% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeOctopusRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeOctopusRepository.cs index fc6d800..9e0ae1a 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeOctopusRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeOctopusRepository.cs @@ -1,84 +1,128 @@ -using Octopus.Client; -using Octopus.Client.Repositories.Async; - -namespace OctopusProjectBuilder.Uploader.Tests.Helpers -{ - internal class FakeOctopusRepository : IOctopusAsyncRepository - { - public FakeOctopusRepository() - { - var fakeVariableSetRepository = new FakeVariableSetRepository(); - var fakeDeploymentProcessRepository = new FakeDeploymentProcessRepository(); - var fakeProjectTriggersRepository = new FakeProjectTriggersRepository(); - var fakeOctopusClient = new FakeOctopusClient(); - - MachinePolicies = new FakeMachinePolicyRepository(); - DeploymentProcesses = fakeDeploymentProcessRepository; - ProjectGroups = new FakeProjectGroupRepository(); - VariableSets = fakeVariableSetRepository; - LibraryVariableSets = new FakeLibraryVariableSetRepository(fakeVariableSetRepository); - Projects = new FakeProjectRepository(fakeVariableSetRepository, fakeDeploymentProcessRepository, fakeProjectTriggersRepository); - Lifecycles = new FakeLifecycleRepository(); - Environments = new FakeEnvironmentRepository(); - MachineRoles = new FakeMachineRoleRepository(); - Machines = new FakeMachineRepository(); - UserRoles = new FakeUserRolesRepository(); - Teams = new FakeTeamsRepository(); - Users = new FakeUsersRepository(); - ProjectTriggers = fakeProjectTriggersRepository; - Channels = new FakeChannelRepository(); - TagSets = new FakeTagSetsRepository(); - Tenants = new FakeTenantsRepository(); - Client = fakeOctopusClient; - } - - public IOctopusAsyncClient Client { get; } - public IArtifactRepository Artifacts { get; } - public IActionTemplateRepository ActionTemplates { get; } - public ICertificateRepository Certificates { get; } - public ICertificateConfigurationRepository CertificateConfiguration { get; } - public IBackupRepository Backups { get; } - public IBuiltInPackageRepositoryRepository BuiltInPackageRepository { get; } - public ICommunityActionTemplateRepository CommunityActionTemplates { get; } - public IConfigurationRepository Configuration { get; } - public IDashboardConfigurationRepository DashboardConfigurations { get; } - public IDashboardRepository Dashboards { get; } - public IDeploymentProcessRepository DeploymentProcesses { get; } - public IDeploymentRepository Deployments { get; } - public IEnvironmentRepository Environments { get; } - public IEventRepository Events { get; } - public IFeaturesConfigurationRepository FeaturesConfiguration { get; } - public IFeedRepository Feeds { get; } - public IInterruptionRepository Interruptions { get; } - public ILibraryVariableSetRepository LibraryVariableSets { get; } - public ILifecyclesRepository Lifecycles { get; } - public IMachineRepository Machines { get; } - public IMachineRoleRepository MachineRoles { get; } - public IMigrationRepository Migrations { get; } - public IMachinePolicyRepository MachinePolicies { get; } - public IPerformanceConfigurationRepository PerformanceConfiguration { get; } - public IProjectGroupRepository ProjectGroups { get; } - public IProjectRepository Projects { get; } - public IReleaseRepository Releases { get; } - public IProxyRepository Proxies { get; } - public IServerStatusRepository ServerStatus { get; } - public ISchedulerRepository Schedulers { get; } - public ISubscriptionRepository Subscriptions { get; } - public ITaskRepository Tasks { get; } - public ITeamsRepository Teams { get; } - public ITagSetRepository TagSets { get; } - public ITenantRepository Tenants { get; } - public ITenantVariablesRepository TenantVariables { get; } - public IUserRepository Users { get; } - public IUserRolesRepository UserRoles { get; } - public IVariableSetRepository VariableSets { get; } - public IWorkerPoolRepository WorkerPools { get; } - public IWorkerRepository Workers { get; } - public IChannelRepository Channels { get; } - public IProjectTriggerRepository ProjectTriggers { get; } - public IAccountRepository Accounts { get; } - public IRetentionPolicyRepository RetentionPolicies { get; } - public IDefectsRepository Defects { get; } - public IOctopusServerNodeRepository OctopusServerNodes { get; } - } -} +using System.Threading.Tasks; +using Octopus.Client; +using Octopus.Client.Model; +using Octopus.Client.Repositories.Async; + +namespace OctopusProjectBuilder.Uploader +{ + public class FakeOctopusRepository : IOctopusAsyncRepository + { + public FakeOctopusRepository() + { + var fakeVariableSetRepository = new FakeVariableSetRepository(); + var fakeDeploymentProcessRepository = new FakeDeploymentProcessRepository(); + var fakeProjectTriggersRepository = new FakeProjectTriggersRepository(); + var fakeOctopusClient = new FakeOctopusClient(); + + MachinePolicies = new FakeMachinePolicyRepository(); + DeploymentProcesses = fakeDeploymentProcessRepository; + ProjectGroups = new FakeProjectGroupRepository(); + VariableSets = fakeVariableSetRepository; + LibraryVariableSets = new FakeLibraryVariableSetRepository(fakeVariableSetRepository); + Projects = new FakeProjectRepository(fakeVariableSetRepository, fakeDeploymentProcessRepository, fakeProjectTriggersRepository); + Lifecycles = new FakeLifecycleRepository(); + Environments = new FakeEnvironmentRepository(); + MachineRoles = new FakeMachineRoleRepository(); + Machines = new FakeMachineRepository(); + UserRoles = new FakeUserRolesRepository(); + Teams = new FakeTeamsRepository(); + Users = new FakeUsersRepository(); + ProjectTriggers = fakeProjectTriggersRepository; + Channels = new FakeChannelRepository(); + TagSets = new FakeTagSetsRepository(); + Tenants = new FakeTenantsRepository(); + Client = fakeOctopusClient; + } + + public IUserInvitesRepository UserInvites { get; } + public IOctopusAsyncClient Client { get; } + public RepositoryScope Scope { get; } + public IArtifactRepository Artifacts { get; } + public IOctopusSpaceAsyncBetaRepository Beta { get; } + public IActionTemplateRepository ActionTemplates { get; } + public IBuildInformationRepository BuildInformationRepository { get; } + public ICertificateRepository Certificates { get; } + public ICertificateConfigurationRepository CertificateConfiguration { get; } + public IBackupRepository Backups { get; } + public IBuiltInPackageRepositoryRepository BuiltInPackageRepository { get; } + public IUserTeamsRepository UserTeams { get; } + public ICommunityActionTemplateRepository CommunityActionTemplates { get; } + public IConfigurationRepository Configuration { get; } + public IDashboardConfigurationRepository DashboardConfigurations { get; } + public IDashboardRepository Dashboards { get; } + public IDeploymentProcessRepository DeploymentProcesses { get; } + public IDeploymentSettingsRepository DeploymentSettings { get; } + public IDeploymentRepository Deployments { get; } + public IEnvironmentRepository Environments { get; } + public Task HasLink(string name) + { + throw new System.NotImplementedException(); + } + + public Task HasLinkParameter(string linkName, string parameterName) + { + throw new System.NotImplementedException(); + } + + public Task Link(string name) + { + throw new System.NotImplementedException(); + } + + public IEventRepository Events { get; } + public IFeaturesConfigurationRepository FeaturesConfiguration { get; } + public IFeedRepository Feeds { get; } + public IInterruptionRepository Interruptions { get; } + public ILibraryVariableSetRepository LibraryVariableSets { get; } + public ILifecyclesRepository Lifecycles { get; } + public IMachineRepository Machines { get; } + public IMachineRoleRepository MachineRoles { get; } + public IPackageMetadataRepository PackageMetadataRepository { get; } + public IMigrationRepository Migrations { get; } + public ILicensesRepository Licenses { get; } + public IMachinePolicyRepository MachinePolicies { get; } + public IPerformanceConfigurationRepository PerformanceConfiguration { get; } + public IProjectGroupRepository ProjectGroups { get; } + public IProjectRepository Projects { get; } + public IRunbookRepository Runbooks { get; } + public IRunbookProcessRepository RunbookProcesses { get; } + public IRunbookSnapshotRepository RunbookSnapshots { get; } + public IRunbookRunRepository RunbookRuns { get; } + public IReleaseRepository Releases { get; } + public IProxyRepository Proxies { get; } + public IServerStatusRepository ServerStatus { get; } + public ISpaceRepository Spaces { get; } + + public Task LoadRootDocument() + { + throw new System.NotImplementedException(); + } + + public ISchedulerRepository Schedulers { get; } + public ISubscriptionRepository Subscriptions { get; } + public ITaskRepository Tasks { get; } + public ITeamsRepository Teams { get; } + public IScopedUserRoleRepository ScopedUserRoles { get; } + public IUserPermissionsRepository UserPermissions { get; } + public ITagSetRepository TagSets { get; } + public ITenantRepository Tenants { get; } + public ITenantVariablesRepository TenantVariables { get; } + public IUserRepository Users { get; } + public IUserRolesRepository UserRoles { get; } + public IUpgradeConfigurationRepository UpgradeConfiguration { get; } + public IVariableSetRepository VariableSets { get; } + public IWorkerPoolRepository WorkerPools { get; } + public IWorkerRepository Workers { get; } + public IChannelRepository Channels { get; } + public IProjectTriggerRepository ProjectTriggers { get; } + public Task LoadSpaceRootDocument() + { + throw new System.NotImplementedException(); + } + + public IAccountRepository Accounts { get; } + public IRetentionPolicyRepository RetentionPolicies { get; } + public IDefectsRepository Defects { get; } + public IOctopusServerNodeRepository OctopusServerNodes { get; } + } +} diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeProjectGroupRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeProjectGroupRepository.cs similarity index 94% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeProjectGroupRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeProjectGroupRepository.cs index 3fe2fe3..5dfe8c1 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeProjectGroupRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeProjectGroupRepository.cs @@ -5,7 +5,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeProjectGroupRepository : FakeNamedRepository, IProjectGroupRepository { diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeProjectRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeProjectRepository.cs similarity index 66% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeProjectRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeProjectRepository.cs index 37cfc7b..df43186 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeProjectRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeProjectRepository.cs @@ -1,88 +1,134 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Octopus.Client.Editors.Async; -using Octopus.Client.Extensibility; -using Octopus.Client.Model; -using Octopus.Client.Repositories.Async; - -namespace OctopusProjectBuilder.Uploader.Tests.Helpers -{ - internal class FakeProjectRepository : FakeNamedRepository, IProjectRepository - { - private readonly FakeVariableSetRepository _variableSetRepository; - private readonly FakeDeploymentProcessRepository _deploymentProcessRepository; - private readonly FakeProjectTriggersRepository _projectTriggersRepository; - - public FakeProjectRepository(FakeVariableSetRepository variableSetRepository, FakeDeploymentProcessRepository deploymentProcessRepository, FakeProjectTriggersRepository projectTriggersRepository) - { - _variableSetRepository = variableSetRepository; - _deploymentProcessRepository = deploymentProcessRepository; - _projectTriggersRepository = projectTriggersRepository; - } - - protected override async Task OnCreate(ProjectResource resource) - { - resource.VariableSetId = (await _variableSetRepository.Create(new VariableSetResource())).Id; - resource.DeploymentProcessId = (await _deploymentProcessRepository.Create(new DeploymentProcessResource())).Id; - } - - public Task> GetAll() - { - throw new NotImplementedException(); - } - - public Task> GetReleases(ProjectResource project, int skip = 0) - { - throw new NotImplementedException(); - } - - public Task> GetReleases(ProjectResource project, int skip = 0, int? take = null, string searchByVersion = null) - { - throw new NotImplementedException(); - } - - public Task> GetAllReleases(ProjectResource project) - { - throw new NotImplementedException(); - } - - public Task GetReleaseByVersion(ProjectResource project, string version) - { - throw new NotImplementedException(); - } - - public Task> GetChannels(ProjectResource project) - { - throw new NotImplementedException(); - } - - public Task GetProgression(ProjectResource project) - { - throw new NotImplementedException(); - } - - public Task> GetTriggers(ProjectResource project) - { - var projectTriggers = _projectTriggersRepository.FindAll().GetAwaiter().GetResult().Where(pt => pt.ProjectId == project.Id); - return Task.FromResult(new ResourceCollection(projectTriggers, new LinkCollection())); - } - - public Task SetLogo(ProjectResource project, string fileName, Stream contents) - { - throw new NotImplementedException(); - } - - public Task CreateOrModify(string name, ProjectGroupResource projectGroup, LifecycleResource lifecycle) - { - throw new NotImplementedException(); - } - - public Task CreateOrModify(string name, ProjectGroupResource projectGroup, LifecycleResource lifecycle, string description) - { - throw new NotImplementedException(); - } - } +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client.Editors.Async; +using Octopus.Client.Extensibility; +using Octopus.Client.Model; +using Octopus.Client.Repositories.Async; + +namespace OctopusProjectBuilder.Uploader +{ + internal class FakeProjectRepository : FakeNamedRepository, IProjectRepository + { + private readonly FakeVariableSetRepository _variableSetRepository; + private readonly FakeDeploymentProcessRepository _deploymentProcessRepository; + private readonly FakeProjectTriggersRepository _projectTriggersRepository; + + public FakeProjectRepository(FakeVariableSetRepository variableSetRepository, FakeDeploymentProcessRepository deploymentProcessRepository, FakeProjectTriggersRepository projectTriggersRepository) + { + _variableSetRepository = variableSetRepository; + _deploymentProcessRepository = deploymentProcessRepository; + _projectTriggersRepository = projectTriggersRepository; + } + + protected override async Task OnCreate(ProjectResource resource) + { + resource.VariableSetId = (await _variableSetRepository.Create(new VariableSetResource())).Id; + resource.DeploymentProcessId = (await _deploymentProcessRepository.Create(new DeploymentProcessResource())).Id; + } + + public Task> GetAll() + { + throw new NotImplementedException(); + } + + public Task> GetReleases(ProjectResource project, int skip = 0) + { + throw new NotImplementedException(); + } + + public IProjectBetaRepository Beta() + { + throw new NotImplementedException(); + } + + public Task> GetReleases(ProjectResource project, int skip = 0, int? take = null, string searchByVersion = null) + { + throw new NotImplementedException(); + } + + public Task> GetAllReleases(ProjectResource project) + { + throw new NotImplementedException(); + } + + public Task GetReleaseByVersion(ProjectResource project, string version) + { + throw new NotImplementedException(); + } + + public Task> GetChannels(ProjectResource project) + { + throw new NotImplementedException(); + } + + public Task> GetAllChannels(ProjectResource project) + { + throw new NotImplementedException(); + } + + public Task GetProgression(ProjectResource project) + { + throw new NotImplementedException(); + } + + public Task> GetTriggers(ProjectResource project) + { + var projectTriggers = _projectTriggersRepository.FindAll().GetAwaiter().GetResult().Where(pt => pt.ProjectId == project.Id); + return Task.FromResult(new ResourceCollection(projectTriggers, new LinkCollection())); + } + + public Task> GetAllTriggers(ProjectResource project) + { + throw new NotImplementedException(); + } + + public Task SetLogo(ProjectResource project, string fileName, Stream contents) + { + throw new NotImplementedException(); + } + + public Task CreateOrModify(string name, ProjectGroupResource projectGroup, LifecycleResource lifecycle) + { + throw new NotImplementedException(); + } + + public Task CreateOrModify(string name, ProjectGroupResource projectGroup, LifecycleResource lifecycle, string description, + string cloneId = null) + { + throw new NotImplementedException(); + } + + public Task> GetRunbookSnapshots(ProjectResource project, int skip = 0, int? take = null, string searchByName = null) + { + throw new NotImplementedException(); + } + + public Task> GetAllRunbookSnapshots(ProjectResource project) + { + throw new NotImplementedException(); + } + + public Task GetRunbookSnapshotByName(ProjectResource project, string name) + { + throw new NotImplementedException(); + } + + public Task> GetRunbooks(ProjectResource project, int skip = 0, int? take = null, string searchByName = null) + { + throw new NotImplementedException(); + } + + public Task> GetAllRunbooks(ProjectResource project) + { + throw new NotImplementedException(); + } + + public Task CreateOrModify(string name, ProjectGroupResource projectGroup, LifecycleResource lifecycle, string description) + { + throw new NotImplementedException(); + } + } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeProjectTriggersRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeProjectTriggersRepository.cs similarity index 81% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeProjectTriggersRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeProjectTriggersRepository.cs index ca4e313..77ddcf5 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeProjectTriggersRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeProjectTriggersRepository.cs @@ -1,29 +1,34 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Octopus.Client.Editors.Async; -using Octopus.Client.Model; -using Octopus.Client.Model.Triggers; -using Octopus.Client.Repositories.Async; - -namespace OctopusProjectBuilder.Uploader.Tests.Helpers -{ - internal class FakeProjectTriggersRepository : FakeRepository, IProjectTriggerRepository - { - public Task> FindAll() - { - return Task.FromResult(_items); - } - - public Task FindByName(ProjectResource project, string name) - { - var trigger = _items.FirstOrDefault(m => string.Equals(m.Name, name, System.StringComparison.OrdinalIgnoreCase)); - return Task.FromResult(trigger); - } - - public Task CreateOrModify(ProjectResource project, string name, TriggerFilterResource filter, TriggerActionResource action) - { - throw new System.NotImplementedException(); - } - } +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client.Editors.Async; +using Octopus.Client.Model; +using Octopus.Client.Model.Triggers; +using Octopus.Client.Repositories.Async; + +namespace OctopusProjectBuilder.Uploader +{ + internal class FakeProjectTriggersRepository : FakeRepository, IProjectTriggerRepository + { + public Task> FindAll() + { + return Task.FromResult(_items); + } + + public Task FindByName(ProjectResource project, string name) + { + var trigger = _items.FirstOrDefault(m => string.Equals(m.Name, name, System.StringComparison.OrdinalIgnoreCase)); + return Task.FromResult(trigger); + } + + public Task CreateOrModify(ProjectResource project, string name, TriggerFilterResource filter, TriggerActionResource action) + { + throw new System.NotImplementedException(); + } + + public Task> FindByRunbook(params string[] runbookIds) + { + throw new System.NotImplementedException(); + } + } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeRepository.cs similarity index 91% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeRepository.cs index b43d8c1..8d43396 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeRepository.cs @@ -1,75 +1,74 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Octopus.Client.Model; -using Octopus.Client.Repositories.Async; -using OctopusProjectBuilder.Uploader.Tests.Serialization; - -namespace OctopusProjectBuilder.Uploader.Tests.Helpers -{ - public class FakeRepository : ICreate, IGet, IModify, IDelete where T : Resource - { - protected readonly List _items = new List(); - - public Task Create(T resource, object pathParameters = null) - { - resource.Id = Guid.NewGuid().ToString(); - OnCreate(resource); - _items.Add(Clone(resource)); - return Task.FromResult(resource); - } - - public Task Get(string idOrHref) - { - return Task.FromResult(Clone(_items.Single(t => t.Id == idOrHref))); - } - - public Task> Get(params string[] ids) - { - return Task.FromResult(_items.Where(t => ids.Contains(t.Id)).Select(Clone).ToList()); - } - - public Task Refresh(T resource) - { - throw new NotImplementedException(); - } - - public Task Modify(T resource) - { - var index = _items.FindIndex(t => t.Id == resource.Id); - if (index < 0) - throw new KeyNotFoundException(resource.Id); - OnModify(_items[index], resource); - resource.Id = _items[index].Id; - _items[index] = Clone(resource); - return Task.FromResult(resource); - } - - public Task Delete(T resource) - { - throw new NotImplementedException(); - } - - protected virtual Task OnCreate(T resource) - { - return Task.CompletedTask; - } - - protected virtual Task OnModify(T currentItem, T newItem) - { - return Task.CompletedTask; - } - - protected static T Clone(T resource) - { - if (ReferenceEquals(resource, null)) - { - return default(T); - } - - return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(resource), JsonSerialization.GetDefaultSerializerSettings()); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Octopus.Client.Model; +using Octopus.Client.Repositories.Async; + +namespace OctopusProjectBuilder.Uploader +{ + public class FakeRepository : ICreate, IGet, IModify, IDelete where T : Resource + { + protected readonly List _items = new List(); + + public Task Create(T resource, object pathParameters = null) + { + resource.Id = Guid.NewGuid().ToString(); + OnCreate(resource); + _items.Add(Clone(resource)); + return Task.FromResult(resource); + } + + public Task Get(string idOrHref) + { + return Task.FromResult(Clone(_items.Single(t => t.Id == idOrHref))); + } + + public Task> Get(params string[] ids) + { + return Task.FromResult(_items.Where(t => ids.Contains(t.Id)).Select(Clone).ToList()); + } + + public Task Refresh(T resource) + { + throw new NotImplementedException(); + } + + public Task Modify(T resource) + { + var index = _items.FindIndex(t => t.Id == resource.Id); + if (index < 0) + throw new KeyNotFoundException(resource.Id); + OnModify(_items[index], resource); + resource.Id = _items[index].Id; + _items[index] = Clone(resource); + return Task.FromResult(resource); + } + + public Task Delete(T resource) + { + throw new NotImplementedException(); + } + + protected virtual Task OnCreate(T resource) + { + return Task.CompletedTask; + } + + protected virtual Task OnModify(T currentItem, T newItem) + { + return Task.CompletedTask; + } + + protected static T Clone(T resource) + { + if (ReferenceEquals(resource, null)) + { + return default(T); + } + + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(resource), JsonSerialization.GetDefaultSerializerSettings()); + } + } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeTagSetsRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeTagSetsRepository.cs similarity index 93% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeTagSetsRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeTagSetsRepository.cs index 2cc2f3d..86f6227 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeTagSetsRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeTagSetsRepository.cs @@ -4,7 +4,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { public class FakeTagSetsRepository : FakeNamedRepository, ITagSetRepository { diff --git a/OctopusProjectBuilder.Uploader/Helpers/FakeTeamsRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeTeamsRepository.cs new file mode 100644 index 0000000..4ea3c7e --- /dev/null +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeTeamsRepository.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Octopus.Client; +using Octopus.Client.Model; +using Octopus.Client.Repositories.Async; + +namespace OctopusProjectBuilder.Uploader +{ + internal class FakeTeamsRepository : FakeNamedRepository, ITeamsRepository + { + public ITeamsRepository UsingContext(SpaceContext spaceContext) + { + throw new System.NotImplementedException(); + } + + public Task> GetScopedUserRoles(TeamResource team) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeTenantsRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeTenantsRepository.cs similarity index 76% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeTenantsRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeTenantsRepository.cs index 041aad4..e50917c 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeTenantsRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeTenantsRepository.cs @@ -5,7 +5,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeTenantsRepository : FakeNamedRepository, ITenantRepository { @@ -14,6 +14,11 @@ public Task> GetAll() throw new System.NotImplementedException(); } + public Task Status() + { + throw new System.NotImplementedException(); + } + public Task SetLogo(TenantResource tenant, string fileName, Stream contents) { throw new System.NotImplementedException(); @@ -48,5 +53,15 @@ public Task CreateOrModify(string name) { throw new System.NotImplementedException(); } + + public Task CreateOrModify(string name, string description) + { + throw new System.NotImplementedException(); + } + + public Task CreateOrModify(string name, string description, string cloneId) + { + throw new System.NotImplementedException(); + } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeUserRoleRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeUserRoleRepository.cs similarity index 77% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeUserRoleRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeUserRoleRepository.cs index 7b2ee90..e7cdae3 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeUserRoleRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeUserRoleRepository.cs @@ -1,7 +1,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeUserRolesRepository : FakeNamedRepository, IUserRolesRepository { diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeUsersRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeUsersRepository.cs similarity index 82% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/FakeUsersRepository.cs rename to OctopusProjectBuilder.Uploader/Helpers/FakeUsersRepository.cs index 1a53854..2167777 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/FakeUsersRepository.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeUsersRepository.cs @@ -5,7 +5,7 @@ using Octopus.Client.Model; using Octopus.Client.Repositories.Async; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class FakeUsersRepository : FakeRepository, IUserRepository { @@ -30,6 +30,11 @@ public Task> FindAll(string path = null, object pathParameter throw new NotImplementedException(); } + public Task FindByUsername(string username) + { + throw new NotImplementedException(); + } + public Task Create(string username, string displayName, string password = null, string emailAddress = null) { throw new NotImplementedException(); @@ -65,6 +70,16 @@ public Task GetCurrent() throw new NotImplementedException(); } + public Task GetSpaces(UserResource user) + { + throw new NotImplementedException(); + } + + public Task CreateApiKey(UserResource user, string purpose = null, DateTimeOffset? expires = null) + { + throw new NotImplementedException(); + } + public Task GetPermissions(UserResource user) { throw new NotImplementedException(); @@ -80,6 +95,11 @@ public Task> GetApiKeys(UserResource user) throw new NotImplementedException(); } + public Task RevokeApiKey(ApiKeyResourceBase apiKey) + { + throw new NotImplementedException(); + } + public Task RevokeApiKey(ApiKeyResource apiKey) { throw new NotImplementedException(); diff --git a/OctopusProjectBuilder.Uploader/Helpers/FakeVariableSetRepository.cs b/OctopusProjectBuilder.Uploader/Helpers/FakeVariableSetRepository.cs new file mode 100644 index 0000000..ee83d96 --- /dev/null +++ b/OctopusProjectBuilder.Uploader/Helpers/FakeVariableSetRepository.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Octopus.Client.Model; +using Octopus.Client.Repositories.Async; + +namespace OctopusProjectBuilder.Uploader +{ + internal class FakeVariableSetRepository : FakeRepository, IVariableSetRepository + { + public Task GetVariableNames(string projects, string[] environments) + { + throw new System.NotImplementedException(); + } + + public Task GetVariablePreview(string project, string channel, string tenant, string runbook, string action, + string environment, string machine, string role) + { + throw new System.NotImplementedException(); + } + + public Task> GetAll() + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.Uploader.Tests/Helpers/PropertyValueResourceJsonConverter.cs b/OctopusProjectBuilder.Uploader/Helpers/PropertyValueResourceJsonConverter.cs similarity index 95% rename from OctopusProjectBuilder.Uploader.Tests/Helpers/PropertyValueResourceJsonConverter.cs rename to OctopusProjectBuilder.Uploader/Helpers/PropertyValueResourceJsonConverter.cs index 788a0ff..ab1e0ad 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Helpers/PropertyValueResourceJsonConverter.cs +++ b/OctopusProjectBuilder.Uploader/Helpers/PropertyValueResourceJsonConverter.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json.Linq; using Octopus.Client.Model; -namespace OctopusProjectBuilder.Uploader.Tests.Helpers +namespace OctopusProjectBuilder.Uploader { internal class PropertyValueResourceJsonConverter : JsonConverter { diff --git a/OctopusProjectBuilder.Uploader/ModelDownloader.cs b/OctopusProjectBuilder.Uploader/ModelDownloader.cs index e53b3a6..b30a5c9 100644 --- a/OctopusProjectBuilder.Uploader/ModelDownloader.cs +++ b/OctopusProjectBuilder.Uploader/ModelDownloader.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Octopus.Client; @@ -19,19 +20,40 @@ public ModelDownloader(IOctopusAsyncRepository repository, ILoggerFactory logger _logger = loggerFactory.CreateLogger(); } - public async Task DownloadModel() + public async Task DownloadModel(string projectName = null) { + List projects; + projects = projectName != null ? + Enumerable.Repeat(await _repository.Projects.FindByName(projectName), 1).ToList() : + (await _repository.Projects.FindAll()).ToList(); + + List channels; + channels = projectName != null ? + (await _repository.Channels.FindMany(c => projects.Any(p => p.Id == c.ProjectId))).ToList() : + (await _repository.Channels.FindAll()).ToList(); + + List runbooks; + runbooks = projectName != null ? + (await _repository.Runbooks.FindMany(c => projects.Any(p => p.Id == c.ProjectId))).ToList() : + (await _repository.Runbooks.FindAll()).ToList(); + return new SystemModel( - await Task.WhenAll((await _repository.MachinePolicies.FindAll()).Select(ReadMachinePolicy)), + await Task.WhenAll((await _repository.MachinePolicies.FindMany(x => false)).Select(ReadMachinePolicy)), await Task.WhenAll((await _repository.Lifecycles.FindAll()).Select(ReadLifecycle)), - await Task.WhenAll((await _repository.ProjectGroups.FindAll()).Select(ReadProjectGroup)), - await Task.WhenAll((await _repository.LibraryVariableSets.FindAll()).Select(ReadLibraryVariableSet)), - await Task.WhenAll((await _repository.Projects.FindAll()).Select(ReadProject)), + await Task.WhenAll((await _repository.ProjectGroups.FindAll()) + .Where(g => projectName == null || projects.Any(p => p.ProjectGroupId == g.Id)) + .Select(ReadProjectGroup)), + await Task.WhenAll((await _repository.LibraryVariableSets.FindAll()) + .Where(v => projectName == null || projects.Any(p => p.IncludedLibraryVariableSetIds.Contains(v.Id))) + .Select(ReadLibraryVariableSet)), + await Task.WhenAll(projects.Select(ReadProject)), + await Task.WhenAll(channels.Select(ReadChannel)), await Task.WhenAll((await _repository.Environments.FindAll()).Select(ReadEnvironment)), - await Task.WhenAll((await _repository.UserRoles.FindAll()).Select(ReadUserRole)), - await Task.WhenAll((await _repository.Teams.FindAll()).Select(ReadTeam)), - await Task.WhenAll((await _repository.Tenants.FindAll()).Select(ReadTenant)), - await Task.WhenAll((await _repository.TagSets.FindAll()).Select(ReadTagSet))); + await Task.WhenAll((await _repository.UserRoles.FindMany(x => false)).Select(ReadUserRole)), + await Task.WhenAll((await _repository.Teams.FindMany(x => false)).Select(ReadTeam)), + await Task.WhenAll((await _repository.Tenants.FindMany(x => false)).Select(ReadTenant)), + await Task.WhenAll((await _repository.TagSets.FindMany(x => false)).Select(ReadTagSet)), + await Task.WhenAll(runbooks.Select(ReadRunbook))); } private async Task ReadMachinePolicy(MachinePolicyResource resource) @@ -57,6 +79,12 @@ private async Task ReadProject(ProjectResource resource) _logger.LogInformation($"Downloading {nameof(ProjectResource)}: {resource.Name}"); return await resource.ToModel(_repository); } + + private async Task ReadChannel(ChannelResource resource) + { + _logger.LogInformation($"Downloading {nameof(ChannelResource)}: {resource.Name}"); + return await resource.ToModel(_repository); + } private async Task ReadProjectGroup(ProjectGroupResource resource) { @@ -93,5 +121,11 @@ private async Task ReadTagSet(TagSetResource resource) _logger.LogInformation($"Downloading {nameof(TagSetResource)}: {resource.Name}"); return await Task.FromResult(resource.ToModel(_repository)); } + + private async Task ReadRunbook(RunbookResource resource) + { + _logger.LogInformation($"Downloading {nameof(Runbook)}: {resource.Name}"); + return await resource.ToModel(_repository); + } } } diff --git a/OctopusProjectBuilder.Uploader/ModelUploader.cs b/OctopusProjectBuilder.Uploader/ModelUploader.cs index 28e64ad..f412f13 100644 --- a/OctopusProjectBuilder.Uploader/ModelUploader.cs +++ b/OctopusProjectBuilder.Uploader/ModelUploader.cs @@ -45,6 +45,12 @@ public async Task UploadModel(SystemModel model) foreach (var project in model.Projects) await UploadProject(project); + + foreach (var runbook in model.Runbooks) + await UploadRunbook(runbook); + + foreach (var channel in model.Channels) + await UploadChannel(channel); foreach (var tenant in model.Tenants) await UploadTenant(tenant); @@ -115,7 +121,33 @@ await Update( variableSetResource, projectResource.Name); - await UploadProjectTriggers(projectResource, project.Triggers); + if (project.Triggers != null) + { + await UploadProjectTriggers(projectResource, project.Triggers); + } + } + + private async Task UploadRunbook(Runbook runbook) + { + var runbookResource = await LoadResource(_repository.Runbooks, runbook.Identifier); + await runbookResource.UpdateWith(runbook, _repository); + var response = await Upsert(_repository.Runbooks, runbookResource); + runbookResource.RunbookProcessId = response.RunbookProcessId; + + var runbookProcessResource = await _repository.RunbookProcesses.Get(runbookResource.RunbookProcessId); + await runbookProcessResource.UpdateWith(runbook.Process, _repository); + + await Update(_repository.RunbookProcesses, + runbookProcessResource, + runbookResource.Name); + } + + private async Task UploadChannel(Channel channel) + { + var projectResource = await LoadResource(_repository.Projects, new ElementIdentifier(channel.ProjectName)); + var resource = await LoadResource(name => _repository.Channels.FindByName(projectResource, name), channel.Identifier); + await resource.UpdateWith(channel, _repository); + await Upsert(_repository.Channels, resource); } private async Task UploadProjectTriggers(ProjectResource projectResource, IEnumerable triggers) diff --git a/OctopusProjectBuilder.Uploader/OctopusProjectBuilder.Uploader.csproj b/OctopusProjectBuilder.Uploader/OctopusProjectBuilder.Uploader.csproj index 51506fa..967bd82 100644 --- a/OctopusProjectBuilder.Uploader/OctopusProjectBuilder.Uploader.csproj +++ b/OctopusProjectBuilder.Uploader/OctopusProjectBuilder.Uploader.csproj @@ -7,7 +7,7 @@ - + diff --git a/OctopusProjectBuilder.Uploader.Tests/Serialization/AccountConverter.cs b/OctopusProjectBuilder.Uploader/Serialization/AccountConverter.cs similarity index 93% rename from OctopusProjectBuilder.Uploader.Tests/Serialization/AccountConverter.cs rename to OctopusProjectBuilder.Uploader/Serialization/AccountConverter.cs index d5af773..a9bf6ad 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Serialization/AccountConverter.cs +++ b/OctopusProjectBuilder.Uploader/Serialization/AccountConverter.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Octopus.Client.Model.Accounts; -namespace OctopusProjectBuilder.Uploader.Tests.Serialization +namespace OctopusProjectBuilder.Uploader { public class AccountConverter : InheritedClassConverter { diff --git a/OctopusProjectBuilder.Uploader.Tests/Serialization/ControlConverter.cs b/OctopusProjectBuilder.Uploader/Serialization/ControlConverter.cs similarity index 97% rename from OctopusProjectBuilder.Uploader.Tests/Serialization/ControlConverter.cs rename to OctopusProjectBuilder.Uploader/Serialization/ControlConverter.cs index 26c4271..6e9a49c 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Serialization/ControlConverter.cs +++ b/OctopusProjectBuilder.Uploader/Serialization/ControlConverter.cs @@ -6,7 +6,7 @@ using Octopus.Client.Extensions; using Octopus.Client.Model.Forms; -namespace OctopusProjectBuilder.Uploader.Tests.Serialization +namespace OctopusProjectBuilder.Uploader { /// /// Serializes s by including and reading a custom Type property. diff --git a/OctopusProjectBuilder.Uploader.Tests/Serialization/EndpointConverter.cs b/OctopusProjectBuilder.Uploader/Serialization/EndpointConverter.cs similarity index 95% rename from OctopusProjectBuilder.Uploader.Tests/Serialization/EndpointConverter.cs rename to OctopusProjectBuilder.Uploader/Serialization/EndpointConverter.cs index 9deeb9c..aaa968e 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Serialization/EndpointConverter.cs +++ b/OctopusProjectBuilder.Uploader/Serialization/EndpointConverter.cs @@ -3,7 +3,7 @@ using Octopus.Client.Model; using Octopus.Client.Model.Endpoints; -namespace OctopusProjectBuilder.Uploader.Tests.Serialization +namespace OctopusProjectBuilder.Uploader { /// /// Serializes s by including and the CommunicationStyle property. diff --git a/OctopusProjectBuilder.Uploader.Tests/Serialization/FeedConverter.cs b/OctopusProjectBuilder.Uploader/Serialization/FeedConverter.cs similarity index 92% rename from OctopusProjectBuilder.Uploader.Tests/Serialization/FeedConverter.cs rename to OctopusProjectBuilder.Uploader/Serialization/FeedConverter.cs index cfac299..468dda9 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Serialization/FeedConverter.cs +++ b/OctopusProjectBuilder.Uploader/Serialization/FeedConverter.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Octopus.Client.Model; -namespace OctopusProjectBuilder.Uploader.Tests.Serialization +namespace OctopusProjectBuilder.Uploader { public class FeedConverter : InheritedClassConverter { diff --git a/OctopusProjectBuilder.Uploader.Tests/Serialization/HrefConverter.cs b/OctopusProjectBuilder.Uploader/Serialization/HrefConverter.cs similarity index 97% rename from OctopusProjectBuilder.Uploader.Tests/Serialization/HrefConverter.cs rename to OctopusProjectBuilder.Uploader/Serialization/HrefConverter.cs index eb1be7c..f0fd097 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Serialization/HrefConverter.cs +++ b/OctopusProjectBuilder.Uploader/Serialization/HrefConverter.cs @@ -3,7 +3,7 @@ using Octopus.Client.Extensibility; using Octopus.Client.Model; -namespace OctopusProjectBuilder.Uploader.Tests.Serialization +namespace OctopusProjectBuilder.Uploader { public class HrefConverter : JsonConverter { diff --git a/OctopusProjectBuilder.Uploader.Tests/Serialization/InheritedClassConverter.cs b/OctopusProjectBuilder.Uploader/Serialization/InheritedClassConverter.cs similarity index 98% rename from OctopusProjectBuilder.Uploader.Tests/Serialization/InheritedClassConverter.cs rename to OctopusProjectBuilder.Uploader/Serialization/InheritedClassConverter.cs index 5312c1f..bbba9aa 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Serialization/InheritedClassConverter.cs +++ b/OctopusProjectBuilder.Uploader/Serialization/InheritedClassConverter.cs @@ -6,7 +6,7 @@ using Newtonsoft.Json.Linq; using Octopus.Client.Model; -namespace OctopusProjectBuilder.Uploader.Tests.Serialization +namespace OctopusProjectBuilder.Uploader { public abstract class InheritedClassConverter : JsonConverter where TBaseResource : Resource { diff --git a/OctopusProjectBuilder.Uploader.Tests/Serialization/JsonSerialization.cs b/OctopusProjectBuilder.Uploader/Serialization/JsonSerialization.cs similarity index 95% rename from OctopusProjectBuilder.Uploader.Tests/Serialization/JsonSerialization.cs rename to OctopusProjectBuilder.Uploader/Serialization/JsonSerialization.cs index f7ecbba..a860015 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Serialization/JsonSerialization.cs +++ b/OctopusProjectBuilder.Uploader/Serialization/JsonSerialization.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace OctopusProjectBuilder.Uploader.Tests.Serialization +namespace OctopusProjectBuilder.Uploader { /// /// Support for reading and writing JSON, exposed for convenience of those using JSON.NET. diff --git a/OctopusProjectBuilder.Uploader.Tests/Serialization/PropertyValueResource.cs b/OctopusProjectBuilder.Uploader/Serialization/PropertyValueResource.cs similarity index 96% rename from OctopusProjectBuilder.Uploader.Tests/Serialization/PropertyValueResource.cs rename to OctopusProjectBuilder.Uploader/Serialization/PropertyValueResource.cs index c650823..010f58a 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Serialization/PropertyValueResource.cs +++ b/OctopusProjectBuilder.Uploader/Serialization/PropertyValueResource.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json.Linq; using Octopus.Client.Model; -namespace OctopusProjectBuilder.Uploader.Tests.Serialization +namespace OctopusProjectBuilder.Uploader { public class PropertyValueJsonConverter : JsonConverter { diff --git a/OctopusProjectBuilder.Uploader.Tests/Serialization/TriggerActionConverter.cs b/OctopusProjectBuilder.Uploader/Serialization/TriggerActionConverter.cs similarity index 91% rename from OctopusProjectBuilder.Uploader.Tests/Serialization/TriggerActionConverter.cs rename to OctopusProjectBuilder.Uploader/Serialization/TriggerActionConverter.cs index 0589242..1434e4d 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Serialization/TriggerActionConverter.cs +++ b/OctopusProjectBuilder.Uploader/Serialization/TriggerActionConverter.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Octopus.Client.Model.Triggers; -namespace OctopusProjectBuilder.Uploader.Tests.Serialization +namespace OctopusProjectBuilder.Uploader { public class TriggerActionConverter : InheritedClassConverter { diff --git a/OctopusProjectBuilder.Uploader.Tests/Serialization/TriggerFilterConverter.cs b/OctopusProjectBuilder.Uploader/Serialization/TriggerFilterConverter.cs similarity index 91% rename from OctopusProjectBuilder.Uploader.Tests/Serialization/TriggerFilterConverter.cs rename to OctopusProjectBuilder.Uploader/Serialization/TriggerFilterConverter.cs index 0af84e1..53d2256 100644 --- a/OctopusProjectBuilder.Uploader.Tests/Serialization/TriggerFilterConverter.cs +++ b/OctopusProjectBuilder.Uploader/Serialization/TriggerFilterConverter.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Octopus.Client.Model.Triggers; -namespace OctopusProjectBuilder.Uploader.Tests.Serialization +namespace OctopusProjectBuilder.Uploader { public class TriggerFilterConverter : InheritedClassConverter { diff --git a/OctopusProjectBuilder.YamlReader/Model/Templates/YamlRunbookTemplate.cs b/OctopusProjectBuilder.YamlReader/Model/Templates/YamlRunbookTemplate.cs new file mode 100644 index 0000000..9d80ec2 --- /dev/null +++ b/OctopusProjectBuilder.YamlReader/Model/Templates/YamlRunbookTemplate.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel; +using YamlDotNet.Serialization; + +namespace OctopusProjectBuilder.YamlReader.Model.Templates +{ + [Description("Runbook Template model definition.")] + [Serializable] + public class YamlRunbookTemplate : YamlRunbook, IYamlTemplate + { + [Description("Unique template name.")] + [YamlMember(Order = -2)] + public string TemplateName { get; set; } + + [Description("List of template parameters, where accepted names should consist of alphanumeric characters and/or underscores. If template is not parameterized, the list should be left empty or undefined.")] + [YamlMember(Order = -1)] + public string[] TemplateParameters { get; set; } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/Templates/YamlTemplates.cs b/OctopusProjectBuilder.YamlReader/Model/Templates/YamlTemplates.cs index 995d772..940fa5e 100644 --- a/OctopusProjectBuilder.YamlReader/Model/Templates/YamlTemplates.cs +++ b/OctopusProjectBuilder.YamlReader/Model/Templates/YamlTemplates.cs @@ -43,6 +43,8 @@ public class YamlTemplates public YamlDeploymentStepTemplate[] DeploymentSteps { get; set; } [Description("List of Project templates")] public YamlProjectTemplate[] Projects { get; set; } + [Description("List of Runbook templates")] + public YamlRunbookTemplate[] Runbooks { get; set; } public static YamlTemplates MergeIn(YamlTemplates dst, YamlTemplates src) { @@ -53,6 +55,7 @@ public static YamlTemplates MergeIn(YamlTemplates dst, YamlTemplates src) dst.DeploymentActions = dst.MergeItemsIn(src, x => x.DeploymentActions); dst.DeploymentSteps = dst.MergeItemsIn(src, x => x.DeploymentSteps); dst.Projects = dst.MergeItemsIn(src, x => x.Projects); + dst.Runbooks = dst.MergeItemsIn(src, x => x.Runbooks); return dst; } } diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlChannel.cs b/OctopusProjectBuilder.YamlReader/Model/YamlChannel.cs new file mode 100644 index 0000000..97eb08e --- /dev/null +++ b/OctopusProjectBuilder.YamlReader/Model/YamlChannel.cs @@ -0,0 +1,63 @@ +using System; +using System.ComponentModel; +using System.Linq; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; +using OctopusProjectBuilder.YamlReader.Helpers; +using YamlDotNet.Serialization; + +namespace OctopusProjectBuilder.YamlReader.Model +{ + [Description("Octopus Channel model.")] + [Serializable] + public class YamlChannel : YamlNamedElement + { + [Description("Description.")] + [YamlMember(Order = 3)] + public string Description { get; set; } + + [Description("Project name.")] + [YamlMember(Order = 4)] + public string ProjectName { get; set; } + + [Description("Channel default flag.")] + [YamlMember(Order = 5)] + public bool? IsDefault { get; set; } = false; + + [Description("Channel lifecycle name.")] + [YamlMember(Order = 6)] + public string LifecycleName { get; set; } + + [Description("Channel version rules.")] + [YamlMember(Order = 7)] + public YamlChannelVersionRule[] VersionRules { get; set; } + + [Description("List of TenantTag references")] + [YamlMember(Order = 8)] + public string[] TenantTagRefs { get; set; } + + public Channel ToModel() + { + return new Channel(ToModelName(), + Description, ProjectName, IsDefault, LifecycleName, + VersionRules.Select(rule => rule.ToModel()), + TenantTagRefs.EnsureNotNull().Select(t => new ElementReference(t)).ToArray()); + } + + public static YamlChannel FromModel(Channel model) + { + return new YamlChannel + { + Name = model.Identifier.Name, + RenamedFrom = model.Identifier.RenamedFrom, + Description = string.IsNullOrEmpty(model.Description) ? null : model.Description, + ProjectName = model.ProjectName, + IsDefault = model.IsDefault, + LifecycleName = string.IsNullOrEmpty(model.LifecycleName) ? null : model.LifecycleName, + VersionRules = model.VersionRules.Select(rule => YamlChannelVersionRule.FromModel(rule)).ToArray(), + TenantTagRefs = model.TenantTags.Select(t => t.Name).ToArray().NullIfEmpty(), + }; + } + + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlChannelVersionRule.cs b/OctopusProjectBuilder.YamlReader/Model/YamlChannelVersionRule.cs new file mode 100644 index 0000000..9e8c3c5 --- /dev/null +++ b/OctopusProjectBuilder.YamlReader/Model/YamlChannelVersionRule.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel; +using System.Linq; +using OctopusProjectBuilder.Model; +using YamlDotNet.Serialization; + +namespace OctopusProjectBuilder.YamlReader.Model +{ + [Description("Octopus Verison Rule model.")] + [Serializable] + public class YamlChannelVersionRule + { + [Description("Channel version range.")] + [YamlMember(Order = 1)] + public string VersionRange { get; set; } + + [Description("Release tag.")] + [YamlMember(Order = 2)] + public string Tag { get; set; } + + [Description("Action package names.")] + [YamlMember(Order = 3)] + public YamlChannelVersionRulePackage[] ActionPackages { get; set; } + + public ChannelVersionRule ToModel() + { + return new ChannelVersionRule(Tag, VersionRange, ActionPackages.Select(package => package.ToModel()).ToList()); + } + + public static YamlChannelVersionRule FromModel(ChannelVersionRule model) + { + return new YamlChannelVersionRule + { + Tag = string.IsNullOrEmpty(model.Tag) ? null : model.Tag, + VersionRange = string.IsNullOrEmpty(model.VersionRange) ? null : model.VersionRange, + ActionPackages = model.ActionPackages.Select(package => YamlChannelVersionRulePackage.FromModel(package)).ToArray() + }; + } + + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlChannelVersionRulePackage.cs b/OctopusProjectBuilder.YamlReader/Model/YamlChannelVersionRulePackage.cs new file mode 100644 index 0000000..134458a --- /dev/null +++ b/OctopusProjectBuilder.YamlReader/Model/YamlChannelVersionRulePackage.cs @@ -0,0 +1,35 @@ +using System; +using System.ComponentModel; +using OctopusProjectBuilder.Model; +using YamlDotNet.Serialization; + +namespace OctopusProjectBuilder.YamlReader.Model +{ + [Description("Octopus Channel model.")] + [Serializable] + public class YamlChannelVersionRulePackage + { + [Description("Deployment action name.")] + [YamlMember(Order = 1)] + public string DeploymentAction { get; set; } + + [Description("Package reference.")] + [YamlMember(Order = 2)] + public string PackageReference { get; set; } + + public static YamlChannelVersionRulePackage FromModel(ChannelVersionRulePackage model) + { + return new YamlChannelVersionRulePackage() + { + DeploymentAction = string.IsNullOrEmpty(model.DeploymentAction) ? null : model.DeploymentAction, + PackageReference = string.IsNullOrEmpty(model.PackageReference) ? null : model.PackageReference + }; + } + + public ChannelVersionRulePackage ToModel() + { + return new ChannelVersionRulePackage(DeploymentAction, PackageReference); + } + + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlDeploymentAction.cs b/OctopusProjectBuilder.YamlReader/Model/YamlDeploymentAction.cs index fe7b8ed..a57ddb3 100644 --- a/OctopusProjectBuilder.YamlReader/Model/YamlDeploymentAction.cs +++ b/OctopusProjectBuilder.YamlReader/Model/YamlDeploymentAction.cs @@ -16,21 +16,33 @@ public class YamlDeploymentAction : IYamlTemplateBased [Description("Unique name.")] [YamlMember(Order = 1)] public string Name { get; set; } + + [Description("Defines if this action is disabled.")] + [YamlMember(Order = 2)] + public bool IsDisabled { get; set; } [Description("Indicates that the resource is template based.")] - [YamlMember(Order = 2)] + [YamlMember(Order = 3)] public YamlTemplateReference UseTemplate { get; set; } [Description("Action type.")] - [YamlMember(Order = 3)] + [YamlMember(Order = 4)] public string ActionType { get; set; } + + [Description("Action run condition.")] + [YamlMember(Order = 5)] + public DeploymentAction.ActionCondition Condition { get; set; } [Description("List of Environment references (based on the name) where action would be performed on. If none are specified, then action would be performed on all environments.")] - [YamlMember(Order = 4)] + [YamlMember(Order = 6)] public string[] EnvironmentRefs { get; set; } + + [Description("Action package references.")] + [YamlMember(Order = 7)] + public YamlDeploymentActionPackage[] Packages { get; set; } [Description("Action properties.")] - [YamlMember(Order = 5)] + [YamlMember(Order = 8)] public YamlPropertyValue[] Properties { get; set; } public void ApplyTemplate(YamlTemplates templates) @@ -43,6 +55,8 @@ public static YamlDeploymentAction FromModel(DeploymentAction model) return new YamlDeploymentAction { Name = model.Name, + IsDisabled = model.IsDisabled, + Condition = model.Condition, ActionType = model.ActionType, Properties = YamlPropertyValue.FromModel(model.Properties), EnvironmentRefs = model.EnvironmentRefs.Select(r => r.Name).ToArray().NullIfEmpty() @@ -51,7 +65,10 @@ public static YamlDeploymentAction FromModel(DeploymentAction model) public DeploymentAction ToModel() { - return new DeploymentAction(Name, ActionType, YamlPropertyValue.ToModel(Properties), EnvironmentRefs.EnsureNotNull().Select(name => new ElementReference(name))); + return new DeploymentAction(Name, IsDisabled, Condition, ActionType, + YamlPropertyValue.ToModel(Properties), + EnvironmentRefs.EnsureNotNull().Select(name => new ElementReference(name)), + Packages.EnsureNotNull().Select(x => x.ToModel())); } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlDeploymentActionPackage.cs b/OctopusProjectBuilder.YamlReader/Model/YamlDeploymentActionPackage.cs new file mode 100644 index 0000000..f65f1f1 --- /dev/null +++ b/OctopusProjectBuilder.YamlReader/Model/YamlDeploymentActionPackage.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using OctopusProjectBuilder.Model; +using OctopusProjectBuilder.YamlReader.Helpers; +using OctopusProjectBuilder.YamlReader.Model.Templates; +using YamlDotNet.Serialization; + +namespace OctopusProjectBuilder.YamlReader.Model +{ + [Serializable] + public class YamlDeploymentActionPackage + { + [Description("Unique name.")] + [YamlMember(Order = 1)] + public string Name { get; set; } + + [Description("Package ID.")] + [YamlMember(Order = 2)] + public string PackageId { get; set; } + + [Description("Feed ID.")] + [YamlMember(Order = 3)] + public string FeedId { get; set; } + + [Description("Acquisition location.")] + [YamlMember(Order = 4)] + public string AcquisitionLocation { get; set; } + + [Description("Properties")] + [YamlMember(Order = 5)] + public IDictionary Properties { get; set; } = new Dictionary(); + + public static YamlDeploymentActionPackage FromModel(DeploymentActionPackage model) + { + return new YamlDeploymentActionPackage + { + Name = model.Name, + PackageId = model.PackageId, + FeedId = model.FeedId, + AcquisitionLocation = model.AcquisitionLocation, + Properties = model.Properties + }; + } + + public DeploymentActionPackage ToModel() + { + return new DeploymentActionPackage() + { + Name = Name, + PackageId = PackageId, + FeedId = FeedId, + AcquisitionLocation = AcquisitionLocation, + Properties = Properties + }; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlDeploymentConnectivityPolicy.cs b/OctopusProjectBuilder.YamlReader/Model/YamlDeploymentConnectivityPolicy.cs new file mode 100644 index 0000000..053ba63 --- /dev/null +++ b/OctopusProjectBuilder.YamlReader/Model/YamlDeploymentConnectivityPolicy.cs @@ -0,0 +1,52 @@ +using System; +using System.ComponentModel; +using System.Linq; +using OctopusProjectBuilder.Model; +using OctopusProjectBuilder.YamlReader.Helpers; +using OctopusProjectBuilder.YamlReader.Model.Templates; +using YamlDotNet.Serialization; + +namespace OctopusProjectBuilder.YamlReader.Model +{ + [Serializable] + public class YamlDeploymentConnectivityPolicy + { + [Description("Skip machine behavior.")] + [YamlMember(Order = 1)] + public SkipMachineBehavior SkipMachineBehavior { get; set; } + + [Description("Target roles.")] + [YamlMember(Order = 2)] + public string[] TargetRoles { get; set; } + + [Description("Allow deployments to no targets.")] + [YamlMember(Order = 3)] + public bool AllowDeploymentsToNoTargets { get; set; } + + [Description("Exclude unhealthy targets.")] + [YamlMember(Order = 4)] + public bool ExcludeUnhealthyTargets { get; set; } + + public static YamlDeploymentConnectivityPolicy FromModel(DeploymentConnectivityPolicy model) + { + return new YamlDeploymentConnectivityPolicy + { + SkipMachineBehavior = (SkipMachineBehavior) model.SkipMachineBehavior, + TargetRoles = model.TargetRoles.Select(r => r.Name).ToArray().NullIfEmpty(), + AllowDeploymentsToNoTargets = model.AllowDeploymentsToNoTargets, + ExcludeUnhealthyTargets = model.ExcludeUnhealthyTargets + }; + } + + public DeploymentConnectivityPolicy ToModel() + { + return new DeploymentConnectivityPolicy + { + SkipMachineBehavior = (Octopus.Client.Model.SkipMachineBehavior) SkipMachineBehavior, + TargetRoles = TargetRoles?.Select(t => new ElementReference(t)).ToArray(), + AllowDeploymentsToNoTargets = AllowDeploymentsToNoTargets, + ExcludeUnhealthyTargets = ExcludeUnhealthyTargets + }; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlOctopusModel.cs b/OctopusProjectBuilder.YamlReader/Model/YamlOctopusModel.cs index f908530..1f75d3e 100644 --- a/OctopusProjectBuilder.YamlReader/Model/YamlOctopusModel.cs +++ b/OctopusProjectBuilder.YamlReader/Model/YamlOctopusModel.cs @@ -13,18 +13,23 @@ public class YamlOctopusModel { public YamlOctopusModel() { } - private YamlOctopusModel(YamlMachinePolicy[] machinePolicies, YamlEnvironment[] environments, YamlProjectGroup[] projectGroups, YamlProject[] projects, YamlLifecycle[] lifecycles, YamlLibraryVariableSet[] libraryVariableSets, YamlUserRole[] userRoles, YamlTeam[] teams, YamlTenant[] tenants, YamlTagSet[] tagsets) + private YamlOctopusModel(YamlMachinePolicy[] machinePolicies, YamlEnvironment[] environments, + YamlProjectGroup[] projectGroups, YamlProject[] projects, YamlChannel[] channels, YamlLifecycle[] lifecycles, + YamlLibraryVariableSet[] libraryVariableSets, YamlUserRole[] userRoles, YamlTeam[] teams, YamlTenant[] tenants, + YamlTagSet[] tagsets, YamlRunbook[] runbooks) { MachinePolicies = machinePolicies; Environments = environments; ProjectGroups = projectGroups; Projects = projects; + Channels = channels; Lifecycles = lifecycles; LibraryVariableSets = libraryVariableSets; UserRoles = userRoles; Teams = teams; Tenants = tenants; TagSets = tagsets; + Runbooks = runbooks; } [Description("List of Machine Policies.")] @@ -35,6 +40,8 @@ private YamlOctopusModel(YamlMachinePolicy[] machinePolicies, YamlEnvironment[] public YamlProjectGroup[] ProjectGroups { get; set; } [Description("List of Projects.")] public YamlProject[] Projects { get; set; } + [Description("List of Channels.")] + public YamlChannel[] Channels { get; set; } [Description("List of Lifecycles.")] public YamlLifecycle[] Lifecycles { get; set; } [Description("List of Library Variable Sets (including Script modules).")] @@ -47,6 +54,8 @@ private YamlOctopusModel(YamlMachinePolicy[] machinePolicies, YamlEnvironment[] public YamlTenant[] Tenants { get; set; } [Description("List of tagsets")] public YamlTagSet[] TagSets { get; set; } + [Description("List of runbooks")] + public YamlRunbook[] Runbooks { get; set; } [Description("Templates node allowing to define templates for other octopus model elements.")] public YamlTemplates Templates { get; set; } @@ -69,6 +78,9 @@ public SystemModelBuilder BuildWith(SystemModelBuilder builder) foreach (var project in Projects.EnsureNotNull()) builder.AddProject(project.ToModel()); + foreach (var channel in Channels.EnsureNotNull()) + builder.AddChannel(channel.ToModel()); + foreach (var lifecycle in Lifecycles.EnsureNotNull()) builder.AddLifecycle(lifecycle.ToModel()); @@ -89,6 +101,9 @@ public SystemModelBuilder BuildWith(SystemModelBuilder builder) foreach (var tagset in TagSets.EnsureNotNull()) builder.AddTagSet(tagset.ToModel()); + + foreach (var runbook in Runbooks.EnsureNotNull()) + builder.AddRunbook(runbook.ToModel()); return builder; } @@ -100,12 +115,14 @@ public static YamlOctopusModel FromModel(SystemModel model) model.Environments.Select(YamlEnvironment.FromModel).ToArray().NullIfEmpty(), model.ProjectGroups.Select(YamlProjectGroup.FromModel).ToArray().NullIfEmpty(), model.Projects.Select(YamlProject.FromModel).ToArray().NullIfEmpty(), + model.Channels.Select(YamlChannel.FromModel).ToArray().NullIfEmpty(), model.Lifecycles.Select(YamlLifecycle.FromModel).ToArray().NullIfEmpty(), model.LibraryVariableSets.Select(YamlLibraryVariableSet.FromModel).ToArray().NullIfEmpty(), model.UserRoles.Select(YamlUserRole.FromModel).ToArray().NullIfEmpty(), model.Teams.Select(YamlTeam.FromModel).ToArray().NullIfEmpty(), model.Tenants.Select(YamlTenant.FromModel).ToArray().NullIfEmpty(), - model.TagSets.Select(YamlTagSet.FromModel).ToArray().NullIfEmpty()); + model.TagSets.Select(YamlTagSet.FromModel).ToArray().NullIfEmpty(), + model.Runbooks.Select(YamlRunbook.FromModel).ToArray().NullIfEmpty()); } public YamlOctopusModel MergeIn(YamlOctopusModel model) @@ -116,10 +133,12 @@ public YamlOctopusModel MergeIn(YamlOctopusModel model) LibraryVariableSets = this.MergeItemsIn(model, x => x.LibraryVariableSets); Lifecycles = this.MergeItemsIn(model, x => x.Lifecycles); Projects = this.MergeItemsIn(model, x => x.Projects); + Channels = this.MergeItemsIn(model, x => x.Channels); UserRoles = this.MergeItemsIn(model, x => x.UserRoles); Teams = this.MergeItemsIn(model, x => x.Teams); Tenants = this.MergeItemsIn(model, x => x.Tenants); TagSets = this.MergeItemsIn(model, x => x.TagSets); + Runbooks = this.MergeItemsIn(model, x => x.Runbooks); Templates = YamlTemplates.MergeIn(Templates, model.Templates); return this; } diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlProject.cs b/OctopusProjectBuilder.YamlReader/Model/YamlProject.cs index f594812..e509108 100644 --- a/OctopusProjectBuilder.YamlReader/Model/YamlProject.cs +++ b/OctopusProjectBuilder.YamlReader/Model/YamlProject.cs @@ -34,14 +34,14 @@ public class YamlProject : YamlNamedElement, IYamlTemplateBased [Description("Disable a project to prevent releases or deployments from being created.")] [YamlMember(Order = 8)] - public bool IsDisabled { get; set; } + public bool? IsDisabled { get; set; } [YamlMember(Order = 9)] - public bool AutoCreateRelease { get; set; } + public bool? AutoCreateRelease { get; set; } [Description("Skips package deployment and installation if it is already installed.")] [YamlMember(Order = 10)] - public bool DefaultToSkipIfAlreadyInstalled { get; set; } + public bool? DefaultToSkipIfAlreadyInstalled { get; set; } [Description("Versioning strategy.")] [YamlMember(Order = 11)] @@ -58,9 +58,13 @@ public class YamlProject : YamlNamedElement, IYamlTemplateBased [Description("Project variables.")] [YamlMember(Order = 14)] public YamlVariable[] Variables { get; set; } + + [Description("Project templates.")] + [YamlMember(Order = 15)] + public YamlTemplateVariable[] Templates { get; set; } [Description("Project triggers.")] - [YamlMember(Order = 15)] + [YamlMember(Order = 16)] public YamlProjectTrigger[] Triggers { get; set; } public void ApplyTemplate(YamlTemplates templates) @@ -72,20 +76,25 @@ public void ApplyTemplate(YamlTemplates templates) public Project ToModel() { + TenantedDeploymentMode? tenantedDeploymentMode = TenantedDeploymentMode != null + ? (TenantedDeploymentMode?) Enum.Parse(typeof(TenantedDeploymentMode), TenantedDeploymentMode) + : null; + return new Project( ToModelName(), Description, IsDisabled, AutoCreateRelease, DefaultToSkipIfAlreadyInstalled, - DeploymentProcess.ToModel(), - Variables.EnsureNotNull().Select(v => v.ToModel()), - IncludedLibraryVariableSetRefs.EnsureNotNull().Select(reference => new ElementReference(reference)), + DeploymentProcess?.ToModel(), + Variables?.Select(v => v.ToModel()), + IncludedLibraryVariableSetRefs?.Select(reference => new ElementReference(reference)), new ElementReference(LifecycleRef), new ElementReference(ProjectGroupRef), VersioningStrategy?.ToModel(), - Triggers.EnsureNotNull().Select(t => t.ToModel()), - (TenantedDeploymentMode)Enum.Parse(typeof(TenantedDeploymentMode), TenantedDeploymentMode ?? default(TenantedDeploymentMode).ToString())); + Triggers?.Select(t => t.ToModel()), + tenantedDeploymentMode, + Templates?.Select(t => t.ToModel())); } public static YamlProject FromModel(Project model) @@ -105,7 +114,8 @@ public static YamlProject FromModel(Project model) IncludedLibraryVariableSetRefs = model.IncludedLibraryVariableSetRefs.Select(r => r.Name).ToArray().NullIfEmpty(), VersioningStrategy = YamlVersioningStrategy.FromModel(model.VersioningStrategy), Triggers = model.Triggers.Select(YamlProjectTrigger.FromModel).ToArray().NullIfEmpty(), - TenantedDeploymentMode = model.TenantedDeploymentMode.ToString() + TenantedDeploymentMode = model.TenantedDeploymentMode.ToString(), + Templates = model.Templates.Select(YamlTemplateVariable.FromModel).ToArray().NullIfEmpty() }; } } diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlPropertyValue.cs b/OctopusProjectBuilder.YamlReader/Model/YamlPropertyValue.cs index a3df06a..ac58ad9 100644 --- a/OctopusProjectBuilder.YamlReader/Model/YamlPropertyValue.cs +++ b/OctopusProjectBuilder.YamlReader/Model/YamlPropertyValue.cs @@ -16,22 +16,30 @@ public class YamlPropertyValue [YamlMember(Order = 1)] public string Key { get; set; } - [Description("Property value.")] + [Description("Property value type. This describes the transformation that should be applied to the value of this. Options are: Literal (default), ProjectNameToId, EnvironmentNameToId, StepTemplateNameToId, FeedNameToId, MachineNameToId, SpaceNameToId, TenantNameToId, LifecycleNameToId, CertificateNameToId, ProxyNameToId, TagSetNameToId, UserRoleNameToId, RunbookNameToId")] [YamlMember(Order = 2)] + public string ValueType { get; set; } = "Literal"; + + [Description("Property value.")] + [YamlMember(Order = 3)] public string Value { get; set; } + + [Description("An optionally provided path to a file to override Value with.")] + [YamlMember(Order = 4)] + public string File { get; set; } [Description("Should Octopus store this property value in encrypted format? \\(Please note that at this moment the sensitive values have to be stored in plain text in yaml definition.\\)")] - [YamlMember(Order = 3)] + [YamlMember(Order = 4)] public bool IsSensitive { get; set; } - public static IReadOnlyDictionary ToModel(YamlPropertyValue[] properties) + public static IDictionary ToModel(YamlPropertyValue[] properties) { - return properties.EnsureNotNull().ToDictionary(kv => kv.Key, kv => new PropertyValue(kv.IsSensitive, kv.Value)); + return properties.EnsureNotNull().ToDictionary(kv => kv.Key, kv => new PropertyValue(kv.IsSensitive, kv.File != null ? System.IO.File.ReadAllText(kv.File) : kv.Value, kv.ValueType)); } - public static YamlPropertyValue[] FromModel(IReadOnlyDictionary properties) + public static YamlPropertyValue[] FromModel(IDictionary properties) { - return properties.EnsureNotNull().Select(kv => new YamlPropertyValue { IsSensitive = kv.Value.IsSensitive, Value = kv.Value.Value, Key = kv.Key }).ToArray().NullIfEmpty(); + return properties.EnsureNotNull().Select(kv => new YamlPropertyValue { IsSensitive = kv.Value.IsSensitive, Value = kv.Value.Value, ValueType = kv.Value.ValueType, Key = kv.Key }).ToArray().NullIfEmpty(); } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlRunbook.cs b/OctopusProjectBuilder.YamlReader/Model/YamlRunbook.cs new file mode 100644 index 0000000..3e0536d --- /dev/null +++ b/OctopusProjectBuilder.YamlReader/Model/YamlRunbook.cs @@ -0,0 +1,96 @@ +using System; +using System.ComponentModel; +using System.Linq; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; +using OctopusProjectBuilder.YamlReader.Helpers; +using OctopusProjectBuilder.YamlReader.Model.Templates; +using YamlDotNet.Serialization; +using GuidedFailureMode = OctopusProjectBuilder.Model.GuidedFailureMode; +using RunbookEnvironmentScope = OctopusProjectBuilder.Model.RunbookEnvironmentScope; +using RunbookRetentionPeriod = OctopusProjectBuilder.Model.RunbookRetentionPeriod; +using TenantedDeploymentMode = OctopusProjectBuilder.Model.TenantedDeploymentMode; + +namespace OctopusProjectBuilder.YamlReader.Model +{ + [Description("Octopus Runbook model.")] + [Serializable] + public class YamlRunbook : YamlNamedElement, IYamlTemplateBased + { + [Description("Indicates that the resource is template based.")] + [YamlMember(Order = 3)] + public YamlTemplateReference UseTemplate { get; set; } + + [Description("Description.")] + [YamlMember(Order = 4)] + public string Description { get; set; } + + [Description("Project name.")] + [YamlMember(Order = 5)] + public string ProjectName { get; set; } + + [Description("Runbook environment scope.")] + [YamlMember(Order = 6)] + public RunbookEnvironmentScope? EnvironmentScope { get; set; } + + [Description("List of Environment references (based on the name) where runbook would be performed on.")] + [YamlMember(Order = 7)] + public string[] EnvironmentRefs { get; set; } + + [Description("Runbook tenanted deployment mode.")] + [YamlMember(Order = 8)] + public TenantedDeploymentMode? MultiTenancyMode { get; set; } + + [Description("Runbook retention policy.")] + [YamlMember(Order = 9)] + public RunbookRetentionPeriod RetentionPolicy { get; set; } + + [Description("Runbook guided failure mode.")] + [YamlMember(Order = 10)] + public GuidedFailureMode? GuidedFailureMode { get; set; } + + [Description("Runbook process definition.")] + [YamlMember(Order = 11)] + public YamlRunbookProcess Process { get; set; } + + public void ApplyTemplate(YamlTemplates templates) + { + this.ApplyTemplate(templates?.Runbooks); + foreach (var step in (Process?.Steps).EnsureNotNull()) + step.ApplyTemplate(templates); + } + + public Runbook ToModel() + { + return new Runbook() + { + Identifier = ToModelName(), + ProjectName = ProjectName, + Description = Description, + Process = Process?.ToModel(), + MultiTenancyMode = MultiTenancyMode, + EnvironmentScope = EnvironmentScope, + RunRetentionPolicy = RetentionPolicy, + GuidedFailureMode = GuidedFailureMode, + EnvironmentRefs = EnvironmentRefs.EnsureNotNull().Select(name => new ElementReference(name)) + }; + } + + public static YamlRunbook FromModel(Runbook model) + { + return new YamlRunbook + { + Name = model.Identifier.Name, + RenamedFrom = model.Identifier.RenamedFrom, + ProjectName = model.ProjectName, + Description = model.Description, + Process = YamlRunbookProcess.FromModel(model.Process), + MultiTenancyMode = model.MultiTenancyMode, + EnvironmentScope = model.EnvironmentScope, + RetentionPolicy = model.RunRetentionPolicy, + GuidedFailureMode = model.GuidedFailureMode, + EnvironmentRefs = model.EnvironmentRefs.Select(r => r.Name).ToArray().NullIfEmpty() + }; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlRunbookProcess.cs b/OctopusProjectBuilder.YamlReader/Model/YamlRunbookProcess.cs new file mode 100644 index 0000000..d90b0b7 --- /dev/null +++ b/OctopusProjectBuilder.YamlReader/Model/YamlRunbookProcess.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel; +using System.Linq; +using OctopusProjectBuilder.Model; + +namespace OctopusProjectBuilder.YamlReader.Model +{ + [Description("Runbook deployment process definition.")] + [Serializable] + public class YamlRunbookProcess + { + [Description("List of steps to execute.")] + public YamlDeploymentStep[] Steps { get; set; } + + public RunbookProcess ToModel() + { + return new RunbookProcess(Steps.Select(s => s.ToModel())); + } + + public static YamlRunbookProcess FromModel(RunbookProcess model) + { + return new YamlRunbookProcess + { + Steps = model.DeploymentSteps.Select(YamlDeploymentStep.FromModel).ToArray() + }; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlTemplateVariable.cs b/OctopusProjectBuilder.YamlReader/Model/YamlTemplateVariable.cs new file mode 100644 index 0000000..c5c8de4 --- /dev/null +++ b/OctopusProjectBuilder.YamlReader/Model/YamlTemplateVariable.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Octopus.Client.Model; +using OctopusProjectBuilder.Model; +using YamlDotNet.Core.Tokens; +using YamlDotNet.Serialization; + +namespace OctopusProjectBuilder.YamlReader.Model +{ + [Description("Project Template Variable definition.")] + [Serializable] + public class YamlTemplateVariable + { + [Description("Template Id")] + [YamlMember(Order = 1)] + public string Id { get; set; } = ""; + + [Description("Variable name.")] + [YamlMember(Order = 2)] + public string Name { get; set; } + + [Description("Variable label.")] + [YamlMember(Order = 3)] + public string Label { get; set; } + + [Description("Default Value")] + [YamlMember(Order = 4)] + [DefaultValue("")] + public string DefaultValue { get; set; } = ""; + + [Description("Display Settings")] + [YamlMember(Order = 5)] + public IDictionary DisplaySettings { get; set; } = new Dictionary(); + + [Description("Help Text")] + [YamlMember(Order = 6)] + [DefaultValue("")] + public string HelpText { get; set; } = ""; + + [Description("Is sensitive")] + [YamlMember(Order = 6)] + [DefaultValue(false)] + public bool IsSensitive { get; set; } = false; + + public static YamlTemplateVariable FromModel(ActionTemplateParameterResource model) + { + return new YamlTemplateVariable + { + Id = model.Id, + Name = model.Name, + Label = model.Label, + DefaultValue = model.DefaultValue.Value, + IsSensitive = model.DefaultValue.IsSensitive, + HelpText = model.HelpText, + DisplaySettings = model.DisplaySettings, + }; + } + + public ActionTemplateParameterResource ToModel() + { + return new ActionTemplateParameterResource() + { + Id = Id, + Name = Name, + Label = Label, + DefaultValue = new PropertyValueResource(DefaultValue, IsSensitive), + HelpText = HelpText, + DisplaySettings = DisplaySettings + }; + } + } +} \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/Model/YamlVariable.cs b/OctopusProjectBuilder.YamlReader/Model/YamlVariable.cs index 244d7e7..2878ca4 100644 --- a/OctopusProjectBuilder.YamlReader/Model/YamlVariable.cs +++ b/OctopusProjectBuilder.YamlReader/Model/YamlVariable.cs @@ -31,6 +31,10 @@ public class YamlVariable [YamlMember(Order = 6)] public YamlVariablePrompt Prompt { get; set; } + + [YamlMember(Order = 7)] + [Description("Variable file value. \\(Please note that OctopusProjectBuilder is not able to retrieve values of sensitive variables from Octopus\\)")] + public string File { get; set; } public static YamlVariable FromModel(Variable model) { @@ -47,7 +51,7 @@ public static YamlVariable FromModel(Variable model) public Variable ToModel() { - return new Variable(Name, IsEditable, IsSensitive, Value, (Scope ?? new YamlVariableScope()).ToModel(), Prompt?.ToModel()); + return new Variable(Name, IsEditable, IsSensitive, (File != null ? System.IO.File.ReadAllText(File) : Value), (Scope ?? new YamlVariableScope()).ToModel(), Prompt?.ToModel()); } } } \ No newline at end of file diff --git a/OctopusProjectBuilder.YamlReader/YamlSystemModelRepository.cs b/OctopusProjectBuilder.YamlReader/YamlSystemModelRepository.cs index 4451507..c83b501 100644 --- a/OctopusProjectBuilder.YamlReader/YamlSystemModelRepository.cs +++ b/OctopusProjectBuilder.YamlReader/YamlSystemModelRepository.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using OctopusProjectBuilder.Model; using OctopusProjectBuilder.YamlReader.Helpers; @@ -28,6 +30,11 @@ public SystemModel Load(string modelDirectory) return model.ApplyTemplates().BuildWith(new SystemModelBuilder()).Build(); } + public void Save(SystemModel model, string modelDirectory) + { + Save(model, modelDirectory, async yaml => { }); + } + private YamlOctopusModel[] LoadModels(string path) { _logger.LogInformation($"Loading: {Path.GetFileName(path)}"); @@ -51,11 +58,16 @@ private YamlOctopusModel[] ReadFile(string file) return _reader.Read(stream); } - public void Save(SystemModel model, string modelDirectory) + public async Task Save(SystemModel model, string modelDirectory, Func modelManipulator) { Directory.CreateDirectory(modelDirectory); foreach (var splitModel in model.SplitModel().Select(YamlOctopusModel.FromModel)) + { + if (splitModel != null) + await modelManipulator(splitModel); + SaveModel(splitModel, GetModelPath(splitModel, modelDirectory)); + } } private void SaveModel(YamlOctopusModel model, string path) @@ -76,12 +88,14 @@ private string GetModelPath(YamlOctopusModel splitModel, string modelDirectory) .Concat(splitModel.Environments.EnsureNotNull().Select(x => $"Environment_{x.Name.SanitiseNameIfNeeded()}.yml")) .Concat(splitModel.ProjectGroups.EnsureNotNull().Select(x => $"ProjectGroup_{x.Name.SanitiseNameIfNeeded()}.yml")) .Concat(splitModel.Projects.EnsureNotNull().Select(x => $"Project_{x.Name.SanitiseNameIfNeeded()}.yml")) + .Concat(splitModel.Channels.EnsureNotNull().Select(x => $"Channel_{x.ProjectName.SanitiseNameIfNeeded()}_{x.Name.SanitiseNameIfNeeded()}.yml")) .Concat(splitModel.Lifecycles.EnsureNotNull().Select(x => $"Lifecycle_{x.Name.SanitiseNameIfNeeded()}.yml")) .Concat(splitModel.LibraryVariableSets.EnsureNotNull().Select(x => $"LibraryVariableSet_{x.Name.SanitiseNameIfNeeded()}.yml")) .Concat(splitModel.UserRoles.EnsureNotNull().Select(x => $"UserRole_{x.Name.SanitiseNameIfNeeded()}.yml")) .Concat(splitModel.Teams.EnsureNotNull().Select(x => $"Team_{x.Name.SanitiseNameIfNeeded()}.yml")) .Concat(splitModel.Tenants.EnsureNotNull().Select(x => $"Tenant_{x.Name.SanitiseNameIfNeeded()}.yml")) .Concat(splitModel.TagSets.EnsureNotNull().Select(x => $"TagSet_{x.Name.SanitiseNameIfNeeded()}.yml")) + .Concat(splitModel.Runbooks.EnsureNotNull().Select(x => $"Runbook_{x.ProjectName.SanitiseNameIfNeeded()}_{x.Name.SanitiseNameIfNeeded()}.yml")) .Single(); return Path.Combine(modelDirectory, name); } diff --git a/OctopusProjectBuilder.sln b/OctopusProjectBuilder.sln index 6148c63..295084d 100644 --- a/OctopusProjectBuilder.sln +++ b/OctopusProjectBuilder.sln @@ -11,8 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution LICENSE = LICENSE LICENSE-YamlDotNet = LICENSE-YamlDotNet Manual.md = Manual.md - OctopusProjectBuilder.Console.nuspec = OctopusProjectBuilder.Console.nuspec README.md = README.md + Jenkinsfile = Jenkinsfile EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OctopusProjectBuilder.Console", "OctopusProjectBuilder.Console\OctopusProjectBuilder.Console.csproj", "{A3E6150A-A08F-4114-A9EE-7C0CE6BF942D}" diff --git a/README.md b/README.md index 17f9eb3..45fc2c4 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ To see the yaml configuration manual, please take a look at [Configuration Manua * [Project Groups](https://github.com/Suremaker/OctopusProjectBuilder/wiki/Manual#YamlProjectGroup) * [Projects](https://github.com/Suremaker/OctopusProjectBuilder/wiki/Manual#YamlProject) + * Variables + * Variable Templates + * Runbooks + * Release Channels * [Lifecycles](https://github.com/Suremaker/OctopusProjectBuilder/wiki/Manual#YamlLifecycle) * [Library Variable Sets](https://github.com/Suremaker/OctopusProjectBuilder/wiki/Manual#YamlLibraryVariableSet) \(including script modules\) * [Environments](https://github.com/Suremaker/OctopusProjectBuilder/wiki/Manual#YamlEnvironment) @@ -34,6 +38,7 @@ OctopusProjectBuilder allows template definition for most repetative configurati * Projects - see [Project Templates](https://github.com/Suremaker/OctopusProjectBuilder/wiki/Manual#YamlProjectTemplate) * Project Steps - see [Deployment Step Templates](https://github.com/Suremaker/OctopusProjectBuilder/wiki/Manual#YamlDeploymentStepTemplate) * Project Step Actions - see [Deployment Action Templates](https://github.com/Suremaker/OctopusProjectBuilder/wiki/Manual#YamlDeploymentActionTemplate) +* Project Runbooks It is possible to create parameterized templates with ability to parameterize any properties of string type. It is also possible to use templates for lower configuration sections in template of higher configuration sections. For example The Project template can use Step templates that can be composed from Action templates. @@ -49,8 +54,11 @@ The OctopusProjectBuilder.Console.exe requires following paramters: |d:definitions|Definitions directory| |k:octopusApiKey|Octopus API key| |u:octopusUrl|Octopus Url| +|p:projectName|Project name to restrict download for| +|n:normalize|Normalize downloaded YAML and split out scripts into unique files| The **download** action allows to download the current Octopus configuration to the target directory, +the **validate** action allows to validate (stage) the downloaded YAML configuration in an existing directory, the **upload** action allows to apply the yaml configuration on Octopus server, while the **cleanupConfig** action allows to rewrite the configuration, and it is helpful to reorder nodes, reformats parameters or remove the entries with default values.