Skip to content

Commit

Permalink
Implement image metadata check for Kubernetes Agent tools to use the …
Browse files Browse the repository at this point in the history
…latest image tag revision for pull policy workaround (#1010)
  • Loading branch information
kevjt authored Oct 2, 2024
1 parent 0e2c713 commit 0cde870
Show file tree
Hide file tree
Showing 11 changed files with 477 additions and 121 deletions.
87 changes: 87 additions & 0 deletions source/Octopus.Tentacle.Tests/Kubernetes/ClusterVersionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using k8s.Models;
using NUnit.Framework;
using Octopus.Tentacle.Kubernetes;

namespace Octopus.Tentacle.Tests.Kubernetes
{
[TestFixture]
public class ClusterVersionTests
{
[TestCase("0", "1", 0, 1, false)]
[TestCase("1abc", "1", 1, 1, false)]
[TestCase("0", "1abc", 0, 1, false)]
[TestCase("1+", "0", 1, 0, false)]
[TestCase("0", "1+", 0, 1, false)]
[TestCase("abc", "1", 999, 999, true)]
public void FromVersionInfo_SanitizesAndReturnsNewClusterVersion(string major, string minor, int expectedMajor, int expectedMinor, bool shouldFail)
{
try
{
var versionInfo = new VersionInfo
{
Major = major,
Minor = minor
};
var result = ClusterVersion.FromVersionInfo(versionInfo);
result.Should().BeEquivalentTo(new ClusterVersion(expectedMajor, expectedMinor));
}
catch (Exception e)
{
if (shouldFail)
e.Should().BeOfType<FormatException>();
else
throw;
}
}

static IEnumerable<TestCaseData> FromVersionTestData()
{
yield return new TestCaseData(new Version("0.0.1"), 0, 0);
yield return new TestCaseData(new Version("1.31"), 1, 31);
yield return new TestCaseData(new Version("2.24.4"), 2, 24);
}

[TestCaseSource(nameof(FromVersionTestData))]
public void FromVersion_ReturnsNewClusterVersion(Version version, int expectedMajor, int expectedMinor)
{
var result = ClusterVersion.FromVersion(version);
result.Should().BeEquivalentTo(new ClusterVersion(expectedMajor, expectedMinor));
}

static IEnumerable<TestCaseData> CompareClusterVersionsTestData()
{
yield return new TestCaseData(new ClusterVersion(0, 0), null, 1);
yield return new TestCaseData(new ClusterVersion(0, 0), new ClusterVersion(0, 0), 0);
yield return new TestCaseData(new ClusterVersion(0, 5), new ClusterVersion(0, 6), -1);
yield return new TestCaseData(new ClusterVersion(1, 1), new ClusterVersion(2, 0), -1);
yield return new TestCaseData(new ClusterVersion(1, 30), new ClusterVersion(1, 29), 1);
yield return new TestCaseData(new ClusterVersion(3, 0), new ClusterVersion(2, 11), 1);
yield return new TestCaseData(new ClusterVersion(3, 14), new ClusterVersion(3, 14), 0);
}

[TestCaseSource(nameof(CompareClusterVersionsTestData))]
public void CompareClusterVersions(ClusterVersion thisClusterVersion, ClusterVersion otherClusterVersion, int expected)
{
var result = thisClusterVersion.CompareTo(otherClusterVersion);
result.Should().Be(expected);
}

static IEnumerable<TestCaseData> CheckEqualityClusterVersionsTestData()
{
yield return new TestCaseData(new ClusterVersion(0, 0), null, false);
yield return new TestCaseData(new ClusterVersion(0, 5), new ClusterVersion(0, 6), false);
yield return new TestCaseData(new ClusterVersion(1, 0), new ClusterVersion(2, 0), false);
yield return new TestCaseData(new ClusterVersion(3, 14), new ClusterVersion(3, 14), true);
}

[TestCaseSource(nameof(CheckEqualityClusterVersionsTestData))]
public void CheckEqualityClusterVersions(ClusterVersion thisClusterVersion, ClusterVersion otherClusterVersion, bool expected)
{
var result = thisClusterVersion.Equals(otherClusterVersion);
result.Should().Be(expected);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using NSubstitute;
using NSubstitute.ReturnsExtensions;
using NUnit.Framework;
using Octopus.Tentacle.Kubernetes;

namespace Octopus.Tentacle.Tests.Kubernetes
{
[TestFixture]
public class KubernetesPodContainerResolverTests
{
readonly KubernetesAgentToolsImageVersionMetadata testVersionMetadata = new(new KubernetesAgentToolVersions(new List<Version>
{
new("1.31.1"),
new("1.30.5"),
new("1.29.9"),
new("1.28.14")
}, new List<Version> { new("3.16.1") },
new List<Version> { new("7.4.5") }), new Version("1.30"), "Juaa5J", new Dictionary<Version, KubernetesAgentToolDeprecation>
{
{ new Version("1.26"), new KubernetesAgentToolDeprecation("1.26@sha256:a0892db") },
{ new Version("1.27"), new KubernetesAgentToolDeprecation("1.27@sha256:9d1ce87") }
});

readonly IToolsImageVersionMetadataProvider mockToolsImageVersionMetadataProvider = Substitute.For<IToolsImageVersionMetadataProvider>();

[SetUp]
public void Init()
{
mockToolsImageVersionMetadataProvider.TryGetVersionMetadata().Returns(testVersionMetadata);
}

[TestCase(30)]
[TestCase(29)]
[TestCase(28)]
public async Task GetContainerImageForCluster_VersionMetadataExists_ClusterVersionSupported_GetsImageWithRevision(int clusterMinorVersion)
{
// Arrange
var clusterService = Substitute.For<IKubernetesClusterService>();
clusterService.GetClusterVersion().Returns(new ClusterVersion(1, clusterMinorVersion));

var podContainerResolver = new KubernetesPodContainerResolver(clusterService, mockToolsImageVersionMetadataProvider);

// Act
var result = await podContainerResolver.GetContainerImageForCluster();

// Assert
result.Should().Be($"octopusdeploy/kubernetes-agent-tools-base:1.{clusterMinorVersion}-Juaa5J");
}

[TestCase(27, "1.27@sha256:9d1ce87")]
[TestCase(26, "1.26@sha256:a0892db")]
public async Task GetContainerImageForCluster_VersionMetadataExists_ClusterVersionDeprecated_GetsLatestDeprecatedTag(int clusterMinorVersion, string expectedImageTag)
{
// Arrange
var clusterService = Substitute.For<IKubernetesClusterService>();
clusterService.GetClusterVersion().Returns(new ClusterVersion(1, clusterMinorVersion));

var podContainerResolver = new KubernetesPodContainerResolver(clusterService, mockToolsImageVersionMetadataProvider);

// Act
var result = await podContainerResolver.GetContainerImageForCluster();

// Assert
result.Should().Be($"octopusdeploy/kubernetes-agent-tools-base:{expectedImageTag}");
}

[Test]
public async Task GetContainerImageForCluster_VersionMetadataExists_ClusterVersionGreaterThanLatest_FallbackToLatest()
{
// Arrange
var clusterService = Substitute.For<IKubernetesClusterService>();
clusterService.GetClusterVersion().Returns(new ClusterVersion(1, 31));

var podContainerResolver = new KubernetesPodContainerResolver(clusterService, mockToolsImageVersionMetadataProvider);

// Act
var result = await podContainerResolver.GetContainerImageForCluster();

// Assert
result.Should().Be("octopusdeploy/kubernetes-agent-tools-base:latest");
}

[Test]
public async Task GetContainerImageForCluster_VersionMetadataExists_ClusterVersionNotFound_FallbackToLatest()
{
// Arrange
var clusterService = Substitute.For<IKubernetesClusterService>();
clusterService.GetClusterVersion().Returns(new ClusterVersion(1, 40));

var podContainerResolver = new KubernetesPodContainerResolver(clusterService, mockToolsImageVersionMetadataProvider);

// Act
var result = await podContainerResolver.GetContainerImageForCluster();

// Assert
result.Should().Be("octopusdeploy/kubernetes-agent-tools-base:latest");
}

[TestCase(31, "latest")]
[TestCase(30, "1.30")]
[TestCase(29, "1.29")]
[TestCase(28, "1.28")]
[TestCase(27, "1.27")]
[TestCase(26, "1.26")]
[TestCase(25, "latest")]
public async Task GetContainerImageForCluster_VersionMetadataNotFound_FallBackToKnownTags(int clusterMinorVersion, string expectedImageTag)
{
// Arrange
var clusterService = Substitute.For<IKubernetesClusterService>();
clusterService.GetClusterVersion().Returns(new ClusterVersion(1, clusterMinorVersion));
mockToolsImageVersionMetadataProvider.TryGetVersionMetadata().ReturnsNull();

var podContainerResolver = new KubernetesPodContainerResolver(clusterService, mockToolsImageVersionMetadataProvider);

// Act
var result = await podContainerResolver.GetContainerImageForCluster();

// Assert
result.Should().Be($"octopusdeploy/kubernetes-agent-tools-base:{expectedImageTag}");
}
}
}

This file was deleted.

69 changes: 69 additions & 0 deletions source/Octopus.Tentacle/Kubernetes/ClusterVersion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Text.RegularExpressions;
using k8s.Models;

namespace Octopus.Tentacle.Kubernetes
{
public class ClusterVersion : IComparable<ClusterVersion>
{
public ClusterVersion(int major, int minor)
{
Major = major;
Minor = minor;
}

public int Major { get; }
public int Minor { get; }

public static ClusterVersion FromVersion(Version version)
{
return new ClusterVersion(version.Major, version.Minor);
}

public static ClusterVersion FromVersionInfo(VersionInfo versionInfo)
{
return new ClusterVersion(SanitizeAndParseVersionNumber(versionInfo.Major), SanitizeAndParseVersionNumber(versionInfo.Minor));
}

static int SanitizeAndParseVersionNumber(string version)
{
return int.Parse(Regex.Replace(version, "[^0-9]", ""));
}

public int CompareTo(ClusterVersion? other)
{
if (other == null) return 1;
if (Major > other.Major || (Major == other.Major && Minor > other.Minor)) return 1;
if (Major == other.Major && Minor == other.Minor) return 0;
return -1;
}

public override bool Equals(object? obj)
{
if (obj is not ClusterVersion clusterVersion)
return false;

return clusterVersion.Major == Major && clusterVersion.Minor == Minor;
}

public override int GetHashCode()
{
#if NET8_0_OR_GREATER
return HashCode.Combine(Major, Minor);
#else
unchecked // Overflow is fine in hash code calculations
{
int hash = 17;
hash = hash * 23 + Major.GetHashCode();
hash = hash * 23 + Minor.GetHashCode();
return hash;
}
#endif
}

public override string ToString()
{
return $"{Major}.{Minor}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Octopus.Tentacle.Kubernetes
{
public record KubernetesAgentToolsImageVersionMetadata(
[JsonProperty("tools")] KubernetesAgentToolVersions ToolVersions,
[JsonProperty("latest")] Version Latest,
[JsonProperty("revisionHash")] string RevisionHash,
[JsonProperty("deprecations")] Dictionary<Version, KubernetesAgentToolDeprecation> Deprecations);

public record KubernetesAgentToolVersions(
[JsonProperty("kubectl")] List<Version> Kubectl,
[JsonProperty("helm")] List<Version> Helm,
[JsonProperty("powershell")] List<Version> Powershell
);

public record KubernetesAgentToolDeprecation(
[JsonProperty("latestTag")] string LatestTag);
}
Loading

0 comments on commit 0cde870

Please sign in to comment.