From 3b438916c66de62f4ea93f79c08a7894ec5598ec Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 10:33:46 +1000 Subject: [PATCH 01/16] Add dotsettings --- .../KubernetesAgent.sln.DotSettings | 418 ++++++++++++++++++ 1 file changed, 418 insertions(+) create mode 100644 tests/kubernetes-agent/KubernetesAgent.sln.DotSettings diff --git a/tests/kubernetes-agent/KubernetesAgent.sln.DotSettings b/tests/kubernetes-agent/KubernetesAgent.sln.DotSettings new file mode 100644 index 00000000..b58a4c8b --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.sln.DotSettings @@ -0,0 +1,418 @@ + + True + True + True + + + + + + True + ExplicitlyExcluded + + True + ExplicitlyExcluded + ExplicitlyExcluded + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + SUGGESTION + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + + + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + ERROR + <?xml version="1.0" encoding="utf-16"?><Profile name="Octopus Deploy"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSRemoveCodeRedundancies>True</CSRemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CssAlphabetizeProperties>True</CssAlphabetizeProperties><HtmlReformatCode>True</HtmlReformatCode><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSArrangeQualifiers>True</CSArrangeQualifiers><CSEnforceVarKeywordUsageSettings>True</CSEnforceVarKeywordUsageSettings><CSShortenReferences>True</CSShortenReferences><CSReorderTypeMembers>True</CSReorderTypeMembers><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + &lt;option name="myName" value="Octopus Deploy" /&gt; +&lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; + &lt;Language id="CSS"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="EditorConfig"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HCL"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTML"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTTP Request"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Handlebars"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Ini"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JSON"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Jade"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JavaScript"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Markdown"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="PowerShell"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Properties"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="RELAX-NG"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="SQL"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="VueExpr"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="XML"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="yaml"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; +&lt;/profile&gt;</RIDER_SETTINGS></Profile> + Octopus Deploy + RequiredForMultilineStatement + RequiredForMultilineStatement + RequiredForMultilineStatement + Implicit + Implicit + ExpressionBody + ExpressionBody + required public private protected internal file static abstract virtual sealed override new readonly extern unsafe volatile async + BlockScoped + False + + NEXT_LINE + False + False + SEPARATE + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + False + + NEXT_LINE + 1 + 1 + True + True + True + True + 5 + 1 + 5 + 5 + 5 + 5 + NEVER + True + False + NEVER + False + False + False + True + True + CHOP_IF_LONG + True + True + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_IF_LONG + 200 + False + CHOP_ALWAYS + CHOP_IF_LONG + DoNotTouch + DoNotTouch + False + 1 + ByFirstAttr + 300 + 300 + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TestCaseFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Entry Priority="100" DisplayName="Public Delegates"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Delegate" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry Priority="100" DisplayName="Public Enums"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Enum" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Static Fields and Constants"> + <Entry.Match> + <Or> + <Kind Is="Constant" /> + <And> + <Kind Is="Field" /> + <Static /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constant Field" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Fields"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Readonly /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Constructors"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Properties, Indexers"> + <Entry.Match> + <Or> + <Kind Is="Property" /> + <Kind Is="Indexer" /> + </Or> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Nested Types"> + <Entry.Match> + <Kind Is="Type" /> + </Entry.Match> + </Entry> + </TypePattern> +</Patterns> + System + System + AD + CSS + ID + SSL + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb" /></Policy> + + + + + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + False + False + None + TRACE + True + True + Document map + True + 0 + True + True + Everywhere + map + True + public class $Target$Map : DocumentMap<$Target$> +{ + public $Target$Map() + { + Column(m => m.Foo); + } +} + True + True + True + True + True + True + True + True + True + True + True + True + True + True + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + From 2e581783dfc00e7962e78eaade87f40ca2346237 Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 10:34:10 +1000 Subject: [PATCH 02/16] Run Upgrade --- .../GlobalUsings.cs | 3 + .../HelmChartBuilder.cs | 58 +++++ .../KubernetesAgent.IntegrationTests.csproj | 31 +++ .../RunsAgentUpgrade.cs | 96 ++++++++ .../Setup/Common/AssemblyExtensions.cs | 12 + .../Setup/Common/EnumerableExtensions.cs | 8 + .../Common/Extraction/TarFileExtractor.cs | 30 +++ .../Common/FileSystemInfoExtensionMethods.cs | 14 ++ .../Common/PollingSubscriptionIdGenerator.cs | 17 ++ .../Setup/Common/ProcessRunner.cs | 69 ++++++ .../Setup/Common/TemporaryDirectory.cs | 20 ++ .../TemporaryDirectoryExtensionMethods.cs | 21 ++ .../Setup/Common/TestCertificates.cs | 26 ++ .../Setup/Common/TestLogger.cs | 228 ++++++++++++++++++ .../Setup/KubernetesAgentInstaller.cs | 227 +++++++++++++++++ .../Setup/KubernetesClusterInstaller.cs | 139 +++++++++++ .../Setup/RequiredToolDownloader.cs | 32 +++ .../Setup/ServerHalibutRuntimeRunner.cs | 27 +++ .../Setup/Tooling/HelmDownloader.cs | 67 +++++ .../Setup/Tooling/IToolDownloader.cs | 6 + .../Setup/Tooling/KindDownloader.cs | 31 +++ .../Setup/Tooling/KubeCtlDownloader.cs | 33 +++ .../Setup/Tooling/ToolDownloader.cs | 89 +++++++ .../Setup/agent-values.yaml | 14 ++ .../Setup/docker-desktop-network-routing.yaml | 8 + .../Setup/kind-config.yaml | 8 + .../Setup/linux-network-routing.yaml | 16 ++ .../UnitTest1.cs | 9 - 28 files changed, 1330 insertions(+), 9 deletions(-) create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/GlobalUsings.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/HelmChartBuilder.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/AssemblyExtensions.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/EnumerableExtensions.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/Extraction/TarFileExtractor.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/FileSystemInfoExtensionMethods.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/PollingSubscriptionIdGenerator.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/ProcessRunner.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectory.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectoryExtensionMethods.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestCertificates.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestLogger.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesAgentInstaller.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/RequiredToolDownloader.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/ServerHalibutRuntimeRunner.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/HelmDownloader.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/IToolDownloader.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/KindDownloader.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/KubeCtlDownloader.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/ToolDownloader.cs create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/agent-values.yaml create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/docker-desktop-network-routing.yaml create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/kind-config.yaml create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/linux-network-routing.yaml delete mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/UnitTest1.cs diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/GlobalUsings.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/GlobalUsings.cs new file mode 100644 index 00000000..70748def --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/GlobalUsings.cs @@ -0,0 +1,3 @@ +// Global using directives + +global using Serilog; \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/HelmChartBuilder.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/HelmChartBuilder.cs new file mode 100644 index 00000000..5318c726 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/HelmChartBuilder.cs @@ -0,0 +1,58 @@ +using System; +using KubernetesAgent.Integration.Setup.Common; + +namespace KubernetesAgent.Integration +{ + public static class HelmChartBuilder + { + public static string BuildHelmChart(TemporaryDirectory directory) + { + var version = GetChartVersion(); + version = $"{version}-{DateTime.Now:yyyymdHHmmss}"; + + var packager = ProcessRunner.Run("helm", directory, GetHelmChartPackageArguments(version)); + if (packager.ExitCode != 0) + { + throw new Exception($"Failed to package Helm chart. Exit code: {packager.ExitCode}"); + } + + var output = packager.StandardOutput.ReadToEnd(); + return output.Split(":")[^1].ToString().Trim(); + } + + static string[] GetHelmChartPackageArguments(string version) + { + var chartsDirectory = GetChartsDirectory().FullName; + return + [ + "package", + chartsDirectory, + "--version", + version + ]; + } + + static DirectoryInfo GetChartsDirectory() + { + var chartsDirectory = Path.Combine(AppContext.BaseDirectory); + var currentDirectory = new DirectoryInfo(chartsDirectory); + while (currentDirectory != null && currentDirectory.EnumerateDirectories().All(d => d.Name != "charts")) + { + currentDirectory = currentDirectory.Parent; + } + if (currentDirectory == null) + { + throw new DirectoryNotFoundException("Could not find the charts directory"); + } + return new DirectoryInfo(Path.Combine(currentDirectory.FullName, "charts", "kubernetes-agent")); + } + + static string GetChartVersion() + { + var chartDirectory = GetChartsDirectory(); + var chartYaml = Path.Combine(chartDirectory.FullName, "Chart.yaml"); + var chart = File.ReadAllText(chartYaml); + return chart.Split("\n").First(l => l.StartsWith("version:")).Split(":")[1].Trim('"').Trim(); + } + } +} diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/KubernetesAgent.IntegrationTests.csproj b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/KubernetesAgent.IntegrationTests.csproj index 951ed96b..80a2fbe9 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/KubernetesAgent.IntegrationTests.csproj +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/KubernetesAgent.IntegrationTests.csproj @@ -13,6 +13,10 @@ + + + + @@ -21,4 +25,31 @@ + + + + PreserveNewest + + + + PreserveNewest + + + + PreserveNewest + + + + PreserveNewest + + + + PreserveNewest + + + + PreserveNewest + + + diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs new file mode 100644 index 00000000..fa8b8e9d --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs @@ -0,0 +1,96 @@ +using Halibut; +using KubernetesAgent.Integration.Setup; +using KubernetesAgent.Integration.Setup.Common; +using Octopus.Tentacle.Client; +using Octopus.Tentacle.Client.Scripts.Models; +using Octopus.Tentacle.Client.Scripts.Models.Builders; +using Octopus.Tentacle.Contracts; +using Xunit.Abstractions; + +namespace KubernetesAgent.Integration; + +public class HelmUpgradeTests(ITestOutputHelper output) : IAsyncLifetime +{ + readonly ITestOutputHelper output = output; + ILogger logger = null!; + readonly TemporaryDirectory workingDirectory = new(Directory.CreateTempSubdirectory()); + KubernetesClusterInstaller clusterInstaller = null!; + KubernetesAgentInstaller agentInstaller = null!; + string kindExePath = null!; + string helmExePath = null!; + string kubeCtlPath = null!; + TentacleClient client = null!; + + public async Task InitializeAsync() + { + logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.TestOutput(output) + .CreateLogger(); + + var requiredToolDownloader = new RequiredToolDownloader(workingDirectory, logger); + (kindExePath, helmExePath, kubeCtlPath) = await requiredToolDownloader.DownloadRequiredTools(CancellationToken.None); + clusterInstaller = new KubernetesClusterInstaller(workingDirectory, kindExePath, helmExePath, kubeCtlPath, logger); + await clusterInstaller.Install(); + agentInstaller = new KubernetesAgentInstaller(workingDirectory , helmExePath, kubeCtlPath, clusterInstaller.KubeConfigPath, logger); + client = await agentInstaller.InstallAgent(); + } + + public async Task DisposeAsync() + { + clusterInstaller.Dispose(); + workingDirectory.Dispose(); + await Task.CompletedTask; + } + + [Fact] + public async Task CanRunCommandOnExistingAgent() + { + var scriptCommand = new ExecuteKubernetesScriptCommandBuilder(Guid.NewGuid().ToString()).WithScriptBody("echo 'Hello World'").Build(); + void onScriptStatusResponseReceived(ScriptExecutionStatus res) => logger.Information("{Output}", res.ToString()); + async Task onScriptCompleted(CancellationToken t) + { + await Task.CompletedTask; + logger.Information("Script completed"); + } + var testLogger = new TestLogger(logger); + var result = await client.ExecuteScript(scriptCommand, onScriptStatusResponseReceived, onScriptCompleted, testLogger, CancellationToken.None); + if (result.ExitCode != 0) + { + throw new Exception($"Script failed with exit code {result.ExitCode}"); + } + } + + [Fact] + public async Task CanUpgradeAgentAndRunCommand() + { + var helmPackage = HelmChartBuilder.BuildHelmChart(workingDirectory); + var helmPackageFile = new FileInfo(helmPackage); + var packageName = helmPackageFile.Name; + var packageBytes = await File.ReadAllBytesAsync(helmPackage); + var helmUpgradeScript = + $""" + helm upgrade \ + --atomic \ + --namespace {agentInstaller.Namespace} \ + {agentInstaller.AgentName} \ + /tmp/{packageName} + """; + var scriptCommand = new ExecuteKubernetesScriptCommandBuilder(Guid.NewGuid().ToString()) + .WithScriptBody($"cp ./{packageName} /tmp/{packageName} && {helmUpgradeScript}") + .WithScriptFile(new ScriptFile(packageName, DataStream.FromBytes(packageBytes))) + .Build(); + void onScriptStatusResponseReceived(ScriptExecutionStatus res) => logger.Information("{Output}", res.ToString()); + async Task onScriptCompleted(CancellationToken t) + { + await Task.CompletedTask; + logger.Information("Script completed"); + } + var testLogger = new TestLogger(logger); + var result = await client.ExecuteScript(scriptCommand, onScriptStatusResponseReceived, onScriptCompleted, testLogger, CancellationToken.None); + if (result.ExitCode != 0) + { + throw new Exception($"Script failed with exit code {result.ExitCode}"); + } + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/AssemblyExtensions.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/AssemblyExtensions.cs new file mode 100644 index 00000000..882b8ac3 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/AssemblyExtensions.cs @@ -0,0 +1,12 @@ +using System.Reflection; + +namespace Octopus.Tentacle.Kubernetes.Tests.Integration.Support; + +public static class AssemblyExtensions +{ + public static Stream GetManifestResourceStreamFromPartialName(this Assembly assembly, string filename) + { + var valuesFileName = assembly.GetManifestResourceNames().Single(n => n.Contains(filename, StringComparison.OrdinalIgnoreCase)); + return assembly.GetManifestResourceStream(valuesFileName)!; + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/EnumerableExtensions.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/EnumerableExtensions.cs new file mode 100644 index 00000000..841ff861 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/EnumerableExtensions.cs @@ -0,0 +1,8 @@ +namespace KubernetesAgent.Integration.Setup.Common; + +public static class EnumerableExtensions +{ + public static IEnumerable WhereNotNull(this IEnumerable items) + where T : class + => items.Where(i => i != null)!; +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/Extraction/TarFileExtractor.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/Extraction/TarFileExtractor.cs new file mode 100644 index 00000000..fc691057 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/Extraction/TarFileExtractor.cs @@ -0,0 +1,30 @@ +using System.Formats.Tar; +using System.IO.Compression; + +namespace KubernetesAgent.Integration.Setup.Common.Extraction; + +public static class TarFileExtractor +{ + public static void Extract(FileSystemInfo tarFile, FileSystemInfo targetDirectory) + { + if (!targetDirectory.Exists) + { + Directory.CreateDirectory(targetDirectory.FullName); + } + using var compressedFileStream = File.Open(tarFile.FullName, FileMode.Open); + using var tarStream = new GZipStream(compressedFileStream, CompressionMode.Decompress); + TarFile.ExtractToDirectory(tarStream, targetDirectory.FullName, true); + } + + public static void ExtractAndMakeExecutable(FileSystemInfo tarFile, FileSystemInfo targetDirectory) + { + Extract(tarFile, targetDirectory); + targetDirectory.MakeExecutable(); + } + public static void ExtractAndMakeExecutable(string tarFilePath, string targetDirectoryPath) + { + var tarFile = new FileInfo(tarFilePath); + var targetDirectory = new FileInfo(targetDirectoryPath); + ExtractAndMakeExecutable(tarFile, targetDirectory); + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/FileSystemInfoExtensionMethods.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/FileSystemInfoExtensionMethods.cs new file mode 100644 index 00000000..d37448bf --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/FileSystemInfoExtensionMethods.cs @@ -0,0 +1,14 @@ +namespace KubernetesAgent.Integration.Setup.Common; + +public static class FileSystemInfoExtensionMethods +{ + public static void MakeExecutable(this FileSystemInfo fsObject) + { + var result = ProcessRunner.Run("chmod", "+x", "-R", fsObject.FullName); + if (result.ExitCode != 0) + { + throw new Exception($"Failed to make {fsObject.FullName} executable. Exit code: {result.ExitCode}. stdout: {result.StandardOutput.ReadToEnd()}, stderr: {result.StandardError.ReadToEnd()}."); + } + result.Close(); + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/PollingSubscriptionIdGenerator.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/PollingSubscriptionIdGenerator.cs new file mode 100644 index 00000000..2a1955bf --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/PollingSubscriptionIdGenerator.cs @@ -0,0 +1,17 @@ +namespace KubernetesAgent.Integration.Setup.Common; + +public class PollingSubscriptionIdGenerator +{ + private static Random random = new Random(); + public static Uri Generate() + { + return new Uri("poll://" + RandomString(20).ToLowerInvariant() + "/"); + } + + static string RandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } +} diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/ProcessRunner.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/ProcessRunner.cs new file mode 100644 index 00000000..7b4d8548 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/ProcessRunner.cs @@ -0,0 +1,69 @@ +using System.Diagnostics; + +namespace KubernetesAgent.Integration.Setup.Common; + +public static class ProcessRunner +{ + public static Process Run(string command, TemporaryDirectory workingDirectory, params string[] arguments) + { + var process = new Process(); + process.StartInfo.FileName = command; + process.StartInfo.Arguments = arguments.Length > 0 ? string.Join(" ", arguments) : string.Empty; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.WorkingDirectory = workingDirectory.Directory.FullName; + process.Start(); + process.WaitForExit(); + return process; + } + public static Process Run(string command, params string[] arguments) + { + var process = new Process(); + process.StartInfo.FileName = command; + process.StartInfo.Arguments = arguments.Length > 0 ? string.Join(" ", arguments) : string.Empty; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.Start(); + process.WaitForExit(); + return process; + } + + + public static Process RunWithLogger(string command, TemporaryDirectory directory, ILogger logger, params string[] arguments) + { + + var process = new Process(); + process.StartInfo.FileName = command; + process.StartInfo.Arguments = arguments.Length > 0 ? string.Join(" ", arguments) : string.Empty; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.WorkingDirectory = directory.Directory.FullName; + + process.OutputDataReceived += (sender, e) => + { + if (e.Data != null) + { + logger.Information("{Data}",e.Data); + } + }; + + process.ErrorDataReceived += (sender, e) => + { + if (e.Data != null) + { + logger.Error("{Data}",e.Data); + } + }; + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + return process; + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectory.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectory.cs new file mode 100644 index 00000000..5f80e673 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectory.cs @@ -0,0 +1,20 @@ +namespace KubernetesAgent.Integration.Setup.Common; + +public class TemporaryDirectory : IDisposable +{ + public DirectoryInfo Directory { get; } + + public TemporaryDirectory(DirectoryInfo directory) + { + Directory = directory; + if (!Directory.Exists) + { + Directory.Create(); + } + } + + public void Dispose() + { + Directory.Delete(true); + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectoryExtensionMethods.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectoryExtensionMethods.cs new file mode 100644 index 00000000..b358641a --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectoryExtensionMethods.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using Octopus.Tentacle.Kubernetes.Tests.Integration.Support; + +namespace KubernetesAgent.Integration.Setup.Common +{ + public static class TemporaryDirectoryExtensionMethods + { + public static string WriteFileToTemporaryDirectory(this TemporaryDirectory tempDir, string resourceFileName, string? outputFilename = null) + { + using var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStreamFromPartialName(resourceFileName); + + var filePath = Path.Combine(tempDir.Directory.FullName, outputFilename ?? resourceFileName); + using var file = File.Create(filePath); + + resourceStream.Seek(0, SeekOrigin.Begin); + resourceStream.CopyTo(file); + + return filePath; + } + } +} diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestCertificates.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestCertificates.cs new file mode 100644 index 00000000..453037d8 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestCertificates.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using Octopus.Tentacle.Kubernetes.Tests.Integration.Support; + +namespace KubernetesAgent.Integration.Setup.Common +{ + public static class TestCertificates + { + public static X509Certificate2 Tentacle; + public static string TentaclePublicThumbprint; + public static X509Certificate2 Server; + public static string ServerPublicThumbprint; + + static TestCertificates() + { + using var tempDir = new TemporaryDirectory(Directory.CreateTempSubdirectory()); + var tentaclePfxPath = tempDir.WriteFileToTemporaryDirectory("Tentacle.pfx"); + Tentacle = new X509Certificate2(tentaclePfxPath); + TentaclePublicThumbprint = Tentacle.Thumbprint; + + var serverPfxPath = tempDir.WriteFileToTemporaryDirectory("Server.pfx"); + Server = new X509Certificate2(serverPfxPath); + ServerPublicThumbprint = Server.Thumbprint; + } + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestLogger.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestLogger.cs new file mode 100644 index 00000000..2f55ab40 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestLogger.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Concurrent; +using Halibut.Diagnostics; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Octopus.Diagnostics; +using ILog = Octopus.Diagnostics.ILog; + +namespace KubernetesAgent.Integration.Setup.Common +{ + public sealed class TestLogger(ILogger logger) : ILog, IDisposable + { + readonly ILogger logger = logger; + + public string CorrelationId { get; } + + public void Dispose() + { + Flush(); + } + + public void WithSensitiveValues(string[] sensitiveValues) + { + } + + public void WithSensitiveValue(string sensitiveValue) + { + } + + public void Write(LogCategory category, string messageText) + { + Write(category, null, messageText); + } + + public void Write(LogCategory category, Exception error) + { + WriteFormat(category, error.Message); + } + + public void Write(LogCategory category, Exception? error, string messageText) + { + WriteFormat(category, "Exception: {Message}, Message: {Message}", error?.Message ?? "", messageText); + } + + public void WriteFormat(LogCategory category, string messageFormat, params object[] args) + { + WriteFormat(category, null, messageFormat, args); + } + + public void WriteFormat(LogCategory category, Exception? error, string messageFormat, params object[] args) + { + switch (category) + { + case LogCategory.Trace: + case LogCategory.Verbose: + logger.Debug(messageFormat, args); + break; + case LogCategory.Info: + case LogCategory.Planned: + case LogCategory.Highlight: + case LogCategory.Abandoned: + case LogCategory.Wait: + case LogCategory.Progress: + logger.Information(messageFormat, args); + break; + case LogCategory.Finished: + case LogCategory.Warning: + case LogCategory.Error: + case LogCategory.Fatal: + logger.Error(messageFormat, args); + break; + } + } + + public void Trace(string messageText) + { + Write(LogCategory.Trace, messageText); + } + + public void Trace(Exception error) + { + Write(LogCategory.Trace, error); + } + + public void Trace(Exception error, string message) + { + Write(LogCategory.Trace, error, message); + } + + public void TraceFormat(string messageFormat, params object[] args) + { + WriteFormat(LogCategory.Trace, messageFormat, args); + } + + public void TraceFormat(Exception error, string format, params object[] args) + { + WriteFormat(LogCategory.Trace, error, format, args); + } + + public void Verbose(string messageText) + { + Write(LogCategory.Verbose, messageText); + } + + public void Verbose(Exception error) + { + Write(LogCategory.Verbose, error); + } + + public void Verbose(Exception error, string message) + { + Write(LogCategory.Verbose, error, message); + } + + public void VerboseFormat(string format, params object[] args) + { + WriteFormat(LogCategory.Verbose, format, args); + } + + public void VerboseFormat(Exception error, string format, params object[] args) + { + WriteFormat(LogCategory.Verbose, error, format, args); + } + + public void Info(string messageText) + { + Write(LogCategory.Info, messageText); + } + + public void Info(Exception error) + { + Write(LogCategory.Info, error); + } + + public void Info(Exception error, string message) + { + Write(LogCategory.Info, error, message); + } + + public void InfoFormat(string format, params object[] args) + { + WriteFormat(LogCategory.Info, format, args); + } + + public void InfoFormat(Exception error, string format, params object[] args) + { + WriteFormat(LogCategory.Info, error, format, args); + } + + public void Warn(string messageText) + { + Write(LogCategory.Warning, messageText); + } + + public void Warn(Exception error) + { + Write(LogCategory.Warning, error); + } + + public void Warn(Exception error, string messageText) + { + Write(LogCategory.Warning, error, messageText); + } + + public void WarnFormat(string format, params object[] args) + { + WriteFormat(LogCategory.Warning, format, args); + } + + public void WarnFormat(Exception exception, string format, params object[] args) + { + WriteFormat(LogCategory.Warning, exception, format, args); + } + + public void Error(string messageText) + { + Write(LogCategory.Error, messageText); + } + + public void Error(Exception error) + { + Write(LogCategory.Error, error); + } + + public void Error(Exception error, string messageText) + { + Write(LogCategory.Error, error, messageText); + } + + public void ErrorFormat(string format, params object[] args) + { + WriteFormat(LogCategory.Error, format, args); + } + + public void ErrorFormat(Exception exception, string format, params object[] args) + { + WriteFormat(LogCategory.Error, exception, format, args); + } + + public void Fatal(string messageText) + { + Write(LogCategory.Fatal, messageText); + } + + public void Fatal(Exception error) + { + Write(LogCategory.Fatal, error); + } + + public void Fatal(Exception error, string messageText) + { + Write(LogCategory.Fatal, error, messageText); + } + + public void FatalFormat(string messageFormat, params object[] args) + { + WriteFormat(LogCategory.Fatal, messageFormat, args); + } + + public void FatalFormat(Exception exception, string format, params object[] args) + { + WriteFormat(LogCategory.Fatal, exception, format, args); + } + + public void Flush() + { } + + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesAgentInstaller.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesAgentInstaller.cs new file mode 100644 index 00000000..5f295156 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesAgentInstaller.cs @@ -0,0 +1,227 @@ +using System.Diagnostics; +using System.Reflection; +using System.Text; +using Halibut; +using Halibut.Diagnostics; +using KubernetesAgent.Integration.Setup.Common; +using Octopus.Client.Model; +using Octopus.Tentacle.Client; +using Octopus.Tentacle.Client.Retries; +using Octopus.Tentacle.Client.Scripts; +using Octopus.Tentacle.Contracts.Observability; +using Octopus.Tentacle.Kubernetes.Tests.Integration.Support; + +namespace KubernetesAgent.Integration.Setup; + +public class KubernetesAgentInstaller +{ + //This is the DNS of the localhost Kubernetes Server we add to the cluster in the KubernetesClusterInstaller.SetLocalhostRouting() + const string LocalhostKubernetesServiceDns = "dockerhost.default.svc.cluster.local"; + + readonly string helmExePath; + readonly string kubeCtlExePath; + readonly TemporaryDirectory temporaryDirectory; + readonly ILogger logger; + readonly string kubeConfigPath; + protected HalibutRuntime ServerHalibutRuntime { get; private set; } = null!; + protected TentacleClient TentacleClient { get; private set; } = null!; + + bool isAgentInstalled; + + public KubernetesAgentInstaller(TemporaryDirectory temporaryDirectory, string helmExePath, string kubeCtlExePath, string kubeConfigPath, ILogger logger) + { + this.temporaryDirectory = temporaryDirectory; + this.helmExePath = helmExePath; + this.kubeCtlExePath = kubeCtlExePath; + this.kubeConfigPath = kubeConfigPath; + this.logger = logger; + + AgentName = Guid.NewGuid().ToString("N"); + } + + public string AgentName { get; } + + public string Namespace => $"octopus-agent-{AgentName}"; + + public Uri SubscriptionId { get; } = PollingSubscriptionIdGenerator.Generate(); + + public async Task InstallAgent() + { + var listeningPort = BuildServerHalibutRuntimeAndListen(); + var valuesFilePath = await WriteValuesFile(listeningPort); + var arguments = BuildAgentInstallArguments(valuesFilePath); + + var sw = new Stopwatch(); + sw.Restart(); + + + var result = ProcessRunner.RunWithLogger(helmExePath, temporaryDirectory, logger, arguments); + sw.Stop(); + + if (result.ExitCode != 0) + { + throw new InvalidOperationException($"Failed to install Kubernetes Agent via Helm."); + } + + isAgentInstalled = true; + + var thumbprint = await GetAgentThumbprint(); + + logger.Information("Agent certificate thumbprint: {Thumbprint:l}", thumbprint); + + ServerHalibutRuntime.Trust(thumbprint); + + BuildTentacleClient(thumbprint); + + return TentacleClient; + + } + + async Task WriteValuesFile(int listeningPort) + { + using var reader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStreamFromPartialName("agent-values.yaml")); + + var valuesFile = await reader.ReadToEndAsync(); + + var serverCommsAddress = $"https://{LocalhostKubernetesServiceDns}:{listeningPort}"; + + var configMapData = $@" + Octopus.Home: /octopus + Tentacle.Deployment.ApplicationDirectory: /octopus/Applications + Tentacle.Communication.TrustedOctopusServers: >- + [{{""Thumbprint"":""{TestCertificates.ServerPublicThumbprint}"",""CommunicationStyle"":{(int)CommunicationStyle.TentacleActive},""Address"":""{serverCommsAddress}"",""Squid"":null,""SubscriptionId"":""{SubscriptionId}""}}] + Tentacle.Services.IsRegistered: 'true' + Tentacle.Services.NoListen: 'true'"; + + valuesFile = valuesFile + .Replace("#{TargetName}", AgentName) + .Replace("#{ServerCommsAddress}", serverCommsAddress) + .Replace("#{ConfigMapData}", configMapData); + + var valuesFilePath = Path.Combine(temporaryDirectory.Directory.FullName, "agent-values.yaml"); + await File.WriteAllTextAsync(valuesFilePath, valuesFile, Encoding.UTF8); + + return valuesFilePath; + } + + string BuildAgentInstallArguments(string valuesFilePath) + { + var (chartVersion, chartRepo) = GetChartVersionAndRepository(); + var args = new[] + { + "upgrade", + "--install", + "--atomic", + $"-f \"{valuesFilePath}\"", + $"--version \"{chartVersion}\"", + "--create-namespace", + NamespaceFlag, + KubeConfigFlag, + AgentName, + chartRepo + }; + + return string.Join(" ", args.WhereNotNull()); + } + + static (string ChartVersion, string ChartRepo) GetChartVersionAndRepository() + { + var customHelmChartVersion = Environment.GetEnvironmentVariable("KubernetesIntegrationTests_HelmChartVersion"); + + return !string.IsNullOrWhiteSpace(customHelmChartVersion) + ? (customHelmChartVersion, "oci://docker.packages.octopushq.com/kubernetes-agent") + : ("1.*.*", "oci://registry-1.docker.io/octopusdeploy/kubernetes-agent"); + } + + static string? GetImageAndRepository(string? tentacleImageAndTag) + { + if (tentacleImageAndTag is null) + return null; + + var parts = tentacleImageAndTag.Split(":"); + var repo = parts[0]; + var tag = parts[1]; + + return $"--set agent.image.repository=\"{repo}\" --set agent.image.tag=\"{tag}\""; + } + + async Task GetAgentThumbprint() + { + string? thumbprint = null; + + var attempt = 0; + do + { + var result = ProcessRunner.Run(kubeCtlExePath, temporaryDirectory, $"get cm tentacle-config --namespace {Namespace} --kubeconfig=\"{kubeConfigPath}\" -o \"jsonpath={{.data['Tentacle\\.CertificateThumbprint']}}\""); + thumbprint = await result.StandardOutput.ReadToEndAsync(); + if (result.ExitCode != 0) + { + logger.Error("Failed to load thumbprint. Exit code {ExitCode}", result.ExitCode); + } + + if (!string.IsNullOrWhiteSpace(thumbprint)) + { + return thumbprint; + } + + if (attempt == 60) + { + break; + } + + attempt++; + await Task.Delay(1000); + } while (string.IsNullOrWhiteSpace(thumbprint)); + + throw new InvalidOperationException("Failed to load the generated thumbprint after 60 attempts"); + } + + void BuildTentacleClient(string thumbprint) + { + var endpoint = new ServiceEndPoint(SubscriptionId, thumbprint, ServerHalibutRuntime.TimeoutsAndLimits); + + var retrySettings = new RpcRetrySettings(true, TimeSpan.FromMinutes(2)); + var clientOptions = new TentacleClientOptions(retrySettings); + + TentacleClient.CacheServiceWasNotFoundResponseMessages(ServerHalibutRuntime); + + TentacleClient = new TentacleClient( + endpoint, + ServerHalibutRuntime, + new PollingTentacleScriptObserverBackoffStrategy(), + new NoTentacleClientObserver(), + clientOptions); + } + + int BuildServerHalibutRuntimeAndListen() + { + var serverHalibutRuntimeBuilder = new HalibutRuntimeBuilder() + .WithServerCertificate(TestCertificates.Server) + .WithHalibutTimeoutsAndLimits(HalibutTimeoutsAndLimits.RecommendedValues()); + + ServerHalibutRuntime = serverHalibutRuntimeBuilder.Build(); + + return ServerHalibutRuntime.Listen(); + } + string NamespaceFlag => $"--namespace \"{Namespace}\""; + string KubeConfigFlag => $"--kubeconfig \"{kubeConfigPath}\""; + + public void Dispose() + { + if (isAgentInstalled) + { + var uninstallArgs = string.Join(" ", + "uninstall", + KubeConfigFlag, + NamespaceFlag, + AgentName); + + var result = ProcessRunner.RunWithLogger(helmExePath, temporaryDirectory, logger, uninstallArgs); + + if (result.ExitCode != 0) + { + logger.Error("Failed to uninstall Kubernetes Agent {AgentName} via Helm", AgentName); + } + } + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs new file mode 100644 index 00000000..2623374a --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs @@ -0,0 +1,139 @@ +using System.Diagnostics; +using System.Reflection; +using KubernetesAgent.Integration.Setup.Common; +using Newtonsoft.Json; +using Octopus.Client.Extensions; +using Octopus.Tentacle.Kubernetes.Tests.Integration.Support; + +namespace KubernetesAgent.Integration.Setup; + +public class KubernetesClusterInstaller : IDisposable +{ + readonly string clusterName; + readonly string kubeConfigName; + + readonly TemporaryDirectory tempDir; + readonly string kindExePath; + readonly string helmExePath; + readonly string kubeCtlPath; + readonly ILogger logger; + + public string KubeConfigPath => Path.Combine(tempDir.Directory.FullName, kubeConfigName); + public string ClusterName => clusterName; + + public KubernetesClusterInstaller(TemporaryDirectory tempDirectory, string kindExePath, string helmExePath, string kubeCtlPath, ILogger logger) + { + tempDir = tempDirectory; + this.kindExePath = kindExePath; + this.helmExePath = helmExePath; + this.kubeCtlPath = kubeCtlPath; + this.logger = logger; + + clusterName = $"tentacleint-{DateTime.Now:yyyyMMddhhmmss}"; + kubeConfigName = $"{clusterName}.config"; + } + + public async Task Install() + { + var configFilePath = await WriteFileToTemporaryDirectory("kind-config.yaml"); + + var sw = new Stopwatch(); + sw.Restart(); + + var result = ProcessRunner.RunWithLogger(kindExePath, tempDir, logger, + $"create cluster --name={clusterName} --config=\"{configFilePath}\" --kubeconfig=\"{kubeConfigName}\""); + + sw.Stop(); + + if (result.ExitCode != 0) + { + logger.Error("Failed to create Kind Kubernetes cluster {ClusterName}", clusterName); + throw new InvalidOperationException($"Failed to create Kind Kubernetes cluster {clusterName}"); + } + + logger.Information("Test cluster kubeconfig path: {Path:l}", KubeConfigPath); + + logger.Information("Created Kind Kubernetes cluster {ClusterName} in {ElapsedTime}", clusterName, sw.Elapsed); + + await SetLocalhostRouting(); + + await InstallNfsCsiDriver(); + } + + async Task SetLocalhostRouting() + { + var filename = OperatingSystem.IsLinux() ? "linux-network-routing.yaml" : "docker-desktop-network-routing.yaml"; + + var manifestFilePath = await WriteFileToTemporaryDirectory(filename, "manifest.yaml"); + + var result = ProcessRunner.RunWithLogger(kubeCtlPath, tempDir, logger, + "apply", + "-n default", + $"-f \"{manifestFilePath}\"", + $"--kubeconfig=\"{KubeConfigPath}\""); + + if (result.ExitCode != 0) + { + logger.Error("Failed to apply localhost routing to cluster {ClusterName}", clusterName); + throw new InvalidOperationException($"Failed to apply localhost routing to cluster {clusterName}."); + } + } + + async Task WriteFileToTemporaryDirectory(string resourceFileName, string? outputFilename = null) + { + await using var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStreamFromPartialName(resourceFileName); + + var filePath = Path.Combine(tempDir.Directory.FullName, outputFilename ?? resourceFileName); + await using var file = File.Create(filePath); + + resourceStream.Seek(0, SeekOrigin.Begin); + await resourceStream.CopyToAsync(file); + + return filePath; + } + + async Task InstallNfsCsiDriver() + { + //we need to perform a repo update in helm first + // var exitCode = SilentProcessRunner.ExecuteCommand( + // helmPath, + // "repo update", + // tempDir.DirectoryPath, + // logger.Debug, + // logger.Information, + // logger.Error, + // CancellationToken.None); + + var installArgs = BuildNfsCsiDriverInstallArguments(); + var result = ProcessRunner.RunWithLogger(helmExePath, tempDir, logger, installArgs); + + if (result.ExitCode != 0) + { + throw new InvalidOperationException($"Failed to install NFS CSI driver into cluster {clusterName}."); + } + } + + string BuildNfsCsiDriverInstallArguments() + { + return string.Join(" ", + "install", + "--atomic", + "--repo https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts", + "--namespace kube-system", + "--version v4.6.0", + $"--kubeconfig \"{KubeConfigPath}\"", + "csi-driver-nfs", + "csi-driver-nfs" + ); + } + + public void Dispose() + { + var result = ProcessRunner.Run(kindExePath, + $"delete cluster --name={clusterName}"); + if (result.ExitCode != 0) + { + logger.Error("Failed to delete Kind kubernetes cluster {ClusterName}", clusterName); + } + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/RequiredToolDownloader.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/RequiredToolDownloader.cs new file mode 100644 index 00000000..00320653 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/RequiredToolDownloader.cs @@ -0,0 +1,32 @@ +using KubernetesAgent.Integration.Setup.Common; +using KubernetesAgent.Integration.Setup.Tooling; + +namespace KubernetesAgent.Integration.Setup; + +public class RequiredToolDownloader +{ + readonly TemporaryDirectory temporaryDirectory; + readonly KindDownloader kindDownloader; + readonly HelmDownloader helmDownloader; + readonly KubeCtlDownloader kubeCtlDownloader; + + public RequiredToolDownloader(TemporaryDirectory temporaryDirectory, ILogger logger) + { + this.temporaryDirectory = temporaryDirectory; + + kindDownloader = new KindDownloader(logger); + helmDownloader = new HelmDownloader(logger); + kubeCtlDownloader = new KubeCtlDownloader(logger); + } + + public async Task<(string KindExePath, string HelmExePath, string KubeCtlPath)> DownloadRequiredTools(CancellationToken cancellationToken) + { + var kindExePathTask = kindDownloader.Download(temporaryDirectory.Directory.FullName, cancellationToken); + var helmExePathTask = helmDownloader.Download(temporaryDirectory.Directory.FullName, cancellationToken); + var kubeCtlExePathTask = kubeCtlDownloader.Download(temporaryDirectory.Directory.FullName, cancellationToken); + + await Task.WhenAll(kindExePathTask, helmExePathTask, kubeCtlExePathTask); + + return (kindExePathTask.Result, helmExePathTask.Result, kubeCtlExePathTask.Result); + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/ServerHalibutRuntimeRunner.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/ServerHalibutRuntimeRunner.cs new file mode 100644 index 00000000..a7205f07 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/ServerHalibutRuntimeRunner.cs @@ -0,0 +1,27 @@ +using Halibut; +using Halibut.Diagnostics; +using KubernetesAgent.Integration.Setup.Common; + +namespace KubernetesAgent.Integration.Setup +{ + public class ServerHalibutRuntime + { + public HalibutRuntime ServerHalibutRuntimeInstance { get; set; } = null!; + public int BuildServerHalibutRuntimeAndListen() + { + var serverHalibutRuntimeBuilder = new HalibutRuntimeBuilder() + .WithServerCertificate(TestCertificates.Server) + .WithHalibutTimeoutsAndLimits(HalibutTimeoutsAndLimits.RecommendedValues()); + + ServerHalibutRuntimeInstance = serverHalibutRuntimeBuilder.Build(); + + return ServerHalibutRuntimeInstance.Listen(); + } + + public void TrustThumbprint(string thumbprint) + { + ServerHalibutRuntimeInstance.Trust(thumbprint); + } + + } +} diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/HelmDownloader.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/HelmDownloader.cs new file mode 100644 index 00000000..f39dac3f --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/HelmDownloader.cs @@ -0,0 +1,67 @@ +using System.Formats.Tar; +using System.IO.Compression; +using System.Runtime.InteropServices; +using KubernetesAgent.Integration.Setup.Common; +using KubernetesAgent.Integration.Setup.Common.Extraction; + +namespace KubernetesAgent.Integration.Setup.Tooling; + +public class HelmDownloader : ToolDownloader +{ + const string LatestVersion = "v3.14.3"; + public HelmDownloader( ILogger logger) + : base("helm", logger) + { + } + + protected override string BuildDownloadUrl(Architecture processArchitecture, OperatingSystem operatingSystem) + { + var architecture = GetArchitectureLabel(processArchitecture); + var osName = GetOsName(operatingSystem); + + var suffix = operatingSystem is OperatingSystem.Windows ? "zip" : "tar.gz"; + + return $"https://get.helm.sh/helm-{LatestVersion}-{osName}-{architecture}.{suffix}"; + } + + static string GetArchitectureLabel(Architecture processArchitecture) => processArchitecture == Architecture.Arm64 ? "arm64" : "amd64"; + + protected override string PostDownload(string targetDirectory, string downloadFilePath, Architecture processArchitecture, OperatingSystem operatingSystem) + { + var architecture = GetArchitectureLabel(processArchitecture); + var osName = GetOsName(operatingSystem); + + var extractionDir = Path.Combine(targetDirectory, "extracted"); + + //the helm app is zipped, so we need to extract it + if (operatingSystem is OperatingSystem.Windows) + { + //on windows we need to unzip the file + ZipFile.ExtractToDirectory(downloadFilePath, extractionDir); + } + else + { + //everything else is tar.gz + TarFileExtractor.ExtractAndMakeExecutable(downloadFilePath, extractionDir); + } + + //move the extracted helm executable to the root target directory + var targetFilePath = Path.Combine(targetDirectory, ExecutableName); + File.Move(Path.Combine(extractionDir,$"{osName}-{architecture}", ExecutableName), targetFilePath); + + //delete the extracted directory + Directory.Delete(extractionDir,true); + File.Delete(downloadFilePath); + + return targetFilePath; + } + + static string GetOsName(OperatingSystem operatingSystem) + => operatingSystem switch + { + OperatingSystem.Windows => "windows", + OperatingSystem.Nix => "linux", + OperatingSystem.Mac => "darwin", + _ => throw new ArgumentOutOfRangeException(nameof(operatingSystem), operatingSystem, null) + }; +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/IToolDownloader.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/IToolDownloader.cs new file mode 100644 index 00000000..24bd777d --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/IToolDownloader.cs @@ -0,0 +1,6 @@ +namespace KubernetesAgent.Integration.Setup.Tooling; + +public interface IToolDownloader +{ + Task Download(string targetDirectory, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/KindDownloader.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/KindDownloader.cs new file mode 100644 index 00000000..29f32aea --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/KindDownloader.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace KubernetesAgent.Integration.Setup.Tooling +{ + public class KindDownloader : ToolDownloader + { + const string LatestKindVersion = "v0.22.0"; + + public KindDownloader(ILogger logger) + : base("kind", logger) + { + } + + protected override string BuildDownloadUrl(Architecture processArchitecture, OperatingSystem operatingSystem) + { + var architecture = processArchitecture == Architecture.Arm64 ? "arm64" : "amd64"; + var osName = GetOsName(operatingSystem); + + return $"https://github.com/kubernetes-sigs/kind/releases/download/{LatestKindVersion}/kind-{osName}-{architecture}"; + } + + static string GetOsName(OperatingSystem operatingSystem) + => operatingSystem switch + { + OperatingSystem.Windows => "windows", + OperatingSystem.Nix => "linux", + OperatingSystem.Mac => "darwin", + _ => throw new ArgumentOutOfRangeException(nameof(operatingSystem), operatingSystem, null) + }; + } +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/KubeCtlDownloader.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/KubeCtlDownloader.cs new file mode 100644 index 00000000..64a7b2b1 --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/KubeCtlDownloader.cs @@ -0,0 +1,33 @@ +using System.Runtime.InteropServices; + +namespace KubernetesAgent.Integration.Setup.Tooling; + +public class KubeCtlDownloader : ToolDownloader +{ + public const string LatestKubeCtlVersion = "v1.29.3"; + + public KubeCtlDownloader(ILogger logger) + : base("kubectl", logger) + { } + + protected override string BuildDownloadUrl(Architecture processArchitecture, OperatingSystem operatingSystem) + { + var architecture = processArchitecture == Architecture.Arm64 ? "arm64" : "amd64"; + var osName = GetOsName(operatingSystem); + + var extension = operatingSystem is OperatingSystem.Windows + ? ".exe" + : null; + + return $"https://dl.k8s.io/release/{LatestKubeCtlVersion}/bin/{osName}/{architecture}/kubectl{extension}"; + } + + static string GetOsName(OperatingSystem operatingSystem) + => operatingSystem switch + { + OperatingSystem.Windows => "windows", + OperatingSystem.Nix => "linux", + OperatingSystem.Mac => "darwin", + _ => throw new ArgumentOutOfRangeException(nameof(operatingSystem), operatingSystem, null) + }; +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/ToolDownloader.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/ToolDownloader.cs new file mode 100644 index 00000000..0b97e06d --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/ToolDownloader.cs @@ -0,0 +1,89 @@ +using System.Net; +using System.Runtime.InteropServices; +using KubernetesAgent.Integration.Setup.Common; + +namespace KubernetesAgent.Integration.Setup.Tooling; + +public abstract class ToolDownloader : IToolDownloader +{ + readonly OperatingSystem os; + + protected ILogger Logger { get; } + protected string ExecutableName { get; } + + protected ToolDownloader(string executableName, ILogger logger) + { + ExecutableName = executableName; + Logger = logger; + + os = GetOperationSystem(); + + //we assume that windows always has .exe suffixed + if (os is OperatingSystem.Windows) + { + ExecutableName += ".exe"; + } + } + + public async Task Download(string targetDirectory, CancellationToken cancellationToken) + { + var downloadUrl = BuildDownloadUrl(RuntimeInformation.ProcessArchitecture, os); + + //we download to a random file name + var downloadFilePath = Path.Combine(targetDirectory, Guid.NewGuid().ToString("N")); + + Logger.Information("Downloading {DownloadUrl} to {DownloadFilePath}", downloadUrl, downloadFilePath); + using (var client = new HttpClient()) + { + + await using (var s = await client.GetStreamAsync(downloadUrl, cancellationToken)) + { + await using (var fs = new FileStream(downloadFilePath, FileMode.OpenOrCreate)) + { + await s.CopyToAsync(fs, cancellationToken); + } + } + } + + downloadFilePath = PostDownload(targetDirectory, downloadFilePath, RuntimeInformation.ProcessArchitecture, os); + + return downloadFilePath; + } + + protected abstract string BuildDownloadUrl(Architecture processArchitecture, OperatingSystem operatingSystem); + + protected virtual string PostDownload(string downloadDirectory, string downloadFilePath, Architecture processArchitecture, OperatingSystem operatingSystem) + { + var targetFilename = Path.Combine(downloadDirectory, ExecutableName); + File.Move(downloadFilePath, targetFilename); + new FileInfo(targetFilename).MakeExecutable(); + + return targetFilename; + } + + static OperatingSystem GetOperationSystem() + { + if (System.OperatingSystem.IsWindows()) + { + return OperatingSystem.Windows; + } + if (System.OperatingSystem.IsLinux()) + { + return OperatingSystem.Nix; + } + + if (System.OperatingSystem.IsMacOS()) + { + return OperatingSystem.Mac; + } + + throw new InvalidOperationException("Unsupported OS"); + } +} + +public enum OperatingSystem +{ + Windows, + Nix, + Mac +} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/agent-values.yaml b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/agent-values.yaml new file mode 100644 index 00000000..3b0bb2be --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/agent-values.yaml @@ -0,0 +1,14 @@ +agent: + acceptEula: "Y" + targetName: "#{TargetName}" + serverCommsAddress: "#{ServerCommsAddress}" + serverUrl: "https://this.is.not.required.com/" + bearerToken: "this-is-a-fake-bearer-token" + space: "Default" + targetEnvironments: ["development"] + targetRoles: ["testing-cluster"] + +testing: + tentacle: + configMap: + data: #{ConfigMapData} \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/docker-desktop-network-routing.yaml b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/docker-desktop-network-routing.yaml new file mode 100644 index 00000000..a66ba86d --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/docker-desktop-network-routing.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Service +metadata: + name: dockerhost + namespace: default +spec: + type: ExternalName + externalName: host.docker.internal \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/kind-config.yaml b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/kind-config.yaml new file mode 100644 index 00000000..37eb950b --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/kind-config.yaml @@ -0,0 +1,8 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 + +nodes: + - role: control-plane + image: kindest/node:v1.29.2@sha256:51a1434a5397193442f0be2a297b488b6c919ce8a3931be0ce822606ea5ca245 + - role: worker + image: kindest/node:v1.29.2@sha256:51a1434a5397193442f0be2a297b488b6c919ce8a3931be0ce822606ea5ca245 diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/linux-network-routing.yaml b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/linux-network-routing.yaml new file mode 100644 index 00000000..b6f8e31c --- /dev/null +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/linux-network-routing.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Endpoints +metadata: + name: dockerhost + namespace: default +subsets: + - addresses: + - ip: 172.17.0.1 # this is the gateway IP in the "bridge" docker network +--- +apiVersion: v1 +kind: Service +metadata: + name: dockerhost + namespace: default +spec: + clusterIP: None \ No newline at end of file diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/UnitTest1.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/UnitTest1.cs deleted file mode 100644 index 3df9af36..00000000 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/UnitTest1.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace KubernetesAgent.Integration; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - } -} \ No newline at end of file From abcffff9ffa4735ef9358b1125879bea8ebb607a Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 10:40:57 +1000 Subject: [PATCH 03/16] Minor cleanup --- .../RunsAgentUpgrade.cs | 5 +---- .../Setup/KubernetesClusterInstaller.cs | 10 ---------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs index fa8b8e9d..508d2ec5 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs @@ -16,9 +16,6 @@ public class HelmUpgradeTests(ITestOutputHelper output) : IAsyncLifetime readonly TemporaryDirectory workingDirectory = new(Directory.CreateTempSubdirectory()); KubernetesClusterInstaller clusterInstaller = null!; KubernetesAgentInstaller agentInstaller = null!; - string kindExePath = null!; - string helmExePath = null!; - string kubeCtlPath = null!; TentacleClient client = null!; public async Task InitializeAsync() @@ -29,7 +26,7 @@ public async Task InitializeAsync() .CreateLogger(); var requiredToolDownloader = new RequiredToolDownloader(workingDirectory, logger); - (kindExePath, helmExePath, kubeCtlPath) = await requiredToolDownloader.DownloadRequiredTools(CancellationToken.None); + var (kindExePath, helmExePath, kubeCtlPath) = await requiredToolDownloader.DownloadRequiredTools(CancellationToken.None); clusterInstaller = new KubernetesClusterInstaller(workingDirectory, kindExePath, helmExePath, kubeCtlPath, logger); await clusterInstaller.Install(); agentInstaller = new KubernetesAgentInstaller(workingDirectory , helmExePath, kubeCtlPath, clusterInstaller.KubeConfigPath, logger); diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs index 2623374a..e8af46a1 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs @@ -94,16 +94,6 @@ async Task WriteFileToTemporaryDirectory(string resourceFileName, string async Task InstallNfsCsiDriver() { - //we need to perform a repo update in helm first - // var exitCode = SilentProcessRunner.ExecuteCommand( - // helmPath, - // "repo update", - // tempDir.DirectoryPath, - // logger.Debug, - // logger.Information, - // logger.Error, - // CancellationToken.None); - var installArgs = BuildNfsCsiDriverInstallArguments(); var result = ProcessRunner.RunWithLogger(helmExePath, tempDir, logger, installArgs); From f227db6584b7f9992d551faa733955ecd16b51eb Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 11:01:29 +1000 Subject: [PATCH 04/16] Add nuget config --- tests/kubernetes-agent/NuGet.Config | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/kubernetes-agent/NuGet.Config diff --git a/tests/kubernetes-agent/NuGet.Config b/tests/kubernetes-agent/NuGet.Config new file mode 100644 index 00000000..13f33fd0 --- /dev/null +++ b/tests/kubernetes-agent/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From 5b0dff9849c7ee8203f236eab1954c4db088580a Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 11:01:45 +1000 Subject: [PATCH 05/16] Add .DS_STORE to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index efc2f5b4..e01f1a7e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ octopus*.tgz index.yaml node_modules +.DS_STORE + *.orig \ No newline at end of file From 8025a3df5d70c3db401e8312886538c165bdbbd1 Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 11:05:07 +1000 Subject: [PATCH 06/16] We need the certs --- tests/kubernetes-agent/.gitignore | 1 - .../Setup/Common/Certificates/Server.pfx | Bin 0 -> 2676 bytes .../Setup/Common/Certificates/Tentacle.pfx | Bin 0 -> 2700 bytes 3 files changed, 1 deletion(-) create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/Certificates/Server.pfx create mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/Certificates/Tentacle.pfx diff --git a/tests/kubernetes-agent/.gitignore b/tests/kubernetes-agent/.gitignore index 5e57f180..4763eb2d 100644 --- a/tests/kubernetes-agent/.gitignore +++ b/tests/kubernetes-agent/.gitignore @@ -242,7 +242,6 @@ ClientBin/ *.dbmdl *.dbproj.schemaview *.jfm -*.pfx *.publishsettings orleans.codegen.cs diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/Certificates/Server.pfx b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/Certificates/Server.pfx new file mode 100644 index 0000000000000000000000000000000000000000..3d24af1340d377b19b09d0786f8a172285ed52d9 GIT binary patch literal 2676 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 NWW_jpEgpz${R!pN)hz%3 literal 0 HcmV?d00001 From e9e33ff4c81d5ed1f901ed51deafef0112d13417 Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 11:12:10 +1000 Subject: [PATCH 07/16] Add Global.json --- tests/kubernetes-agent/Global.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/kubernetes-agent/Global.json diff --git a/tests/kubernetes-agent/Global.json b/tests/kubernetes-agent/Global.json new file mode 100644 index 00000000..e7bb7668 --- /dev/null +++ b/tests/kubernetes-agent/Global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.202", + "rollForward": "latestFeature" + } +} \ No newline at end of file From cf07c7b4231558e3fb534a113b099d261e9f4689 Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 11:13:37 +1000 Subject: [PATCH 08/16] Move Global.json --- tests/kubernetes-agent/Global.json => Global.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/kubernetes-agent/Global.json => Global.json (100%) diff --git a/tests/kubernetes-agent/Global.json b/Global.json similarity index 100% rename from tests/kubernetes-agent/Global.json rename to Global.json From 61012c6edfd29ca1b92ebc238f865957c6ce5597 Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 11:15:26 +1000 Subject: [PATCH 09/16] Change version to be same as server --- Global.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Global.json b/Global.json index e7bb7668..6c5cf46a 100644 --- a/Global.json +++ b/Global.json @@ -1,6 +1,7 @@ { "sdk": { - "version": "8.0.202", - "rollForward": "latestFeature" + "version": "8.0.101", + "rollForward": "latestFeature", + "allowPrerelease": false } } \ No newline at end of file From 0d812e3e7235825081e44c85b7c9e4b8a7f1c369 Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 12:23:23 +1000 Subject: [PATCH 10/16] Only run one test --- .../RunsAgentUpgrade.cs | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs index 508d2ec5..670f6a44 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs @@ -39,25 +39,7 @@ public async Task DisposeAsync() workingDirectory.Dispose(); await Task.CompletedTask; } - - [Fact] - public async Task CanRunCommandOnExistingAgent() - { - var scriptCommand = new ExecuteKubernetesScriptCommandBuilder(Guid.NewGuid().ToString()).WithScriptBody("echo 'Hello World'").Build(); - void onScriptStatusResponseReceived(ScriptExecutionStatus res) => logger.Information("{Output}", res.ToString()); - async Task onScriptCompleted(CancellationToken t) - { - await Task.CompletedTask; - logger.Information("Script completed"); - } - var testLogger = new TestLogger(logger); - var result = await client.ExecuteScript(scriptCommand, onScriptStatusResponseReceived, onScriptCompleted, testLogger, CancellationToken.None); - if (result.ExitCode != 0) - { - throw new Exception($"Script failed with exit code {result.ExitCode}"); - } - } - + [Fact] public async Task CanUpgradeAgentAndRunCommand() { From 821387c1b64d30fa45e7910fe953e4adb7c8451f Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 12:25:10 +1000 Subject: [PATCH 11/16] Add script run after upgrade --- .../RunsAgentUpgrade.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs index 670f6a44..e73ae3e3 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs @@ -71,5 +71,16 @@ async Task onScriptCompleted(CancellationToken t) { throw new Exception($"Script failed with exit code {result.ExitCode}"); } + logger.Information("Upgrade executed successfully"); + + scriptCommand = new ExecuteKubernetesScriptCommandBuilder(Guid.NewGuid().ToString()) + .WithScriptBody("echo \"hello world\"") + .Build(); + result = await client.ExecuteScript(scriptCommand, onScriptStatusResponseReceived, onScriptCompleted, testLogger, CancellationToken.None); + if (result.ExitCode != 0) + { + throw new Exception($"Script failed with exit code {result.ExitCode}"); + } + logger.Information("Script executed successfully"); } } \ No newline at end of file From df290e527738c43bfab2ddd165802f396affad37 Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 12:31:59 +1000 Subject: [PATCH 12/16] Pass executable through --- .../KubernetesAgent.IntegrationTests/HelmChartBuilder.cs | 4 ++-- .../KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/HelmChartBuilder.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/HelmChartBuilder.cs index 5318c726..0e53dfdf 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/HelmChartBuilder.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/HelmChartBuilder.cs @@ -5,12 +5,12 @@ namespace KubernetesAgent.Integration { public static class HelmChartBuilder { - public static string BuildHelmChart(TemporaryDirectory directory) + public static string BuildHelmChart(string helmExecutable, TemporaryDirectory directory) { var version = GetChartVersion(); version = $"{version}-{DateTime.Now:yyyymdHHmmss}"; - var packager = ProcessRunner.Run("helm", directory, GetHelmChartPackageArguments(version)); + var packager = ProcessRunner.Run(helmExecutable, directory, GetHelmChartPackageArguments(version)); if (packager.ExitCode != 0) { throw new Exception($"Failed to package Helm chart. Exit code: {packager.ExitCode}"); diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs index e73ae3e3..e0564f17 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs @@ -17,6 +17,9 @@ public class HelmUpgradeTests(ITestOutputHelper output) : IAsyncLifetime KubernetesClusterInstaller clusterInstaller = null!; KubernetesAgentInstaller agentInstaller = null!; TentacleClient client = null!; + string kindExePath = null!; + string helmExePath = null!; + string kubeCtlPath = null!; public async Task InitializeAsync() { @@ -26,7 +29,7 @@ public async Task InitializeAsync() .CreateLogger(); var requiredToolDownloader = new RequiredToolDownloader(workingDirectory, logger); - var (kindExePath, helmExePath, kubeCtlPath) = await requiredToolDownloader.DownloadRequiredTools(CancellationToken.None); + (kindExePath, helmExePath, kubeCtlPath) = await requiredToolDownloader.DownloadRequiredTools(CancellationToken.None); clusterInstaller = new KubernetesClusterInstaller(workingDirectory, kindExePath, helmExePath, kubeCtlPath, logger); await clusterInstaller.Install(); agentInstaller = new KubernetesAgentInstaller(workingDirectory , helmExePath, kubeCtlPath, clusterInstaller.KubeConfigPath, logger); @@ -43,7 +46,7 @@ public async Task DisposeAsync() [Fact] public async Task CanUpgradeAgentAndRunCommand() { - var helmPackage = HelmChartBuilder.BuildHelmChart(workingDirectory); + var helmPackage = HelmChartBuilder.BuildHelmChart(helmExePath, workingDirectory); var helmPackageFile = new FileInfo(helmPackage); var packageName = helmPackageFile.Name; var packageBytes = await File.ReadAllBytesAsync(helmPackage); From 6abf07d67917fddfa5ec8d6cb71ba9d7c82c9a4a Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 12:42:55 +1000 Subject: [PATCH 13/16] Log the cleanup --- .../Setup/KubernetesClusterInstaller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs index e8af46a1..010ff8fd 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs @@ -119,7 +119,7 @@ string BuildNfsCsiDriverInstallArguments() public void Dispose() { - var result = ProcessRunner.Run(kindExePath, + var result = ProcessRunner.RunWithLogger(kindExePath, tempDir, logger, $"delete cluster --name={clusterName}"); if (result.ExitCode != 0) { From 6011b4a399e47ca795b93f78aefff250845f74f2 Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 14:50:46 +1000 Subject: [PATCH 14/16] Swap chmod args --- .../Setup/Common/FileSystemInfoExtensionMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/FileSystemInfoExtensionMethods.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/FileSystemInfoExtensionMethods.cs index d37448bf..4f52a3ad 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/FileSystemInfoExtensionMethods.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/FileSystemInfoExtensionMethods.cs @@ -4,7 +4,7 @@ public static class FileSystemInfoExtensionMethods { public static void MakeExecutable(this FileSystemInfo fsObject) { - var result = ProcessRunner.Run("chmod", "+x", "-R", fsObject.FullName); + var result = ProcessRunner.Run("chmod", "-R", "+x", fsObject.FullName); if (result.ExitCode != 0) { throw new Exception($"Failed to make {fsObject.FullName} executable. Exit code: {result.ExitCode}. stdout: {result.StandardOutput.ReadToEnd()}, stderr: {result.StandardError.ReadToEnd()}."); From 8eb47284e76985b289fd6f114e399986677fe932 Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 15:55:44 +1000 Subject: [PATCH 15/16] PR Feedback --- .../RunsAgentUpgrade.cs | 8 +++--- .../Setup/Common/TestCertificates.cs | 2 -- .../Setup/Common/TestLogger.cs | 3 --- .../Setup/KubernetesClusterInstaller.cs | 9 +++---- .../Setup/ServerHalibutRuntimeRunner.cs | 27 ------------------- .../Setup/Tooling/HelmDownloader.cs | 4 +-- .../Setup/Tooling/ToolDownloader.cs | 3 +-- 7 files changed, 9 insertions(+), 47 deletions(-) delete mode 100644 tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/ServerHalibutRuntimeRunner.cs diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs index e0564f17..51e0c343 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/RunsAgentUpgrade.cs @@ -58,7 +58,7 @@ public async Task CanUpgradeAgentAndRunCommand() {agentInstaller.AgentName} \ /tmp/{packageName} """; - var scriptCommand = new ExecuteKubernetesScriptCommandBuilder(Guid.NewGuid().ToString()) + var upgradeHelmChartCommand = new ExecuteKubernetesScriptCommandBuilder(Guid.NewGuid().ToString()) .WithScriptBody($"cp ./{packageName} /tmp/{packageName} && {helmUpgradeScript}") .WithScriptFile(new ScriptFile(packageName, DataStream.FromBytes(packageBytes))) .Build(); @@ -69,17 +69,17 @@ async Task onScriptCompleted(CancellationToken t) logger.Information("Script completed"); } var testLogger = new TestLogger(logger); - var result = await client.ExecuteScript(scriptCommand, onScriptStatusResponseReceived, onScriptCompleted, testLogger, CancellationToken.None); + var result = await client.ExecuteScript(upgradeHelmChartCommand, onScriptStatusResponseReceived, onScriptCompleted, testLogger, CancellationToken.None); if (result.ExitCode != 0) { throw new Exception($"Script failed with exit code {result.ExitCode}"); } logger.Information("Upgrade executed successfully"); - scriptCommand = new ExecuteKubernetesScriptCommandBuilder(Guid.NewGuid().ToString()) + var runHelloWorldCommand = new ExecuteKubernetesScriptCommandBuilder(Guid.NewGuid().ToString()) .WithScriptBody("echo \"hello world\"") .Build(); - result = await client.ExecuteScript(scriptCommand, onScriptStatusResponseReceived, onScriptCompleted, testLogger, CancellationToken.None); + result = await client.ExecuteScript(runHelloWorldCommand, onScriptStatusResponseReceived, onScriptCompleted, testLogger, CancellationToken.None); if (result.ExitCode != 0) { throw new Exception($"Script failed with exit code {result.ExitCode}"); diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestCertificates.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestCertificates.cs index 453037d8..a49eb95c 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestCertificates.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestCertificates.cs @@ -1,6 +1,4 @@ -using System.Reflection; using System.Security.Cryptography.X509Certificates; -using Octopus.Tentacle.Kubernetes.Tests.Integration.Support; namespace KubernetesAgent.Integration.Setup.Common { diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestLogger.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestLogger.cs index 2f55ab40..4e1d3573 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestLogger.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TestLogger.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Concurrent; -using Halibut.Diagnostics; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Octopus.Diagnostics; using ILog = Octopus.Diagnostics.ILog; diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs index 010ff8fd..4fd8a4e1 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs @@ -1,8 +1,6 @@ using System.Diagnostics; using System.Reflection; using KubernetesAgent.Integration.Setup.Common; -using Newtonsoft.Json; -using Octopus.Client.Extensions; using Octopus.Tentacle.Kubernetes.Tests.Integration.Support; namespace KubernetesAgent.Integration.Setup; @@ -19,7 +17,6 @@ public class KubernetesClusterInstaller : IDisposable readonly ILogger logger; public string KubeConfigPath => Path.Combine(tempDir.Directory.FullName, kubeConfigName); - public string ClusterName => clusterName; public KubernetesClusterInstaller(TemporaryDirectory tempDirectory, string kindExePath, string helmExePath, string kubeCtlPath, ILogger logger) { @@ -29,7 +26,7 @@ public KubernetesClusterInstaller(TemporaryDirectory tempDirectory, string kindE this.kubeCtlPath = kubeCtlPath; this.logger = logger; - clusterName = $"tentacleint-{DateTime.Now:yyyyMMddhhmmss}"; + clusterName = $"helm-octopus-agent-int-{DateTime.Now:yyyyMMddhhmmss}"; kubeConfigName = $"{clusterName}.config"; } @@ -57,7 +54,7 @@ public async Task Install() await SetLocalhostRouting(); - await InstallNfsCsiDriver(); + InstallNfsCsiDriver(); } async Task SetLocalhostRouting() @@ -92,7 +89,7 @@ async Task WriteFileToTemporaryDirectory(string resourceFileName, string return filePath; } - async Task InstallNfsCsiDriver() + void InstallNfsCsiDriver() { var installArgs = BuildNfsCsiDriverInstallArguments(); var result = ProcessRunner.RunWithLogger(helmExePath, tempDir, logger, installArgs); diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/ServerHalibutRuntimeRunner.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/ServerHalibutRuntimeRunner.cs deleted file mode 100644 index a7205f07..00000000 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/ServerHalibutRuntimeRunner.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Halibut; -using Halibut.Diagnostics; -using KubernetesAgent.Integration.Setup.Common; - -namespace KubernetesAgent.Integration.Setup -{ - public class ServerHalibutRuntime - { - public HalibutRuntime ServerHalibutRuntimeInstance { get; set; } = null!; - public int BuildServerHalibutRuntimeAndListen() - { - var serverHalibutRuntimeBuilder = new HalibutRuntimeBuilder() - .WithServerCertificate(TestCertificates.Server) - .WithHalibutTimeoutsAndLimits(HalibutTimeoutsAndLimits.RecommendedValues()); - - ServerHalibutRuntimeInstance = serverHalibutRuntimeBuilder.Build(); - - return ServerHalibutRuntimeInstance.Listen(); - } - - public void TrustThumbprint(string thumbprint) - { - ServerHalibutRuntimeInstance.Trust(thumbprint); - } - - } -} diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/HelmDownloader.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/HelmDownloader.cs index f39dac3f..f29f8c8d 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/HelmDownloader.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/HelmDownloader.cs @@ -1,7 +1,5 @@ -using System.Formats.Tar; -using System.IO.Compression; +using System.IO.Compression; using System.Runtime.InteropServices; -using KubernetesAgent.Integration.Setup.Common; using KubernetesAgent.Integration.Setup.Common.Extraction; namespace KubernetesAgent.Integration.Setup.Tooling; diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/ToolDownloader.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/ToolDownloader.cs index 0b97e06d..eafe1c67 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/ToolDownloader.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Tooling/ToolDownloader.cs @@ -1,5 +1,4 @@ -using System.Net; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using KubernetesAgent.Integration.Setup.Common; namespace KubernetesAgent.Integration.Setup.Tooling; From 2f78b35f1375f806fac79c3f03d00117ffdd2d8e Mon Sep 17 00:00:00 2001 From: Liam Mackie Date: Fri, 10 May 2024 15:56:37 +1000 Subject: [PATCH 16/16] Update namespace --- .../Setup/Common/AssemblyExtensions.cs | 5 +++-- .../Setup/Common/TemporaryDirectoryExtensionMethods.cs | 1 - .../Setup/KubernetesAgentInstaller.cs | 1 - .../Setup/KubernetesClusterInstaller.cs | 1 - 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/AssemblyExtensions.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/AssemblyExtensions.cs index 882b8ac3..59889b0c 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/AssemblyExtensions.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/AssemblyExtensions.cs @@ -1,6 +1,7 @@ -using System.Reflection; +using System; +using System.Reflection; -namespace Octopus.Tentacle.Kubernetes.Tests.Integration.Support; +namespace KubernetesAgent.Integration.Setup.Common; public static class AssemblyExtensions { diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectoryExtensionMethods.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectoryExtensionMethods.cs index b358641a..fc15b69f 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectoryExtensionMethods.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/Common/TemporaryDirectoryExtensionMethods.cs @@ -1,5 +1,4 @@ using System.Reflection; -using Octopus.Tentacle.Kubernetes.Tests.Integration.Support; namespace KubernetesAgent.Integration.Setup.Common { diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesAgentInstaller.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesAgentInstaller.cs index 5f295156..f3d61670 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesAgentInstaller.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesAgentInstaller.cs @@ -9,7 +9,6 @@ using Octopus.Tentacle.Client.Retries; using Octopus.Tentacle.Client.Scripts; using Octopus.Tentacle.Contracts.Observability; -using Octopus.Tentacle.Kubernetes.Tests.Integration.Support; namespace KubernetesAgent.Integration.Setup; diff --git a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs index 4fd8a4e1..c982c970 100644 --- a/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs +++ b/tests/kubernetes-agent/KubernetesAgent.IntegrationTests/Setup/KubernetesClusterInstaller.cs @@ -1,7 +1,6 @@ using System.Diagnostics; using System.Reflection; using KubernetesAgent.Integration.Setup.Common; -using Octopus.Tentacle.Kubernetes.Tests.Integration.Support; namespace KubernetesAgent.Integration.Setup;