From 69f9fcf7e4dce6c2eb081658b30a63c92fb608a6 Mon Sep 17 00:00:00 2001 From: Alastair Pitts Date: Mon, 24 Nov 2025 10:12:28 +1100 Subject: [PATCH 1/2] Upgrade to K8s SDK to support 1.34 --- .../Octopus.Manager.Tentacle.csproj | 2 +- .../KubernetesClientCompatibilityTests.cs | 2 +- ...pus.Tentacle.Kubernetes.Tests.Integration.csproj | 6 +++--- .../Setup/KindConfiguration/kind-config-v1-30.yaml | 8 -------- .../Setup/KindConfiguration/kind-config-v1-31.yaml | 4 ++-- .../Setup/KindConfiguration/kind-config-v1-32.yaml | 4 ++-- .../Setup/KindConfiguration/kind-config-v1-33.yaml | 4 ++-- .../Setup/KindConfiguration/kind-config-v1-34.yaml | 8 ++++++++ .../Setup/Tooling/HelmDownloader.cs | 2 +- .../Setup/Tooling/KindDownloader.cs | 2 +- .../Setup/Tooling/KubeCtlDownloader.cs | 2 +- source/Octopus.Tentacle/Octopus.Tentacle.csproj | 13 ++++++++----- 12 files changed, 30 insertions(+), 27 deletions(-) delete mode 100644 source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-30.yaml create mode 100644 source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-34.yaml diff --git a/source/Octopus.Manager.Tentacle/Octopus.Manager.Tentacle.csproj b/source/Octopus.Manager.Tentacle/Octopus.Manager.Tentacle.csproj index bed230c84..83a5ff374 100644 --- a/source/Octopus.Manager.Tentacle/Octopus.Manager.Tentacle.csproj +++ b/source/Octopus.Manager.Tentacle/Octopus.Manager.Tentacle.csproj @@ -124,7 +124,7 @@ - + diff --git a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/KubernetesClientCompatibilityTests.cs b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/KubernetesClientCompatibilityTests.cs index 96bc605c8..114340bc1 100644 --- a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/KubernetesClientCompatibilityTests.cs +++ b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/KubernetesClientCompatibilityTests.cs @@ -21,10 +21,10 @@ public class KubernetesClientCompatibilityTests { static readonly object[] TestClusterVersions = [ + new object[] {new ClusterVersion(1, 34)}, new object[] {new ClusterVersion(1, 33)}, new object[] {new ClusterVersion(1, 32)}, new object[] {new ClusterVersion(1, 31)}, - new object[] {new ClusterVersion(1, 30)}, ]; KubernetesTestsGlobalContext? testContext; diff --git a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Octopus.Tentacle.Kubernetes.Tests.Integration.csproj b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Octopus.Tentacle.Kubernetes.Tests.Integration.csproj index 388ea4850..1e89f11e9 100644 --- a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Octopus.Tentacle.Kubernetes.Tests.Integration.csproj +++ b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Octopus.Tentacle.Kubernetes.Tests.Integration.csproj @@ -57,9 +57,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -69,6 +66,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-30.yaml b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-30.yaml deleted file mode 100644 index f925dc6df..000000000 --- a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-30.yaml +++ /dev/null @@ -1,8 +0,0 @@ -kind: Cluster -apiVersion: kind.x-k8s.io/v1alpha4 - -nodes: - - role: control-plane - image: kindest/node:v1.30.13@sha256:397209b3d947d154f6641f2d0ce8d473732bd91c87d9575ade99049aa33cd648 - - role: worker - image: kindest/node:v1.30.13@sha256:397209b3d947d154f6641f2d0ce8d473732bd91c87d9575ade99049aa33cd648 diff --git a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-31.yaml b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-31.yaml index 386030778..27a298f6e 100644 --- a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-31.yaml +++ b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-31.yaml @@ -3,6 +3,6 @@ apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - image: kindest/node:v1.31.9@sha256:b94a3a6c06198d17f59cca8c6f486236fa05e2fb359cbd75dabbfc348a10b211 + image: kindest/node:v1.31.12@sha256:0f5cc49c5e73c0c2bb6e2df56e7df189240d83cf94edfa30946482eb08ec57d2 - role: worker - image: kindest/node:v1.31.9@sha256:b94a3a6c06198d17f59cca8c6f486236fa05e2fb359cbd75dabbfc348a10b211 + image: kindest/node:v1.31.12@sha256:0f5cc49c5e73c0c2bb6e2df56e7df189240d83cf94edfa30946482eb08ec57d2 diff --git a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-32.yaml b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-32.yaml index 0f66c51ba..a00c3b64b 100644 --- a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-32.yaml +++ b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-32.yaml @@ -3,6 +3,6 @@ apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - image: kindest/node:v1.32.5@sha256:e3b2327e3a5ab8c76f5ece68936e4cafaa82edf58486b769727ab0b3b97a5b0d + image: kindest/node:v1.32.8@sha256:abd489f042d2b644e2d033f5c2d900bc707798d075e8186cb65e3f1367a9d5a1 - role: worker - image: kindest/node:v1.32.5@sha256:e3b2327e3a5ab8c76f5ece68936e4cafaa82edf58486b769727ab0b3b97a5b0d + image: kindest/node:v1.32.8@sha256:abd489f042d2b644e2d033f5c2d900bc707798d075e8186cb65e3f1367a9d5a1 diff --git a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-33.yaml b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-33.yaml index 5e8a8f0d3..372cbbc10 100644 --- a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-33.yaml +++ b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-33.yaml @@ -3,6 +3,6 @@ apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - image: kindest/node:v1.33.1@sha256:050072256b9a903bd914c0b2866828150cb229cea0efe5892e2b644d5dd3b34f + image: kindest/node:v1.33.4@sha256:25a6018e48dfcaee478f4a59af81157a437f15e6e140bf103f85a2e7cd0cbbf2 - role: worker - image: kindest/node:v1.33.1@sha256:050072256b9a903bd914c0b2866828150cb229cea0efe5892e2b644d5dd3b34f + image: kindest/node:v1.33.4@sha256:25a6018e48dfcaee478f4a59af81157a437f15e6e140bf103f85a2e7cd0cbbf2 diff --git a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-34.yaml b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-34.yaml new file mode 100644 index 000000000..9fef1b1a4 --- /dev/null +++ b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/KindConfiguration/kind-config-v1-34.yaml @@ -0,0 +1,8 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 + +nodes: + - role: control-plane + image: kindest/node:v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a + - role: worker + image: kindest/node:v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a diff --git a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/HelmDownloader.cs b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/HelmDownloader.cs index f4d648b59..f28857be9 100644 --- a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/HelmDownloader.cs +++ b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/HelmDownloader.cs @@ -9,7 +9,7 @@ namespace Octopus.Tentacle.Kubernetes.Tests.Integration.Setup.Tooling; public class HelmDownloader : ToolDownloader { - const string LatestVersion = "v3.18.6"; + const string LatestVersion = "v3.19.2"; public HelmDownloader( ILogger logger) : base("helm", logger) { diff --git a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/KindDownloader.cs b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/KindDownloader.cs index 8bc8a769c..fa903a8c4 100644 --- a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/KindDownloader.cs +++ b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/KindDownloader.cs @@ -6,7 +6,7 @@ namespace Octopus.Tentacle.Kubernetes.Tests.Integration.Setup.Tooling { public class KindDownloader : ToolDownloader { - const string LatestKindVersion = "v0.29.0"; + const string LatestKindVersion = "v0.30.0"; public KindDownloader(ILogger logger) : base("kind", logger) diff --git a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/KubeCtlDownloader.cs b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/KubeCtlDownloader.cs index a2e04adf2..86320b8e5 100644 --- a/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/KubeCtlDownloader.cs +++ b/source/Octopus.Tentacle.Kubernetes.Tests.Integration/Setup/Tooling/KubeCtlDownloader.cs @@ -4,7 +4,7 @@ namespace Octopus.Tentacle.Kubernetes.Tests.Integration.Setup.Tooling; public class KubeCtlDownloader : ToolDownloader { - public const string LatestKubeCtlVersion = "v1.33.5"; + public const string LatestKubeCtlVersion = "v1.34.2"; public KubeCtlDownloader(ILogger logger) : base("kubectl", logger) diff --git a/source/Octopus.Tentacle/Octopus.Tentacle.csproj b/source/Octopus.Tentacle/Octopus.Tentacle.csproj index c5a814c15..b57dc55ed 100644 --- a/source/Octopus.Tentacle/Octopus.Tentacle.csproj +++ b/source/Octopus.Tentacle/Octopus.Tentacle.csproj @@ -46,13 +46,10 @@ $(DefineConstants);HTTP_CLIENT_SUPPORTS_SSL_OPTIONS;REQUIRES_EXPLICIT_LOG_CONFIG;REQUIRES_CODE_PAGE_PROVIDER;USER_INTERACTIVE_DOES_NOT_WORK;DEFAULT_PROXY_IS_NOT_AVAILABLE;HAS_NULLABLE_REF_TYPES - - - - + - + @@ -123,4 +120,10 @@ + + + + + + From c390c9ae438d83af196371fde04e29db7b444400 Mon Sep 17 00:00:00 2001 From: Alastair Pitts Date: Mon, 24 Nov 2025 10:57:02 +1100 Subject: [PATCH 2/2] Fix compilation errors --- .../KubernetesEventMonitorFixture.cs | 104 ++++++++++-------- .../Kubernetes/KubernetesPodMonitorTests.cs | 4 +- .../Kubernetes/TrackedScriptPodFixture.cs | 22 ++-- .../Kubernetes/KubernetesEventMonitor.cs | 2 +- .../Kubernetes/KubernetesPodService.cs | 20 ++-- .../KubernetesRawScriptPodCreator.cs | 11 +- .../Kubernetes/KubernetesScriptPodCreator.cs | 84 +++++++++----- 7 files changed, 148 insertions(+), 99 deletions(-) diff --git a/source/Octopus.Tentacle.Tests/Kubernetes/KubernetesEventMonitorFixture.cs b/source/Octopus.Tentacle.Tests/Kubernetes/KubernetesEventMonitorFixture.cs index a2f366232..d4816ceb6 100644 --- a/source/Octopus.Tentacle.Tests/Kubernetes/KubernetesEventMonitorFixture.cs +++ b/source/Octopus.Tentacle.Tests/Kubernetes/KubernetesEventMonitorFixture.cs @@ -58,7 +58,7 @@ public async Task NoEntriesAreSentToMetricsWhenEventListIsEmpty() var agentMetrics = Substitute.For(); agentMetrics.GetLatestEventTimestamp(Arg.Any()).ReturnsForAnyArgs(testEpoch); var eventService = Substitute.For(); - var sut = new KubernetesEventMonitor(agentMetrics, eventService, "arbitraryNamespace", new IEventMapper[]{new NfsPodRestarted(), new TentacleKilledEventMapper(), new NfsStaleEventMapper()}, log); + var sut = new KubernetesEventMonitor(agentMetrics, eventService, "arbitraryNamespace", new IEventMapper[] { new NfsPodRestarted(), new TentacleKilledEventMapper(), new NfsStaleEventMapper() }, log); await sut.CacheNewEvents(tokenSource.Token); @@ -72,20 +72,23 @@ public async Task NfsPodKillingEventsAreTrackedInMetrics() var agentMetrics = new StubbedAgentMetrics(testEpoch); var eventService = Substitute.For(); eventService.FetchAllEventsAsync(Arg.Any(), Arg.Any()).ReturnsForAnyArgs( - new Corev1EventList(new List + new Corev1EventList { - new() + Items = new List { - Reason = "Killing", - Metadata = new V1ObjectMeta() + new() { - Name = "octopus-agent-nfs-123412-1234123", - }, - FirstTimestamp = testEpoch.DateTime.AddMinutes(1), - LastTimestamp = testEpoch.DateTime.AddMinutes(1) + Reason = "Killing", + Metadata = new V1ObjectMeta() + { + Name = "octopus-agent-nfs-123412-1234123", + }, + FirstTimestamp = testEpoch.DateTime.AddMinutes(1), + LastTimestamp = testEpoch.DateTime.AddMinutes(1) + } } - })); - var sut = new KubernetesEventMonitor(agentMetrics, eventService, "arbitraryNamespace", new IEventMapper[]{new NfsPodRestarted(), new TentacleKilledEventMapper(), new NfsStaleEventMapper()}, log); + }); + var sut = new KubernetesEventMonitor(agentMetrics, eventService, "arbitraryNamespace", new IEventMapper[] { new NfsPodRestarted(), new TentacleKilledEventMapper(), new NfsStaleEventMapper() }, log); //Act await sut.CacheNewEvents(tokenSource.Token); @@ -105,21 +108,24 @@ public async Task NfsWatchDogEventsAreTrackedInMetrics() var agentMetrics = new StubbedAgentMetrics(testEpoch); var eventService = Substitute.For(); eventService.FetchAllEventsAsync(Arg.Any(), Arg.Any()).ReturnsForAnyArgs( - new Corev1EventList(new List + new Corev1EventList { - new() + Items = new List { - Reason = "NfsWatchdogTimeout", - Metadata = new V1ObjectMeta() + new() { - Name = podName, - }, - FirstTimestamp = testEpoch.DateTime.AddSeconds(1), - LastTimestamp = testEpoch.DateTime.AddSeconds(1) + Reason = "NfsWatchdogTimeout", + Metadata = new V1ObjectMeta() + { + Name = podName, + }, + FirstTimestamp = testEpoch.DateTime.AddSeconds(1), + LastTimestamp = testEpoch.DateTime.AddSeconds(1) + } } - })); + }); - var sut = new KubernetesEventMonitor(agentMetrics, eventService, "arbitraryNamespace", new IEventMapper[]{new NfsPodRestarted(), new TentacleKilledEventMapper(), new NfsStaleEventMapper()}, log); + var sut = new KubernetesEventMonitor(agentMetrics, eventService, "arbitraryNamespace", new IEventMapper[] { new NfsPodRestarted(), new TentacleKilledEventMapper(), new NfsStaleEventMapper() }, log); //Act await sut.CacheNewEvents(tokenSource.Token); @@ -139,24 +145,27 @@ public async Task EventsOlderThanOrEqualToMetricsTimestampCursorAreNotAddedToMet var agentMetrics = new StubbedAgentMetrics(testEpoch); var eventService = Substitute.For(); eventService.FetchAllEventsAsync(Arg.Any(), Arg.Any()).ReturnsForAnyArgs( - new Corev1EventList(new List + new Corev1EventList { - new() + Items = new List { - Reason = "NfsWatchdogTimeout", - Metadata = new V1ObjectMeta() + new() { - Name = podName, - }, - FirstTimestamp = testEpoch.DateTime, - LastTimestamp = testEpoch.DateTime + Reason = "NfsWatchdogTimeout", + Metadata = new V1ObjectMeta() + { + Name = podName, + }, + FirstTimestamp = testEpoch.DateTime, + LastTimestamp = testEpoch.DateTime + } } - })); - - var sut = new KubernetesEventMonitor(agentMetrics, eventService, "arbitraryNamespace", new IEventMapper[]{new NfsPodRestarted(), new TentacleKilledEventMapper(), new NfsStaleEventMapper()}, log); + }); + + var sut = new KubernetesEventMonitor(agentMetrics, eventService, "arbitraryNamespace", new IEventMapper[] { new NfsPodRestarted(), new TentacleKilledEventMapper(), new NfsStaleEventMapper() }, log); //Act await sut.CacheNewEvents(tokenSource.Token); - + //Assert agentMetrics.Events.Should().BeEquivalentTo(new Dictionary>>()); } @@ -169,25 +178,28 @@ public async Task NewestTimeStampInEventIsUsedToDetermineAgeAndAsMetricsValue() var agentMetrics = new StubbedAgentMetrics(testEpoch); var eventService = Substitute.For(); eventService.FetchAllEventsAsync(Arg.Any(), Arg.Any()).ReturnsForAnyArgs( - new Corev1EventList(new List + new Corev1EventList { - new() + Items = new List { - Reason = "NfsWatchdogTimeout", - Metadata = new V1ObjectMeta() + new() { - Name = podName, - }, - FirstTimestamp = testEpoch.DateTime.AddMinutes(-2), - LastTimestamp = testEpoch.DateTime.AddMinutes(-1), - EventTime = testEpoch.DateTime.AddMinutes(1) + Reason = "NfsWatchdogTimeout", + Metadata = new V1ObjectMeta() + { + Name = podName, + }, + FirstTimestamp = testEpoch.DateTime.AddMinutes(-2), + LastTimestamp = testEpoch.DateTime.AddMinutes(-1), + EventTime = testEpoch.DateTime.AddMinutes(1) + } } - })); - - var sut = new KubernetesEventMonitor(agentMetrics, eventService, "arbitraryNamespace", new IEventMapper[]{new NfsPodRestarted(), new TentacleKilledEventMapper(), new NfsStaleEventMapper()}, log); + }); + + var sut = new KubernetesEventMonitor(agentMetrics, eventService, "arbitraryNamespace", new IEventMapper[] { new NfsPodRestarted(), new TentacleKilledEventMapper(), new NfsStaleEventMapper() }, log); //Act await sut.CacheNewEvents(tokenSource.Token); - + //Assert // The event.EventTime is newest event Time stamp, and is larger than the last metric date (TestEpoch) as such // the event should factored into the metrics, and should report this latest time value. diff --git a/source/Octopus.Tentacle.Tests/Kubernetes/KubernetesPodMonitorTests.cs b/source/Octopus.Tentacle.Tests/Kubernetes/KubernetesPodMonitorTests.cs index 4698cf51e..8d6493a0e 100644 --- a/source/Octopus.Tentacle.Tests/Kubernetes/KubernetesPodMonitorTests.cs +++ b/source/Octopus.Tentacle.Tests/Kubernetes/KubernetesPodMonitorTests.cs @@ -29,7 +29,7 @@ public void SetUp() { podService = Substitute.For(); log = new InMemoryLog(); - clock = new FixedClock(new DateTimeOffset(2023,5,17,13,4,5, TimeSpan.Zero)); + clock = new FixedClock(new DateTimeOffset(2023, 5, 17, 13, 4, 5, TimeSpan.Zero)); monitor = new KubernetesPodMonitor(podService, log, new TentacleScriptLogProvider(), clock); scriptTicket = new ScriptTicket(Guid.NewGuid().ToString()); @@ -111,7 +111,7 @@ public async Task ExistingPodIsUpdatedWhenCompleted() new() { Name = scriptTicket.ToKubernetesScriptPodName(), - State = new V1ContainerState(terminated: new V1ContainerStateTerminated(0, finishedAt: DateTime.UtcNow)) + State = new V1ContainerState { Terminated = new V1ContainerStateTerminated { ExitCode = 0, FinishedAt = DateTime.UtcNow } } } } }; diff --git a/source/Octopus.Tentacle.Tests/Kubernetes/TrackedScriptPodFixture.cs b/source/Octopus.Tentacle.Tests/Kubernetes/TrackedScriptPodFixture.cs index 45e114ecf..50463e8d9 100644 --- a/source/Octopus.Tentacle.Tests/Kubernetes/TrackedScriptPodFixture.cs +++ b/source/Octopus.Tentacle.Tests/Kubernetes/TrackedScriptPodFixture.cs @@ -20,7 +20,7 @@ public class TrackedScriptPodFixture public void SetUp() { scriptTicket = new ScriptTicket("ScriptTicketId"); - clock = new FixedClock(new DateTimeOffset(2023,5,17,13,4,5, TimeSpan.Zero)); + clock = new FixedClock(new DateTimeOffset(2023, 5, 17, 13, 4, 5, TimeSpan.Zero)); trackedPod = new TrackedScriptPod(scriptTicket, clock); } @@ -29,8 +29,8 @@ public void NullStatus_Pending() { //Just changing the tracked Pod to a different state GetPodInRunningState(); - - trackedPod.Update(new V1Pod(){ Metadata = new V1ObjectMeta(), Status = null}); + + trackedPod.Update(new V1Pod() { Metadata = new V1ObjectMeta(), Status = null }); trackedPod.State.Phase.Should().Be(TrackedScriptPodPhase.Pending); } @@ -39,11 +39,11 @@ public void NullContainerStatus_Pending() { //Just changing the tracked Pod to a different state GetPodInRunningState(); - - trackedPod.Update(new V1Pod(){ Metadata = new V1ObjectMeta(), Status = new V1PodStatus(){ Phase = "Foo", ContainerStatuses = null}}); + + trackedPod.Update(new V1Pod() { Metadata = new V1ObjectMeta(), Status = new V1PodStatus() { Phase = "Foo", ContainerStatuses = null } }); trackedPod.State.Phase.Should().Be(TrackedScriptPodPhase.Pending); } - + [Test] public void ScriptContainerNotFound_Pending() { @@ -58,7 +58,7 @@ public void ScriptContainerNotFound_Pending() public void CanTransitionFromPendingToRunning() { trackedPod.State.Phase.Should().Be(TrackedScriptPodPhase.Pending); - + GetPodInRunningState(); } @@ -89,7 +89,7 @@ public void UpdateWithCompletedPodButNoFinishedAt(string podPhase, int exitCode, trackedPod.State.ExitCode.Should().Be(exitCode); trackedPod.State.FinishedAt.Should().Be(clock.GetUtcTime()); } - + [TestCase(0, TrackedScriptPodPhase.Succeeded)] [TestCase(123, TrackedScriptPodPhase.Failed)] public void MarkAsCompleted(int exitCode, TrackedScriptPodPhase expectedPhase) @@ -130,7 +130,7 @@ public void PodUpdateAfterMarkAsCompleted_UpdatesToNewValue() trackedPod.State.ExitCode.Should().Be(podStatusExitCode); trackedPod.State.FinishedAt.Should().Be(podStatusFinishedAt); } - + void GetPodInRunningState() { trackedPod.Update(CreateV1Pod(RunningContainerState())); @@ -144,7 +144,6 @@ static V1ContainerState RunningContainerState() { return new V1ContainerState() { - Running = new V1ContainerStateRunning() }; } @@ -153,7 +152,6 @@ static V1ContainerState TerminatedContainerState(DateTime? finishedAt, int exitC { return new V1ContainerState() { - Terminated = new V1ContainerStateTerminated() { FinishedAt = finishedAt, @@ -183,7 +181,7 @@ static V1Pod CreateV1Pod(V1ContainerStatus containerStatus) } }; - return new V1Pod(status: podStatus, metadata: new V1ObjectMeta()); + return new V1Pod { Status = podStatus, Metadata = new V1ObjectMeta() }; } } } \ No newline at end of file diff --git a/source/Octopus.Tentacle/Kubernetes/KubernetesEventMonitor.cs b/source/Octopus.Tentacle/Kubernetes/KubernetesEventMonitor.cs index 3b164e58a..a7a12d5b4 100644 --- a/source/Octopus.Tentacle/Kubernetes/KubernetesEventMonitor.cs +++ b/source/Octopus.Tentacle/Kubernetes/KubernetesEventMonitor.cs @@ -38,7 +38,7 @@ public KubernetesEventMonitor(IKubernetesAgentMetrics agentMetrics, IKubernetesE public async Task CacheNewEvents(CancellationToken cancellationToken) { log.Info($"Parsing kubernetes event list for namespace {kubernetesNamespace}."); - var allEvents = await eventService.FetchAllEventsAsync(kubernetesNamespace, cancellationToken) ?? new Corev1EventList(new List()); + var allEvents = await eventService.FetchAllEventsAsync(kubernetesNamespace, cancellationToken) ?? new Corev1EventList(); var lastCachedEventTimeStamp = await agentMetrics.GetLatestEventTimestamp(cancellationToken); diff --git a/source/Octopus.Tentacle/Kubernetes/KubernetesPodService.cs b/source/Octopus.Tentacle/Kubernetes/KubernetesPodService.cs index 15488cc73..4d40ccd5a 100644 --- a/source/Octopus.Tentacle/Kubernetes/KubernetesPodService.cs +++ b/source/Octopus.Tentacle/Kubernetes/KubernetesPodService.cs @@ -34,14 +34,6 @@ public async Task ListAllPods(CancellationToken cancellationToken) public async Task WatchAllPods(string initialResourceVersion, Func onChange, Action onError, CancellationToken cancellationToken) { - using var response = Client.CoreV1.ListNamespacedPodWithHttpMessagesAsync( - KubernetesConfig.Namespace, - labelSelector: OctopusLabels.ScriptTicketId, - resourceVersion: initialResourceVersion, - watch: true, - timeoutSeconds: KubernetesConfig.PodMonitorTimeoutSeconds, - cancellationToken: cancellationToken); - var watchErrorCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); Action internalOnError = ex => @@ -55,7 +47,13 @@ public async Task WatchAllPods(string initialResourceVersion, Func(internalOnError, cancellationToken: watchErrorCancellationTokenSource.Token)) + await foreach (var (type, pod) in Client.CoreV1.WatchListNamespacedPodAsync( + KubernetesConfig.Namespace, + labelSelector: OctopusLabels.ScriptTicketId, + resourceVersion: initialResourceVersion, + timeoutSeconds: KubernetesConfig.PodMonitorTimeoutSeconds, + onError: internalOnError, + cancellationToken: watchErrorCancellationTokenSource.Token)) { await onChange(type, pod, cancellationToken); } @@ -84,10 +82,10 @@ public async Task Create(V1Pod pod, CancellationToken cancellationToken) public async Task DeleteIfExists(ScriptTicket scriptTicket, CancellationToken cancellationToken) => await DeleteIfExistsInternal(scriptTicket, null, cancellationToken); - public async Task DeleteIfExists(ScriptTicket scriptTicket, TimeSpan gracePeriod, CancellationToken cancellationToken) + public async Task DeleteIfExists(ScriptTicket scriptTicket, TimeSpan gracePeriod, CancellationToken cancellationToken) => await DeleteIfExistsInternal(scriptTicket, (long)Math.Floor(gracePeriod.TotalSeconds), cancellationToken); async Task DeleteIfExistsInternal(ScriptTicket scriptTicket, long? gracePeriodSeconds, CancellationToken cancellationToken) - => await TryExecuteAsync(async () => await Client.DeleteNamespacedPodAsync(scriptTicket.ToKubernetesScriptPodName(), KubernetesConfig.Namespace, new V1DeleteOptions(gracePeriodSeconds: gracePeriodSeconds), cancellationToken: cancellationToken)); + => await TryExecuteAsync(async () => await Client.DeleteNamespacedPodAsync(scriptTicket.ToKubernetesScriptPodName(), KubernetesConfig.Namespace, new V1DeleteOptions { GracePeriodSeconds = gracePeriodSeconds }, cancellationToken: cancellationToken)); } } \ No newline at end of file diff --git a/source/Octopus.Tentacle/Kubernetes/KubernetesRawScriptPodCreator.cs b/source/Octopus.Tentacle/Kubernetes/KubernetesRawScriptPodCreator.cs index 5d74e2bd9..aa541b6ce 100644 --- a/source/Octopus.Tentacle/Kubernetes/KubernetesRawScriptPodCreator.cs +++ b/source/Octopus.Tentacle/Kubernetes/KubernetesRawScriptPodCreator.cs @@ -50,8 +50,15 @@ protected override async Task> CreateInitContainers(StartKube container.Image = command.PodImageConfiguration?.Image ?? await containerResolver.GetContainerImageForCluster(); container.ImagePullPolicy = KubernetesConfig.ScriptPodPullPolicy; container.Command = new List { "sh", "-c", GetInitExecutionScript("/nfs-mount", homeDir, workspacePath) }; - container.VolumeMounts = Merge(container.VolumeMounts, new[] { new V1VolumeMount("/nfs-mount", "init-nfs-volume"), new V1VolumeMount(homeDir, "tentacle-home") }); - + container.VolumeMounts = Merge(container.VolumeMounts, new[] + { + new V1VolumeMount + { + MountPath = "/nfs-mount", Name = "init-nfs-volume" + }, + new V1VolumeMount { MountPath = homeDir, Name = "tentacle-home" } + }); + return new List { container }; } diff --git a/source/Octopus.Tentacle/Kubernetes/KubernetesScriptPodCreator.cs b/source/Octopus.Tentacle/Kubernetes/KubernetesScriptPodCreator.cs index 9bada0fa1..27bc1c03f 100644 --- a/source/Octopus.Tentacle/Kubernetes/KubernetesScriptPodCreator.cs +++ b/source/Octopus.Tentacle/Kubernetes/KubernetesScriptPodCreator.cs @@ -194,7 +194,7 @@ async Task CreatePod(StartKubernetesScriptCommandV1 command, IScriptWorkspace wo var imagePullSecretNames = new[] { imagePullSecretName } .Concat(KubernetesConfig.PodImagePullSecretNames) .WhereNotNull() - .Select(secretName => new V1LocalObjectReference(secretName)) + .Select(secretName => new V1LocalObjectReference { Name = secretName }) .ToList(); var scriptPodTemplate = await podTemplateService.GetScriptPodTemplate(cancellationToken); @@ -340,25 +340,37 @@ protected async Task CreateScriptContainer(StartKubernetesScriptCom container.VolumeMounts = Merge(container.VolumeMounts, new[] { - new V1VolumeMount(homeDir, "tentacle-home"), - new V1VolumeMount("/root/agent_upgrade/", "agent-upgrade"), - new V1VolumeMount("/tmp/agent_upgrade/", "agent-upgrade") + new V1VolumeMount + { + MountPath = homeDir, + Name = "tentacle-home" + }, + new V1VolumeMount + { + MountPath = "/root/agent_upgrade/", + Name = "agent-upgrade" + }, + new V1VolumeMount + { + MountPath = "/tmp/agent_upgrade/", + Name = "agent-upgrade" + } }); container.Env = Merge(container.Env, new List { - new(KubernetesConfig.NamespaceVariableName, KubernetesConfig.Namespace), - new(KubernetesConfig.HelmReleaseNameVariableName, KubernetesConfig.HelmReleaseName), - new(KubernetesConfig.HelmChartVersionVariableName, KubernetesConfig.HelmChartVersion), - new(KubernetesConfig.KubernetesMonitorEnabledVariableName, KubernetesConfig.KubernetesMonitorEnabled), - new(KubernetesConfig.ServerCommsAddressesVariableName, string.Join(",", KubernetesConfig.ServerCommsAddresses)), - new(KubernetesConfig.PersistentVolumeFreeBytesVariableName, spaceInformation?.freeSpaceBytes.ToString()), - new(KubernetesConfig.PersistentVolumeSizeBytesVariableName, spaceInformation?.totalSpaceBytes.ToString()), - new(EnvironmentVariables.TentacleHome, homeDir), - new(EnvironmentVariables.TentacleInstanceName, appInstanceSelector.Current.InstanceName), - new(EnvironmentVariables.TentacleVersion, Environment.GetEnvironmentVariable(EnvironmentVariables.TentacleVersion)), - new(EnvironmentVariables.TentacleCertificateSignatureAlgorithm, Environment.GetEnvironmentVariable(EnvironmentVariables.TentacleCertificateSignatureAlgorithm)), - new("OCTOPUS_RUNNING_IN_CONTAINER", "Y") + new() { Name = KubernetesConfig.NamespaceVariableName, Value = KubernetesConfig.Namespace }, + new() { Name = KubernetesConfig.HelmReleaseNameVariableName, Value = KubernetesConfig.HelmReleaseName }, + new() { Name = KubernetesConfig.HelmChartVersionVariableName, Value = KubernetesConfig.HelmChartVersion }, + new() { Name = KubernetesConfig.KubernetesMonitorEnabledVariableName, Value = KubernetesConfig.KubernetesMonitorEnabled }, + new() { Name = KubernetesConfig.ServerCommsAddressesVariableName, Value = string.Join(",", KubernetesConfig.ServerCommsAddresses) }, + new() { Name = KubernetesConfig.PersistentVolumeFreeBytesVariableName, Value = spaceInformation?.freeSpaceBytes.ToString() }, + new() { Name = KubernetesConfig.PersistentVolumeSizeBytesVariableName, Value = spaceInformation?.totalSpaceBytes.ToString() }, + new() { Name = EnvironmentVariables.TentacleHome, Value = homeDir }, + new() { Name = EnvironmentVariables.TentacleInstanceName, Value = appInstanceSelector.Current.InstanceName }, + new() { Name = EnvironmentVariables.TentacleVersion, Value = Environment.GetEnvironmentVariable(EnvironmentVariables.TentacleVersion) }, + new() { Name = EnvironmentVariables.TentacleCertificateSignatureAlgorithm, Value = Environment.GetEnvironmentVariable(EnvironmentVariables.TentacleCertificateSignatureAlgorithm) }, + new() { Name = "OCTOPUS_RUNNING_IN_CONTAINER", Value = "Y" } //We intentionally exclude setting "TentacleJournal" since it doesn't make sense to keep a Deployment Journal for Kubernetes deployments }); @@ -404,14 +416,36 @@ V1Affinity ParseScriptPodAffinity(InMemoryTentacleScriptLog tentacleScriptLog) KubernetesConfig.PodAffinityJsonVariableName, "pod affinity", //we default to running on linux/arm64 and linux/amd64 nodes - new V1Affinity(new V1NodeAffinity(requiredDuringSchedulingIgnoredDuringExecution: new V1NodeSelector(new List + new V1Affinity() { - new(matchExpressions: new List + NodeAffinity = new V1NodeAffinity() { - new("kubernetes.io/os", "In", new List { "linux" }), - new("kubernetes.io/arch", "In", new List { "arm64", "amd64" }) - }) - }))))!; + RequiredDuringSchedulingIgnoredDuringExecution = new V1NodeSelector + { + NodeSelectorTerms = new List + { + new() + { + MatchExpressions = new List + { + new() + { + Key = "kubernetes.io/os", + OperatorProperty = "In", + Values = new List { "linux" } + }, + new() + { + Key = "kubernetes.io/arch", + OperatorProperty = "In", + Values = new List { "arm64", "amd64" } + }, + } + } + } + } + } + })!; List? ParseScriptPodTolerations(InMemoryTentacleScriptLog tentacleScriptLog) => ParseScriptPodJson>( @@ -504,7 +538,7 @@ static string HashValue(string value) { using var sha1 = SHA1.Create(); var bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(value)); - return BitConverter.ToString(bytes).Replace("-",""); + return BitConverter.ToString(bytes).Replace("-", ""); } [return: NotNullIfNotNull("defaultValue")] @@ -564,9 +598,9 @@ static string HashValue(string value) container.Image = KubernetesConfig.NfsWatchdogImage; container.VolumeMounts = Merge(container.VolumeMounts, new List { - new(homeDir, "tentacle-home"), + new V1VolumeMount{MountPath = homeDir, Name = "tentacle-home"}, }); - container.Env = Merge(container.Env, new[] { new V1EnvVar(EnvironmentVariables.NfsWatchdogDirectory, homeDir) }); + container.Env = Merge(container.Env, new[] { new V1EnvVar{Name = EnvironmentVariables.NfsWatchdogDirectory, Value = homeDir} }); return container; }