diff --git a/source/Calamari.Tests/KubernetesFixtures/Integration/HelmCliTests.cs b/source/Calamari.Tests/KubernetesFixtures/Integration/HelmCliTests.cs new file mode 100644 index 000000000..39946d32a --- /dev/null +++ b/source/Calamari.Tests/KubernetesFixtures/Integration/HelmCliTests.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Calamari.Common.Features.Processes; +using Calamari.Common.Plumbing.FileSystem; +using Calamari.Common.Plumbing.Variables; +using Calamari.Kubernetes; +using Calamari.Kubernetes.Integration; +using Calamari.Testing.Helpers; +using FluentAssertions; +using FluentAssertions.Execution; +using NSubstitute; +using NUnit.Framework; + +namespace Calamari.Tests.KubernetesFixtures.Integration +{ + [TestFixture] + public class HelmCliTests + { + [Test] + public void ExecutesWithBuiltInArguments() + { + const string expectedExecutable = "some-exe"; + const string expectedNamespace = "some-namespace"; + const string expectedArgument = "additional-arg"; + + var (helm, commandLineRunner, _) = GetHelmCli(); + CommandLineInvocation actual = null; + commandLineRunner.When(x => x.Execute(Arg.Any())).Do(x => actual = x.Arg()); + + helm.WithExecutable(expectedExecutable); + helm.WithNamespace(expectedNamespace); + + helm.ExecuteCommandAndReturnOutput(expectedArgument); + + using (var _ = new AssertionScope()) + { + actual.Executable.Should().BeEquivalentTo(expectedExecutable); + actual.Arguments.Should().BeEquivalentTo($"--namespace {expectedNamespace} {expectedArgument}"); + } + } + + [Test] + public void UsesCustomHelmExecutable() + { + var (helm, commandLineRunner, _) = GetHelmCli(); + CommandLineInvocation actual = null; + commandLineRunner.When(x => x.Execute(Arg.Any())).Do(x => actual = x.Arg()); + + const string expectedExecutable = "my-custom-exe"; + + helm.WithExecutable(new CalamariVariables + { + { SpecialVariables.Helm.CustomHelmExecutable, expectedExecutable } + }); + + helm.ExecuteCommandAndReturnOutput(); + + actual.Executable.Should().BeEquivalentTo(expectedExecutable); + } + + [Test] + public void UsesCustomHelmExecutableFromPackage() + { + var (helm, commandLineRunner, workingDirectory) = GetHelmCli(); + CommandLineInvocation actual = null; + commandLineRunner.When(x => x.Execute(Arg.Any())).Do(x => actual = x.Arg()); + + const string expectedExecutable = "my-custom-exe"; + const string expectedPackageKey = "helm-exe-package"; + var expectedExecutablePath = Path.Combine(workingDirectory.DirectoryPath, SpecialVariables.Helm.Packages.CustomHelmExePackageKey, expectedExecutable); + + helm.WithExecutable(new CalamariVariables + { + { SpecialVariables.Helm.CustomHelmExecutable, expectedExecutable }, + { SpecialVariables.Helm.Packages.CustomHelmExePackageKey, expectedPackageKey }, + { $"{PackageVariables.PackageCollection}[{expectedPackageKey}]", SpecialVariables.Helm.Packages.CustomHelmExePackageKey } + }); + + helm.ExecuteCommandAndReturnOutput(); + + actual.Executable.Should().BeEquivalentTo(expectedExecutablePath); + } + + [Test] + public void AlwaysUsesCustomHelmExecutableWhenRooted() + { + var (helm, commandLineRunner, _) = GetHelmCli(); + CommandLineInvocation actual = null; + commandLineRunner.When(x => x.Execute(Arg.Any())).Do(x => actual = x.Arg()); + + const string expectedExecutable = "/my-custom-exe"; + const string expectedPackageKey = "helm-exe-package"; + + helm.WithExecutable(new CalamariVariables + { + { SpecialVariables.Helm.CustomHelmExecutable, expectedExecutable }, + { SpecialVariables.Helm.Packages.CustomHelmExePackageKey, expectedPackageKey }, + { $"{PackageVariables.PackageCollection}[{expectedPackageKey}]", SpecialVariables.Helm.Packages.CustomHelmExePackageKey } + }); + + helm.ExecuteCommandAndReturnOutput(); + + actual.Executable.Should().BeEquivalentTo(expectedExecutable); + } + + static (HelmCli, ICommandLineRunner, TemporaryDirectory) GetHelmCli() + { + var memoryLog = new InMemoryLog(); + var commandLineRunner = Substitute.For(); + var workingDirectory = TemporaryDirectory.Create(); + var helm = new HelmCli(memoryLog, commandLineRunner, workingDirectory.DirectoryPath, new Dictionary()); + + return (helm, commandLineRunner, workingDirectory); + } + } +} \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs b/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs index 86d8f9899..924ceeb91 100644 --- a/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs +++ b/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs @@ -27,14 +27,14 @@ public void GivenDisabledFeatureToggle_ShouldNotPostServiceMessage() { var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + mr.ReportManifestFileApplied(filePath); memoryLog.ServiceMessages.Should().BeEmpty(); } } - + [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] - [TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] + [TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] public void GivenValidYaml_ShouldPostSingleServiceMessage(string enabledFeatureToggle) { var memoryLog = new InMemoryLog(); @@ -47,7 +47,7 @@ public void GivenValidYaml_ShouldPostSingleServiceMessage(string enabledFeatureT { var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + mr.ReportManifestFileApplied(filePath); var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", expectedJson)); memoryLog.ServiceMessages.Should().BeEquivalentTo(new List { expected }); @@ -55,7 +55,7 @@ public void GivenValidYaml_ShouldPostSingleServiceMessage(string enabledFeatureT } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] - [TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] + [TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] public void GivenInValidManifest_ShouldNotPostServiceMessage(string enabledFeatureToggle) { var memoryLog = new InMemoryLog(); @@ -67,14 +67,14 @@ public void GivenInValidManifest_ShouldNotPostServiceMessage(string enabledFeatu { var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + mr.ReportManifestFileApplied(filePath); memoryLog.ServiceMessages.Should().BeEmpty(); } } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] - [TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] + [TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] public void GivenNamespaceInManifest_ShouldReportManifestNamespace(string enabledFeatureToggle) { var memoryLog = new InMemoryLog(); @@ -89,14 +89,14 @@ public void GivenNamespaceInManifest_ShouldReportManifestNamespace(string enable variables.Set(SpecialVariables.Namespace, variableNs); var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + mr.ReportManifestFileApplied(filePath); memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "XXX")); } } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] - [TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] + [TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] public void GivenNamespaceNotInManifest_ShouldReportVariableNamespace(string enabledFeatureToggle) { var memoryLog = new InMemoryLog(); @@ -109,14 +109,14 @@ public void GivenNamespaceNotInManifest_ShouldReportVariableNamespace(string ena variables.Set(SpecialVariables.Namespace, variableNs); var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + mr.ReportManifestFileApplied(filePath); memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", variableNs)); } } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] - [TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] + [TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] public void GiveNoNamespaces_ShouldDefaultNamespace(string enabledFeatureToggle) { var memoryLog = new InMemoryLog(); @@ -127,12 +127,30 @@ public void GiveNoNamespaces_ShouldDefaultNamespace(string enabledFeatureToggle) { var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + mr.ReportManifestFileApplied(filePath); memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "default")); } } + [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] + [TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] + public void GivenValidYamlString_ShouldPostSingleServiceMessage(string enabledFeatureToggle) + { + var memoryLog = new InMemoryLog(); + var variables = new CalamariVariables(); + variables.Set(KnownVariables.EnabledFeatureToggles, enabledFeatureToggle); + + var yaml = @"foo: bar"; + var expectedJson = "{\"foo\": \"bar\"}"; + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + + mr.ReportManifestApplied(yaml); + + var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", expectedJson)); + memoryLog.ServiceMessages.Should().BeEquivalentTo(new List { expected }); + } + static IDisposable CreateFile(string yaml, out string filePath) { var tempDir = TemporaryDirectory.Create(); diff --git a/source/Calamari/Kubernetes/Commands/Executors/GatherAndApplyRawYamlExecutor.cs b/source/Calamari/Kubernetes/Commands/Executors/GatherAndApplyRawYamlExecutor.cs index f831dcaf9..7ab9f2e01 100644 --- a/source/Calamari/Kubernetes/Commands/Executors/GatherAndApplyRawYamlExecutor.cs +++ b/source/Calamari/Kubernetes/Commands/Executors/GatherAndApplyRawYamlExecutor.cs @@ -90,7 +90,7 @@ void ReportEachManifestBeingApplied(GlobDirectory globDirectory, string[] files) { var fullFilePath = fileSystem.GetRelativePath(directoryWithTrailingSlash, file); log.Verbose($"Matched file: {fullFilePath}"); - manifestReporter.ReportManifestApplied(file); + manifestReporter.ReportManifestFileApplied(file); } } } diff --git a/source/Calamari/Kubernetes/Commands/HelmUpgradeCommand.cs b/source/Calamari/Kubernetes/Commands/HelmUpgradeCommand.cs index f7f9259e2..f158e5474 100644 --- a/source/Calamari/Kubernetes/Commands/HelmUpgradeCommand.cs +++ b/source/Calamari/Kubernetes/Commands/HelmUpgradeCommand.cs @@ -36,6 +36,7 @@ public class HelmUpgradeCommand : Command readonly IExtractPackage extractPackage; readonly HelmTemplateValueSourcesParser templateValueSourcesParser; readonly ICommandLineRunner commandLineRunner; + readonly IManifestReporter manifestReporter; public HelmUpgradeCommand( ILog log, @@ -45,8 +46,8 @@ public HelmUpgradeCommand( ICalamariFileSystem fileSystem, ISubstituteInFiles substituteInFiles, IExtractPackage extractPackage, - HelmTemplateValueSourcesParser templateValueSourcesParser - ) + HelmTemplateValueSourcesParser templateValueSourcesParser, + IManifestReporter manifestReporter) { Options.Add("package=", "Path to the NuGet package to install.", v => pathToPackage = new PathToPackage(Path.GetFullPath(v))); this.log = log; @@ -56,6 +57,7 @@ HelmTemplateValueSourcesParser templateValueSourcesParser this.substituteInFiles = substituteInFiles; this.extractPackage = extractPackage; this.templateValueSourcesParser = templateValueSourcesParser; + this.manifestReporter = manifestReporter; this.commandLineRunner = commandLineRunner; } @@ -99,6 +101,7 @@ public override int Execute(string[] commandLineArguments) new DelegateInstallConvention(d => substituteInFiles.Substitute(d.CurrentDirectory, TemplateValuesFiles(d) , true)), new ConfiguredScriptConvention(new DeployConfiguredScriptBehaviour(log, fileSystem, scriptEngine, commandLineRunner)), new HelmUpgradeConvention(log, scriptEngine, commandLineRunner, fileSystem, templateValueSourcesParser), + new ReportHelmManifestConvention(log, commandLineRunner, manifestReporter), new ConfiguredScriptConvention(new PostDeployConfiguredScriptBehaviour(log, fileSystem, scriptEngine, commandLineRunner)) }); diff --git a/source/Calamari/Kubernetes/Conventions/HelmUpgradeConvention.cs b/source/Calamari/Kubernetes/Conventions/HelmUpgradeConvention.cs index 3ceb85a45..74765db34 100644 --- a/source/Calamari/Kubernetes/Conventions/HelmUpgradeConvention.cs +++ b/source/Calamari/Kubernetes/Conventions/HelmUpgradeConvention.cs @@ -62,6 +62,7 @@ public void Install(RunningDeployment deployment) } } + // This could/should be refactored to use `HelmCli` at somepoint string BuildHelmCommand(RunningDeployment deployment, ScriptSyntax syntax) { var releaseName = GetReleaseName(deployment.Variables); @@ -118,6 +119,8 @@ void SetExecutable(StringBuilder sb, ScriptSyntax syntax, string customHelmExecu { if (customHelmExecutable != null) { + // Not fixing this in my current change but the chmod here is redundant as we already try to run + // the exe as part of CheckHelmToolVersion() early in the script. // With PowerShell we need to invoke custom executables sb.Append(syntax == ScriptSyntax.PowerShell ? ". " : $"chmod +x \"{customHelmExecutable}\"\n"); sb.Append($"\"{customHelmExecutable}\""); diff --git a/source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs b/source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs new file mode 100644 index 000000000..18b74e101 --- /dev/null +++ b/source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs @@ -0,0 +1,42 @@ +using System; +using Calamari.Common.Commands; +using Calamari.Common.Features.Processes; +using Calamari.Common.FeatureToggles; +using Calamari.Common.Plumbing.Logging; +using Calamari.Deployment.Conventions; +using Calamari.Kubernetes.Integration; + +namespace Calamari.Kubernetes.Conventions +{ + public class ReportHelmManifestConvention : IInstallConvention + { + readonly ILog log; + readonly ICommandLineRunner commandLineRunner; + readonly IManifestReporter manifestReporter; + + public ReportHelmManifestConvention(ILog log, + ICommandLineRunner commandLineRunner, + IManifestReporter manifestReporter) + { + this.log = log; + this.commandLineRunner = commandLineRunner; + this.manifestReporter = manifestReporter; + } + + public void Install(RunningDeployment deployment) + { + if (!FeatureToggle.KubernetesLiveObjectStatusFeatureToggle.IsEnabled(deployment.Variables) + && !OctopusFeatureToggles.KubernetesObjectManifestInspectionFeatureToggle.IsEnabled(deployment.Variables)) + return; + + var releaseName = deployment.Variables.Get("ReleaseName"); + + var helm = new HelmCli(log, commandLineRunner, deployment.CurrentDirectory, deployment.EnvironmentVariables) + .WithExecutable(deployment.Variables) + .WithNamespace(deployment.Variables.Get(SpecialVariables.Helm.Namespace)); + + var manifest = helm.GetManifest(releaseName); + manifestReporter.ReportManifestApplied(manifest); + } + } +} \ No newline at end of file diff --git a/source/Calamari/Kubernetes/Integration/CaptureCommandOutput.cs b/source/Calamari/Kubernetes/Integration/CaptureCommandOutput.cs index c6d99d109..40cc2b1ba 100644 --- a/source/Calamari/Kubernetes/Integration/CaptureCommandOutput.cs +++ b/source/Calamari/Kubernetes/Integration/CaptureCommandOutput.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Calamari.Common.Plumbing.Commands; @@ -8,7 +9,9 @@ public interface ICommandOutput { Message[] Messages { get; } IEnumerable InfoLogs { get; } + string MergeInfoLogs(); } + public class CaptureCommandOutput : ICommandInvocationOutputSink, ICommandOutput { private readonly List messages = new List(); @@ -16,6 +19,8 @@ public class CaptureCommandOutput : ICommandInvocationOutputSink, ICommandOutput public IEnumerable InfoLogs => Messages.Where(m => m.Level == Level.Info).Select(m => m.Text).ToArray(); + public string MergeInfoLogs() => string.Join(Environment.NewLine, InfoLogs); + public void WriteInfo(string line) { messages.Add(new Message(Level.Info, line)); @@ -43,4 +48,4 @@ public enum Level Info, Error } -} +} \ No newline at end of file diff --git a/source/Calamari/Kubernetes/Integration/HelmCli.cs b/source/Calamari/Kubernetes/Integration/HelmCli.cs new file mode 100644 index 000000000..0c74ced8d --- /dev/null +++ b/source/Calamari/Kubernetes/Integration/HelmCli.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Calamari.Common.Features.Processes; +using Calamari.Common.Plumbing.Logging; +using Calamari.Common.Plumbing.Variables; + +namespace Calamari.Kubernetes.Integration +{ + public class HelmCli : CommandLineTool + { + public HelmCli(ILog log, ICommandLineRunner commandLineRunner, string workingDirectory, Dictionary environmentVars) + : base(log, commandLineRunner, workingDirectory, environmentVars) + { + ExecutableLocation = "helm"; + } + + readonly List builtInArguments = new List(); + + public HelmCli WithExecutable(string customExecutable) + { + ExecutableLocation = customExecutable; + return this; + } + + public HelmCli WithExecutable(IVariables variables) + { + var helmExecutable = variables.Get(SpecialVariables.Helm.CustomHelmExecutable); + if (string.IsNullOrWhiteSpace(helmExecutable)) + { + return this; + } + + if (variables.GetIndexes(PackageVariables.PackageCollection) + .Contains(SpecialVariables.Helm.Packages.CustomHelmExePackageKey) + && !Path.IsPathRooted(helmExecutable)) + { + var fullPath = Path.GetFullPath(Path.Combine(workingDirectory, SpecialVariables.Helm.Packages.CustomHelmExePackageKey, helmExecutable)); + log.Info($"Using custom helm executable at {helmExecutable} from inside package. Full path at {fullPath}"); + + return WithExecutable(fullPath); + } + else + { + log.Info($"Using custom helm executable at {helmExecutable}"); + return WithExecutable(helmExecutable); + } + } + + public HelmCli WithNamespace(string @namespace) + { + builtInArguments.Add($"--namespace {@namespace}"); + return this; + } + + public string GetManifest(string releaseName) + { + var result = ExecuteCommandAndReturnOutput("get", "manifest", releaseName); + result.Result.VerifySuccess(); + + return result.Output.MergeInfoLogs(); + } + + public CommandResultWithOutput ExecuteCommandAndReturnOutput(params string[] arguments) => + base.ExecuteCommandAndReturnOutput(ExecutableLocation, builtInArguments.Concat(arguments).ToArray()); + } +} \ No newline at end of file diff --git a/source/Calamari/Kubernetes/ManifestReporter.cs b/source/Calamari/Kubernetes/ManifestReporter.cs index 638fdb4e5..a874f440c 100644 --- a/source/Calamari/Kubernetes/ManifestReporter.cs +++ b/source/Calamari/Kubernetes/ManifestReporter.cs @@ -15,7 +15,8 @@ namespace Calamari.Kubernetes { public interface IManifestReporter { - void ReportManifestApplied(string filePath); + void ReportManifestFileApplied(string filePath); + void ReportManifestApplied(string yaml); } public class ManifestReporter : IManifestReporter @@ -50,9 +51,10 @@ string GetNamespace(YamlMappingNode yamlRoot) return implicitNamespace; } - public void ReportManifestApplied(string filePath) + public void ReportManifestFileApplied(string filePath) { - if (!FeatureToggle.KubernetesLiveObjectStatusFeatureToggle.IsEnabled(variables) && !OctopusFeatureToggles.KubernetesObjectManifestInspectionFeatureToggle.IsEnabled(variables)) + if (!FeatureToggle.KubernetesLiveObjectStatusFeatureToggle.IsEnabled(variables) + && !OctopusFeatureToggles.KubernetesObjectManifestInspectionFeatureToggle.IsEnabled(variables)) return; using (var yamlFile = fileSystem.OpenFile(filePath, FileAccess.ReadWrite)) @@ -61,25 +63,7 @@ public void ReportManifestApplied(string filePath) { var yamlStream = new YamlStream(); yamlStream.Load(new StreamReader(yamlFile)); - - foreach (var document in yamlStream.Documents) - { - if (!(document.RootNode is YamlMappingNode rootNode)) - { - log.Warn("Could not parse manifest, resources will not be added to live object status"); - continue; - } - - var updatedDocument = YamlNodeToJson(rootNode); - - var ns = GetNamespace(rootNode); - log.WriteServiceMessage(new ServiceMessage(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, - new Dictionary - { - { SpecialVariables.ServiceMessageNames.ManifestApplied.ManifestAttribute, updatedDocument }, - { SpecialVariables.ServiceMessageNames.ManifestApplied.NamespaceAttribute, ns } - })); - } + ReportManifestStreamApplied(yamlStream); } catch (SemanticErrorException) { @@ -88,6 +72,49 @@ public void ReportManifestApplied(string filePath) } } + public void ReportManifestApplied(string yamlManifest) + { + if (!FeatureToggle.KubernetesLiveObjectStatusFeatureToggle.IsEnabled(variables) + && !OctopusFeatureToggles.KubernetesObjectManifestInspectionFeatureToggle.IsEnabled(variables)) + return; + + try + { + var yamlStream = new YamlStream(); + yamlStream.Load(new StringReader(yamlManifest)); + ReportManifestStreamApplied(yamlStream); + } + catch (SemanticErrorException) + { + log.Warn("Invalid YAML syntax found, resources will not be added to live object status"); + } + } + + void ReportManifestStreamApplied(YamlStream yamlStream) + { + foreach (var document in yamlStream.Documents) + { + if (!(document.RootNode is YamlMappingNode rootNode)) + { + log.Warn("Could not parse manifest, resources will not be added to live object status"); + continue; + } + + var updatedDocument = YamlNodeToJson(rootNode); + + var ns = GetNamespace(rootNode); + var message = new ServiceMessage( + SpecialVariables.ServiceMessageNames.ManifestApplied.Name, + new Dictionary + { + { SpecialVariables.ServiceMessageNames.ManifestApplied.ManifestAttribute, updatedDocument }, + { SpecialVariables.ServiceMessageNames.ManifestApplied.NamespaceAttribute, ns } + }); + + log.WriteServiceMessage(message); + } + } + static string YamlNodeToJson(YamlNode node) { var stream = new YamlStream { new YamlDocument(node) };