Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions source/Calamari.Tests/ArgoCD/ContainerImageReplacerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,41 @@ public void UpdateImages_WithQuotedReference_PreservesQuotes()
result.UpdatedImageReferences.Should().ContainSingle(r => r == "nginx:1.25");
}

[Test]
public void UpdateImages_WithImageOnNonDefaultRegistry_UpdatesTagAndReportsImage()
{
// Images on a non-default registry (e.g. GAR/GCR/ECR) must be matched and updated. The
// first path segment (us-docker.pkg.dev) is recognised as a registry, and the rest is the
// image name; only the tag should change.
const string inputYaml = @"apiVersion: v1
kind: Pod
spec:
containers:
- name: helloworld
image: us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v1";
const string expectedYaml = @"apiVersion: v1
kind: Pod
spec:
containers:
- name: helloworld
image: us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v2";

var imageReplacer = new ContainerImageReplacer(inputYaml, DefaultContainerRegistry);

var updatedImage = new List<ContainerImageReferenceAndHelmReference>
{
new(ContainerImageReference.FromReferenceString(
"us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v2",
DefaultContainerRegistry))
};

var result = imageReplacer.UpdateImages(updatedImage);

result.UpdatedContents.Should().Be(expectedYaml);
result.UpdatedImageReferences.Should().ContainSingle()
.Which.Should().Be("us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v2");
}

[Test]
public void DoesNotUpdateComments()
{
Expand Down Expand Up @@ -722,8 +757,8 @@ public void UpdateImages_WithPodUpdatesUsingCustomRegistry_ReturnsUpdatedYaml()
result.UpdatedContents.Should().NotBeNull();
result.UpdatedContents.Should().Be(expectedYaml);
result.UpdatedImageReferences.Count.Should().Be(2);
result.UpdatedImageReferences.Should().ContainSingle(r => r == "nginx:1.25");
result.UpdatedImageReferences.Should().ContainSingle(r => r == "busybox:stable");
result.UpdatedImageReferences.Should().ContainSingle(r => r == "my-custom.io/nginx:1.25");
result.UpdatedImageReferences.Should().ContainSingle(r => r == "my-custom.io/busybox:stable");
}

[Test]
Expand Down Expand Up @@ -776,7 +811,7 @@ public void UpdateImages_WithPodUpdatesUsingCustomRegistry_OnlyUpdatesCustomMatc
result.UpdatedContents.Should().NotBeNull();
result.UpdatedContents.Should().Be(expectedYaml);
result.UpdatedImageReferences.Count.Should().Be(1);
result.UpdatedImageReferences.Should().ContainSingle(r => r == "busybox:stable");
result.UpdatedImageReferences.Should().ContainSingle(r => r == "my-custom.io/busybox:stable");
}


Expand Down
87 changes: 87 additions & 0 deletions source/Calamari.Tests/ArgoCD/DirectoryUpdaterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.IO;
using Calamari.ArgoCD;
using Calamari.ArgoCD.Conventions;
using Calamari.ArgoCD.Conventions.UpdateImageTag;
using Calamari.ArgoCD.Domain;
using Calamari.ArgoCD.Models;
using Calamari.Common.Plumbing.FileSystem;
using Calamari.Common.Plumbing.Logging;
using Calamari.Testing.Helpers;
using Calamari.Tests.Fixtures.Integration.FileSystem;
using FluentAssertions;
using NUnit.Framework;

namespace Calamari.Tests.ArgoCD
{
/// <summary>
/// Integration tests for DirectoryUpdater.Process() workflow.
/// Mirrors KustomizeUpdaterTests, focusing on non-default container registries.
/// </summary>
[TestFixture]
public class DirectoryUpdaterTests
{
ILog log;
ICalamariFileSystem fileSystem;
string tempDir;

[SetUp]
public void SetUp()
{
log = new InMemoryLog();
fileSystem = TestCalamariPhysicalFileSystem.GetPhysicalFileSystem();
tempDir = fileSystem.CreateTemporaryDirectory();
}

[TearDown]
public void TearDown()
{
fileSystem?.DeleteDirectory(tempDir);
}

[Test]
public void Process_WithImageOnNonDefaultRegistry_ProducesPatchSoChangesAreCommitted()
{
// Regression: an image on a non-default registry (e.g. GAR/GCR/ECR) must update the
// manifest AND produce a JSON patch, so HasChanges() is true and the commit/push isn't
// silently skipped.
const string deployment = @"apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld
spec:
template:
spec:
containers:
- name: helloworld
image: us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v1
";
fileSystem.OverwriteFile(Path.Combine(tempDir, "deployment.yaml"), deployment);

var garImages = new List<ContainerImageReferenceAndHelmReference>
{
new(ContainerImageReference.FromReferenceString(
"us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v2",
ArgoCDConstants.DefaultContainerRegistry)),
};

var sourceWithMetadata = new ApplicationSourceWithMetadata(
new ApplicationSource { Path = "." },
SourceType.Directory,
0);

var updater = new DirectoryUpdater(garImages, ArgoCDConstants.DefaultContainerRegistry, log, fileSystem);

var result = updater.Process(sourceWithMetadata, tempDir);

result.UpdatedImages.Should().NotBeEmpty();
result.HasChanges().Should().BeTrue("a JSON patch must be produced so the commit/push is not skipped");
result.PatchedFiles.Should().NotBeEmpty();

var updatedContent = fileSystem.ReadFile(Path.Combine(tempDir, "deployment.yaml"));
updatedContent.Should().Contain("us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v2");
updatedContent.Should().NotContain("helloworld:v1");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,30 @@ public void StructuredValue_AlreadyAtTarget_TracksWithFriendlyName()
result.AlreadyUpToDateImages.Should().BeEquivalentTo(["nginx:1.27.1"]);
}

[Test]
public void StructuredValue_ImageOnNonDefaultRegistry_UpdatesFullRefAndTracksWithRegistry()
{
// Helm/Ref report updates using the registry-qualified FriendlyName, so an image on a
// non-default registry (e.g. GAR/GCR/ECR) round-trips with its registry intact.
const string yaml = @"
image:
name: us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v1
";
var replacer = new HelmValuesImageReplaceStepVariables(yaml, DefaultRegistry, log);
var images = new List<ContainerImageReferenceAndHelmReference>
{
new(ContainerImageReference.FromReferenceString(
"us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v2",
DefaultRegistry), "image.name")
};

var result = replacer.UpdateImages(images);

using var scope = new AssertionScope();
result.UpdatedImageReferences.Should().BeEquivalentTo(["us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v2"]);
result.UpdatedContents.Should().Contain("name: us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v2");
}

[Test]
public void TwoImagesWithSameTag_OnlyUpdatesConfiguredPath()
{
Expand Down
8 changes: 4 additions & 4 deletions source/Calamari.Tests/ArgoCD/JsonPatchImageReplacerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public void UpdateImages_WithAddOperation_UpdatesImageReference()

result.UpdatedContents.Should().NotBeNull();
result.UpdatedImageReferences.Count.Should().Be(1);
result.UpdatedImageReferences.Should().ContainSingle(r => r == "busybox:stable");
result.UpdatedImageReferences.Should().ContainSingle(r => r == "my-registry.com/busybox:stable");
result.UpdatedContents.Should().Contain("my-registry.com/busybox:stable");
}

Expand Down Expand Up @@ -100,7 +100,7 @@ public void UpdateImages_WithObjectValue_UpdatesNestedImageReferences()
result.UpdatedContents.Should().NotBeNull();
result.UpdatedImageReferences.Count.Should().Be(2);
result.UpdatedImageReferences.Should().Contain("nginx:1.25");
result.UpdatedImageReferences.Should().Contain("busybox:stable");
result.UpdatedImageReferences.Should().Contain("my-registry.com/busybox:stable");
result.UpdatedContents.Should().Contain("nginx:1.25");
result.UpdatedContents.Should().Contain("my-registry.com/busybox:stable");
}
Expand Down Expand Up @@ -166,7 +166,7 @@ public void UpdateImages_WithComplexNestedStructure_UpdatesAllMatchingImages()
result.UpdatedContents.Should().NotBeNull();
result.UpdatedImageReferences.Count.Should().Be(2);
result.UpdatedImageReferences.Should().Contain("nginx:1.25");
result.UpdatedImageReferences.Should().Contain("busybox:stable");
result.UpdatedImageReferences.Should().Contain("my-registry.com/busybox:stable");
}

[Test]
Expand Down Expand Up @@ -197,7 +197,7 @@ public void UpdateImages_WithMultiplePatchOperations_UpdatesAllMatchingImages()
result.UpdatedContents.Should().NotBeNull();
result.UpdatedImageReferences.Count.Should().Be(2);
result.UpdatedImageReferences.Should().Contain("nginx:1.25");
result.UpdatedImageReferences.Should().Contain("busybox:stable");
result.UpdatedImageReferences.Should().Contain("my-registry.com/busybox:stable");
}

[Test]
Expand Down
38 changes: 38 additions & 0 deletions source/Calamari.Tests/ArgoCD/KustomizeUpdaterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,44 @@ public void Process_WithMixedPatchTypes_UpdatesAllFiles()
updatedKustomizationContent.Should().Contain("busybox:stable");
Comment thread
oleksandr-codefresh marked this conversation as resolved.
}

[Test]
public void Process_WithImageOnNonDefaultRegistry_ProducesPatchSoChangesAreCommitted()
{
// Regression test: an image whose registry differs from the default (e.g. GAR/GCR/ECR)
// must still produce a JSON patch. Previously the recorded image reference had its
// registry stripped, so CreateJsonPatch re-parsed it, defaulted the registry to
// docker.io, failed to match, and returned no patch — meaning HasChanges() was false
// and the working-copy edit was silently discarded (never committed).
const string kustomizationContent = @"apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld
newTag: ""latest""
";

var garImages = new List<ContainerImageReferenceAndHelmReference>
{
new(ContainerImageReference.FromReferenceString(
"us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v2",
ArgoCDConstants.DefaultContainerRegistry)),
};

var sourceWithMetadata = new ApplicationSourceWithMetadata(
new ApplicationSource { Path = "." },
SourceType.Kustomize,
0);

CreateKustomizationFile(kustomizationContent);

var updater = new KustomizeUpdater(CreateMockDeploymentConfig(garImages), ArgoCDConstants.DefaultContainerRegistry, log, fileSystem);

var result = updater.Process(sourceWithMetadata, tempDir);

result.UpdatedImages.Should().Contain("us-docker.pkg.dev/shared-gke-dev-gqtrxy/argo-test/helloworld:v2");
result.HasChanges().Should().BeTrue("a JSON patch must be produced so the commit/push is not skipped");
result.PatchedFiles.Should().NotBeEmpty();
}

[Test]
public void Process_WithNoKustomizationFile_ReturnsEmptyResult()
{
Expand Down
4 changes: 2 additions & 2 deletions source/Calamari/ArgoCD/ContainerImageReplacer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,11 @@ public ImageReplacementResult UpdateImages(IReadOnlyCollection<ContainerImageRef
},
RegexOptions.Multiline);

replacementsMade.Add($"{matchedUpdate.Reference.ImageName}:{matchedUpdate.Reference.Tag}");
replacementsMade.Add(matchedUpdate.Reference.FriendlyName());
}
else
{
alreadyUpToDate.Add($"{matchedUpdate.Reference.ImageName}:{matchedUpdate.Reference.Tag}");
alreadyUpToDate.Add(matchedUpdate.Reference.FriendlyName());
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion source/Calamari/ArgoCD/JsonPatchImageReplacer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ void ProcessImageValue(JsonValue imageValue, IReadOnlyCollection<ContainerImageR
var newImageRef = matchedUpdate.Reference.WithTag(matchedUpdate.Reference.Tag);
UpdateJsonImageValue(imageValue, newImageRef);

changes.Add($"{matchedUpdate.Reference.ImageName}:{matchedUpdate.Reference.Tag}");
changes.Add(matchedUpdate.Reference.FriendlyName());
log.Verbose($"Updated container image in JSON patch: {newImageRef}");
Comment thread
oleksandr-codefresh marked this conversation as resolved.
Outdated
}

Expand Down
6 changes: 3 additions & 3 deletions source/Calamari/ArgoCD/KustomizeImageReplacer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,17 @@ public ImageReplacementResult UpdateImages(IReadOnlyCollection<ContainerImageRef
{
matchedUpdate.ExistingTagNode.Style = ScalarStyle.DoubleQuoted;
}
replacementsMade.Add($"{matchedUpdate.Reference.ImageName}:{matchedUpdate.Reference.Tag}");
replacementsMade.Add(matchedUpdate.Reference.FriendlyName());
}
else
{
alreadyUpToDateImages.Add($"{matchedUpdate.Reference.ImageName}:{matchedUpdate.Reference.Tag}");
alreadyUpToDateImages.Add(matchedUpdate.Reference.FriendlyName());
}
}
else
{
imageNode.Children.Add(new YamlScalarNode(NewTagNodeKey), new YamlScalarNode(matchedUpdate.Reference.Tag) { Style = ScalarStyle.DoubleQuoted });
replacementsMade.Add($"{matchedUpdate.Reference.ImageName}:{matchedUpdate.Reference.Tag}");
replacementsMade.Add(matchedUpdate.Reference.FriendlyName());
}

//remove any digest node (as we want the newTag node to dictate the container version)
Expand Down