-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement image metadata check for Kubernetes Agent tools to use the latest image tag revision for pull policy workaround #1010
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
de8c2e7
update filename
kevjt a8f2cb0
try get tools image metadata to use the revision hash in the image tag
kevjt 7ee86b1
Merge branch 'main' into kevjt/use-latest-rev-of-agent-tools-image
kevjt 2681f8b
add caching decorator for the metadata retriever
kevjt 1d10ed7
remove unused import
kevjt bebb40e
pass exception directly into log.Error
kevjt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
87 changes: 87 additions & 0 deletions
87
source/Octopus.Tentacle.Tests/Kubernetes/ClusterVersionTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
source/Octopus.Tentacle.Tests/Kubernetes/KubernetesPodContainerResolverTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}"); | ||
} | ||
} | ||
} |
43 changes: 0 additions & 43 deletions
43
source/Octopus.Tentacle.Tests/Kubernetes/KubernetesVersionParserTests.cs
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}"; | ||
} | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
source/Octopus.Tentacle/Kubernetes/KubernetesAgentToolsImageVersionMetadata.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if there's a way to avoid doing this. I ran into a compiler error similar to the one described here because HashCode is not available in the .NET Framework. Since we only build the Kubernetes Tentacle with .NET 8, this part of the code would never actually be used.