From 42143f14e1a19cc730cc8ad75267f28069997388 Mon Sep 17 00:00:00 2001 From: Eddy Moulton Date: Thu, 24 Oct 2024 09:20:22 +1100 Subject: [PATCH 1/4] Report helm manifests for live status --- .../Integration/HelmCliTests.cs | 117 ++++++++++++++++++ .../ManifestReporterTests.cs | 91 +++++++------- .../GatherAndApplyRawYamlExecutor.cs | 2 +- .../Kubernetes/Commands/HelmUpgradeCommand.cs | 7 +- .../Conventions/HelmUpgradeConvention.cs | 1 + .../ReportHelmManifestConvention.cs | 37 ++++++ .../Integration/CaptureCommandOutput.cs | 9 +- .../Kubernetes/Integration/HelmCli.cs | 68 ++++++++++ .../Calamari/Kubernetes/ManifestReporter.cs | 71 +++++++---- 9 files changed, 333 insertions(+), 70 deletions(-) create mode 100644 source/Calamari.Tests/KubernetesFixtures/Integration/HelmCliTests.cs create mode 100644 source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs create mode 100644 source/Calamari/Kubernetes/Integration/HelmCli.cs 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..9f496c2ae 100644 --- a/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs +++ b/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs @@ -23,18 +23,15 @@ public void GivenDisabledFeatureToggle_ShouldNotPostServiceMessage() var variables = new CalamariVariables(); var yaml = @"foo: bar"; - using (CreateFile(yaml, out var filePath)) - { - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + mr.ReportManifestApplied(yaml); - memoryLog.ServiceMessages.Should().BeEmpty(); - } + 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(); @@ -43,19 +40,16 @@ public void GivenValidYaml_ShouldPostSingleServiceMessage(string enabledFeatureT var yaml = @"foo: bar"; var expectedJson = "{\"foo\": \"bar\"}"; - using (CreateFile(yaml, out var filePath)) - { - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + mr.ReportManifestApplied(yaml); - var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", expectedJson)); - memoryLog.ServiceMessages.Should().BeEquivalentTo(new List { expected }); - } + var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", expectedJson)); + memoryLog.ServiceMessages.Should().BeEquivalentTo(new List { expected }); } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] - [TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] + [TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] public void GivenInValidManifest_ShouldNotPostServiceMessage(string enabledFeatureToggle) { var memoryLog = new InMemoryLog(); @@ -63,18 +57,15 @@ public void GivenInValidManifest_ShouldNotPostServiceMessage(string enabledFeatu variables.Set(KnownVariables.EnabledFeatureToggles, enabledFeatureToggle); var yaml = @"text - Bar"; - using (CreateFile(yaml, out var filePath)) - { - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + mr.ReportManifestApplied(yaml); - memoryLog.ServiceMessages.Should().BeEmpty(); - } + 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(); @@ -83,53 +74,67 @@ public void GivenNamespaceInManifest_ShouldReportManifestNamespace(string enable var yaml = @"metadata: name: game-demo namespace: XXX"; - using (CreateFile(yaml, out var filePath)) - { - var variableNs = Some.String(); - variables.Set(SpecialVariables.Namespace, variableNs); - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + var variableNs = Some.String(); + variables.Set(SpecialVariables.Namespace, variableNs); + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "XXX")); - } + mr.ReportManifestApplied(yaml); + + 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(); var variables = new CalamariVariables(); variables.Set(KnownVariables.EnabledFeatureToggles, enabledFeatureToggle); var yaml = @"foo: bar"; - using (CreateFile(yaml, out var filePath)) - { - var variableNs = Some.String(); - variables.Set(SpecialVariables.Namespace, variableNs); - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + var variableNs = Some.String(); + variables.Set(SpecialVariables.Namespace, variableNs); + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", variableNs)); - } + mr.ReportManifestApplied(yaml); + + 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(); var variables = new CalamariVariables(); variables.Set(KnownVariables.EnabledFeatureToggles, enabledFeatureToggle); var yaml = @"foo: bar"; + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + + mr.ReportManifestApplied(yaml); + + memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "default")); + } + + [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] + [TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] + public void GivenValidYamlFile_ShouldPostSingleServiceMessage(string enabledFeatureToggle) + { + var memoryLog = new InMemoryLog(); + var variables = new CalamariVariables(); + variables.Set(KnownVariables.EnabledFeatureToggles, enabledFeatureToggle); + + var yaml = @"foo: bar"; + var expectedJson = "{\"foo\": \"bar\"}"; using (CreateFile(yaml, out var filePath)) { var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(filePath); + mr.ReportManifestFileApplied(filePath); - memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "default")); + var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", expectedJson)); + memoryLog.ServiceMessages.Should().BeEquivalentTo(new List { expected }); } } 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..6728b892e 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); diff --git a/source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs b/source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs new file mode 100644 index 000000000..107c5afa7 --- /dev/null +++ b/source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs @@ -0,0 +1,37 @@ +using System; +using Calamari.Common.Commands; +using Calamari.Common.Features.Processes; +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) + { + 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..5f9738d21 --- /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) }; From 763369d79fd6df6408905d63cb819a41e94e68b0 Mon Sep 17 00:00:00 2001 From: Eddy Moulton Date: Thu, 24 Oct 2024 10:05:13 +1100 Subject: [PATCH 2/4] Revert needless test change --- .../ManifestReporterTests.cs | 79 +++++++++++-------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs b/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs index 9f496c2ae..924ceeb91 100644 --- a/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs +++ b/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs @@ -23,11 +23,14 @@ public void GivenDisabledFeatureToggle_ShouldNotPostServiceMessage() var variables = new CalamariVariables(); var yaml = @"foo: bar"; - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + using (CreateFile(yaml, out var filePath)) + { + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(yaml); + mr.ReportManifestFileApplied(filePath); - memoryLog.ServiceMessages.Should().BeEmpty(); + memoryLog.ServiceMessages.Should().BeEmpty(); + } } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] @@ -40,12 +43,15 @@ public void GivenValidYaml_ShouldPostSingleServiceMessage(string enabledFeatureT var yaml = @"foo: bar"; var expectedJson = "{\"foo\": \"bar\"}"; - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + using (CreateFile(yaml, out var filePath)) + { + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(yaml); + mr.ReportManifestFileApplied(filePath); - var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", expectedJson)); - memoryLog.ServiceMessages.Should().BeEquivalentTo(new List { expected }); + var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", expectedJson)); + memoryLog.ServiceMessages.Should().BeEquivalentTo(new List { expected }); + } } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] @@ -57,11 +63,14 @@ public void GivenInValidManifest_ShouldNotPostServiceMessage(string enabledFeatu variables.Set(KnownVariables.EnabledFeatureToggles, enabledFeatureToggle); var yaml = @"text - Bar"; - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + using (CreateFile(yaml, out var filePath)) + { + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(yaml); + mr.ReportManifestFileApplied(filePath); - memoryLog.ServiceMessages.Should().BeEmpty(); + memoryLog.ServiceMessages.Should().BeEmpty(); + } } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] @@ -74,14 +83,16 @@ public void GivenNamespaceInManifest_ShouldReportManifestNamespace(string enable var yaml = @"metadata: name: game-demo namespace: XXX"; + using (CreateFile(yaml, out var filePath)) + { + var variableNs = Some.String(); + variables.Set(SpecialVariables.Namespace, variableNs); + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - var variableNs = Some.String(); - variables.Set(SpecialVariables.Namespace, variableNs); - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - - mr.ReportManifestApplied(yaml); + mr.ReportManifestFileApplied(filePath); - memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "XXX")); + memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "XXX")); + } } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] @@ -92,14 +103,16 @@ public void GivenNamespaceNotInManifest_ShouldReportVariableNamespace(string ena var variables = new CalamariVariables(); variables.Set(KnownVariables.EnabledFeatureToggles, enabledFeatureToggle); var yaml = @"foo: bar"; + using (CreateFile(yaml, out var filePath)) + { + var variableNs = Some.String(); + variables.Set(SpecialVariables.Namespace, variableNs); + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - var variableNs = Some.String(); - variables.Set(SpecialVariables.Namespace, variableNs); - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - - mr.ReportManifestApplied(yaml); + mr.ReportManifestFileApplied(filePath); - memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", variableNs)); + memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", variableNs)); + } } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] @@ -110,16 +123,19 @@ public void GiveNoNamespaces_ShouldDefaultNamespace(string enabledFeatureToggle) var variables = new CalamariVariables(); variables.Set(KnownVariables.EnabledFeatureToggles, enabledFeatureToggle); var yaml = @"foo: bar"; - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + using (CreateFile(yaml, out var filePath)) + { + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestApplied(yaml); + mr.ReportManifestFileApplied(filePath); - memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "default")); + memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "default")); + } } [TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))] [TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)] - public void GivenValidYamlFile_ShouldPostSingleServiceMessage(string enabledFeatureToggle) + public void GivenValidYamlString_ShouldPostSingleServiceMessage(string enabledFeatureToggle) { var memoryLog = new InMemoryLog(); var variables = new CalamariVariables(); @@ -127,15 +143,12 @@ public void GivenValidYamlFile_ShouldPostSingleServiceMessage(string enabledFeat var yaml = @"foo: bar"; var expectedJson = "{\"foo\": \"bar\"}"; - using (CreateFile(yaml, out var filePath)) - { - var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); - mr.ReportManifestFileApplied(filePath); + mr.ReportManifestApplied(yaml); - var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", expectedJson)); - memoryLog.ServiceMessages.Should().BeEquivalentTo(new List { expected }); - } + 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) From b2a2d7bf9d89d0e52e62d8320c590ccbb2fe3261 Mon Sep 17 00:00:00 2001 From: Eddy Moulton Date: Thu, 24 Oct 2024 10:47:10 +1100 Subject: [PATCH 3/4] Add feature toggle --- .../Kubernetes/Conventions/ReportHelmManifestConvention.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs b/source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs index 107c5afa7..18b74e101 100644 --- a/source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs +++ b/source/Calamari/Kubernetes/Conventions/ReportHelmManifestConvention.cs @@ -1,6 +1,7 @@ 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; @@ -24,6 +25,10 @@ public ReportHelmManifestConvention(ILog log, 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) From 8ed5036c2c5a31a0220cdca5d96dca62eff9861f Mon Sep 17 00:00:00 2001 From: Eddy Moulton Date: Fri, 25 Oct 2024 12:02:24 +1100 Subject: [PATCH 4/4] Testing using custom helm exe --- source/Calamari/Kubernetes/Conventions/HelmUpgradeConvention.cs | 2 ++ source/Calamari/Kubernetes/Integration/HelmCli.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/source/Calamari/Kubernetes/Conventions/HelmUpgradeConvention.cs b/source/Calamari/Kubernetes/Conventions/HelmUpgradeConvention.cs index 6728b892e..74765db34 100644 --- a/source/Calamari/Kubernetes/Conventions/HelmUpgradeConvention.cs +++ b/source/Calamari/Kubernetes/Conventions/HelmUpgradeConvention.cs @@ -119,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/Integration/HelmCli.cs b/source/Calamari/Kubernetes/Integration/HelmCli.cs index 5f9738d21..0c74ced8d 100644 --- a/source/Calamari/Kubernetes/Integration/HelmCli.cs +++ b/source/Calamari/Kubernetes/Integration/HelmCli.cs @@ -56,7 +56,7 @@ public HelmCli WithNamespace(string @namespace) public string GetManifest(string releaseName) { - var result = ExecuteCommandAndReturnOutput("get", "manifest", $"\"{releaseName}\""); + var result = ExecuteCommandAndReturnOutput("get", "manifest", releaseName); result.Result.VerifySuccess(); return result.Output.MergeInfoLogs();