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