Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report helm manifests for live status #1368

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions source/Calamari.Tests/KubernetesFixtures/Integration/HelmCliTests.cs
Original file line number Diff line number Diff line change
@@ -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<CommandLineInvocation>())).Do(x => actual = x.Arg<CommandLineInvocation>());

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<CommandLineInvocation>())).Do(x => actual = x.Arg<CommandLineInvocation>());

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<CommandLineInvocation>())).Do(x => actual = x.Arg<CommandLineInvocation>());

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<CommandLineInvocation>())).Do(x => actual = x.Arg<CommandLineInvocation>());

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<ICommandLineRunner>();
var workingDirectory = TemporaryDirectory.Create();
var helm = new HelmCli(memoryLog, commandLineRunner, workingDirectory.DirectoryPath, new Dictionary<string, string>());

return (helm, commandLineRunner, workingDirectory);
}
}
}
42 changes: 30 additions & 12 deletions source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -47,15 +47,15 @@ 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<ServiceMessage> { expected });
}
}

[TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))]
[TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)]
[TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)]
public void GivenInValidManifest_ShouldNotPostServiceMessage(string enabledFeatureToggle)
{
var memoryLog = new InMemoryLog();
Expand All @@ -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();
Expand All @@ -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<string, string>("ns", "XXX"));
}
}

[TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))]
[TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)]
[TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)]
public void GivenNamespaceNotInManifest_ShouldReportVariableNamespace(string enabledFeatureToggle)
{
var memoryLog = new InMemoryLog();
Expand All @@ -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<string, string>("ns", variableNs));
}
}

[TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))]
[TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)]
[TestCase(OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)]
public void GiveNoNamespaces_ShouldDefaultNamespace(string enabledFeatureToggle)
{
var memoryLog = new InMemoryLog();
Expand All @@ -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<string, string>("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<ServiceMessage> { expected });
}

static IDisposable CreateFile(string yaml, out string filePath)
{
var tempDir = TemporaryDirectory.Create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions source/Calamari/Kubernetes/Commands/HelmUpgradeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class HelmUpgradeCommand : Command
readonly IExtractPackage extractPackage;
readonly HelmTemplateValueSourcesParser templateValueSourcesParser;
readonly ICommandLineRunner commandLineRunner;
readonly IManifestReporter manifestReporter;

public HelmUpgradeCommand(
ILog log,
Expand All @@ -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;
Expand All @@ -56,6 +57,7 @@ HelmTemplateValueSourcesParser templateValueSourcesParser
this.substituteInFiles = substituteInFiles;
this.extractPackage = extractPackage;
this.templateValueSourcesParser = templateValueSourcesParser;
this.manifestReporter = manifestReporter;
this.commandLineRunner = commandLineRunner;
}

Expand Down Expand Up @@ -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))
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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}\"");
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Calamari.Common.Plumbing.Commands;

Expand All @@ -8,14 +9,18 @@ public interface ICommandOutput
{
Message[] Messages { get; }
IEnumerable<string> InfoLogs { get; }
string MergeInfoLogs();
}

public class CaptureCommandOutput : ICommandInvocationOutputSink, ICommandOutput
{
private readonly List<Message> messages = new List<Message>();
public Message[] Messages => messages.ToArray();

public IEnumerable<string> InfoLogs => Messages.Where(m => m.Level == Level.Info).Select(m => m.Text).ToArray();

public string MergeInfoLogs() => string.Join(Environment.NewLine, InfoLogs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts on having the newline character depend on the OS where Calamari is running?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a lot of them - there are existing examples of joining these log messages with Environment.NewLine so my assumption is that it works fine in all cases we're using it at the moment.

Sticking with Unix newlines might be a good idea to be consistent - perhaps there is a bug we just haven't seen come up yet?
By the same token, perhaps there is no bug because windows people tend to have everything on windows so we're just getting lucky...

I don't have any good answers aside from sticking with the status quo

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, status quo is good


public void WriteInfo(string line)
{
messages.Add(new Message(Level.Info, line));
Expand Down Expand Up @@ -43,4 +48,4 @@ public enum Level
Info,
Error
}
}
}
Loading