From 9bfea2dae040cea15e64a481c69dc61a70b0dcbd Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Mon, 16 Feb 2026 13:01:23 +1100 Subject: [PATCH 01/30] extracting helm-yaml-path from package variable, but is unused --- .../Plumbing/Variables/PackageVariables.cs | 2 ++ .../ArgoCD/Conventions/DeploymentConfigFactory.cs | 5 +++-- .../ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs | 8 +++++--- .../Conventions/UpdateArgoCDAppImagesInstallConvention.cs | 6 +++--- source/Calamari/ArgoCD/VariablesExtensionMethods.cs | 6 ++++-- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs b/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs index 48a66625a8..2c1f0a21b1 100644 --- a/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs +++ b/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs @@ -27,6 +27,8 @@ public static class PackageVariables public static string IndexedPackagePurpose(string packageReferenceName) => $"Octopus.Action.Package[{packageReferenceName}].Purpose"; + public static string HelmValueYamlPath(string packageReferenceName) => $"Octopus.Action.Package[{packageReferenceName}].HelmValueYamlPath"; + public static string IndexedOriginalPath(string packageReferenceName) { return $"Octopus.Action.Package[{packageReferenceName}].OriginalPath"; diff --git a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs index 6d7e41603e..5d09e7bbbc 100644 --- a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs +++ b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs @@ -34,8 +34,9 @@ public ArgoCommitToGitConfig CreateCommitToGitConfig(RunningDeployment deploymen public UpdateArgoCDAppDeploymentConfig CreateUpdateImageConfig(RunningDeployment deployment) { var commitParameters = CommitParameters(deployment); - var packageReferences = deployment.Variables.GetContainerPackageNames().Select(p => ContainerImageReference.FromReferenceString(p)).ToList(); - return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageReferences); + var packageHelmReference = deployment.Variables.GetContainerPackages().Select(p => new PackageAndHelmReference(ContainerImageReference.FromReferenceString(p.PackageName), + p.HelmReference)).ToList(); + return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageHelmReference); } bool RequiresPullRequest(RunningDeployment deployment) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index ecc2cbaee4..f3367ba8bd 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -6,12 +6,14 @@ namespace Calamari.ArgoCD.Conventions public class UpdateArgoCDAppDeploymentConfig { public GitCommitParameters CommitParameters { get; } - public List ImageReferences { get; } + public List packageWithHelmReference { get; } - public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List imageReferences) + public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List packageWithHelmReference) { CommitParameters = commitParameters; - ImageReferences = imageReferences; + this.packageWithHelmReference = packageWithHelmReference; } } + + public record PackageAndHelmReference(ContainerImageReference ImageReference, string? HelmReference); } diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 88a454221e..c3572e57c7 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -259,7 +259,7 @@ SourceUpdateResult ProcessKustomize( { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.ImageReferences); + var (updatedFiles, updatedImages) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.packageWithHelmReference.Select(ph => ph.ImageReference).ToList()); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -335,7 +335,7 @@ SourceUpdateResult ProcessDirectory( { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.ImageReferences); + var (updatedFiles, updatedImages) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.packageWithHelmReference.Select(ph => ph.ImageReference).ToList()); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -422,7 +422,7 @@ SourceUpdateResult ProcessHelmUpdateTargets( { var results = targets.Select(t => UpdateHelmImageValues(repository.WorkingDirectory, t, - deploymentConfig.ImageReferences + deploymentConfig.packageWithHelmReference.Select(ph => ph.ImageReference).ToList() )) .ToList(); diff --git a/source/Calamari/ArgoCD/VariablesExtensionMethods.cs b/source/Calamari/ArgoCD/VariablesExtensionMethods.cs index 95ddb06176..07eab60416 100644 --- a/source/Calamari/ArgoCD/VariablesExtensionMethods.cs +++ b/source/Calamari/ArgoCD/VariablesExtensionMethods.cs @@ -8,16 +8,18 @@ namespace Calamari.ArgoCD { + public record PackageAndHelmReference(string PackageName, string? HelmReference); public static class VariablesExtensionMethods { - public static IList GetContainerPackageNames(this IVariables variables) + public static IList GetContainerPackages(this IVariables variables) { var packageIndexes = variables.GetIndexes(PackageVariables.PackageCollection); var packageReferences = (from packageIndex in packageIndexes let image = variables.Get(PackageVariables.IndexedImage(packageIndex), string.Empty) let purpose = variables.Get(PackageVariables.IndexedPackagePurpose(packageIndex), string.Empty) + let helmValueYamlPath = variables.Get(PackageVariables.HelmValueYamlPath(packageIndex), null) where purpose.Equals("DockerImageReference", StringComparison.Ordinal) - select image) + select new PackageAndHelmReference(image, helmValueYamlPath)) .ToList(); return packageReferences; From d044ce6eafac8d2f7dbcd2bf89224ed486b33957 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 17 Feb 2026 18:26:15 +1100 Subject: [PATCH 02/30] yay - got first test passing --- ...goCDAppImagesInstallConventionHelmTests.cs | 88 +++++++ .../Conventions/DeploymentConfigFactory.cs | 3 +- .../UpdateArgoCDAppDeploymentConfig.cs | 11 +- .../UpdateArgoCDAppImagesInstallConvention.cs | 231 ++++++++++++++++-- .../Calamari/ArgoCD/Helm/HelmValuesEditor.cs | 19 +- .../ArgoCD/Helm/HelmValuesFileExtractor.cs | 43 ++++ .../ArgoCD/Models/ContainerImageReference.cs | 16 +- .../Calamari/ArgoCD/Models/HelmValuesFile.cs | 20 ++ .../Calamari/Kubernetes/SpecialVariables.cs | 2 + 9 files changed, 393 insertions(+), 40 deletions(-) create mode 100644 source/Calamari/ArgoCD/Helm/HelmValuesFileExtractor.cs create mode 100644 source/Calamari/ArgoCD/Models/HelmValuesFile.cs diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs index 60775f647a..ada33176ad 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Calamari.ArgoCD; using Calamari.ArgoCD.Conventions; using Calamari.ArgoCD.Domain; @@ -1319,6 +1320,93 @@ public void RefSourceWithHelmImageMatchesAndPath_IgnoresFilesUnderPath() AssertOutputVariables(matchingApplicationTotalSourceCounts: "2"); } + [Test] + public void CanUpdateHelmSourceUsingStepBasedVariables() + { + // Arrange + var updater = CreateConvention(); + var variables = new CalamariVariables + { + [PackageVariables.IndexedImage("nginx")] = "index.docker.io/nginx:1.27.1", + [PackageVariables.IndexedPackagePurpose("nginx")] = "DockerImageReference", + [PackageVariables.HelmValueYamlPath("nginx")] = "image.nginx", //NOTE: no .Values to start, and no leading . + [ProjectVariables.Slug] = ProjectSlug, + [DeploymentEnvironment.Slug] = EnvironmentSlug, + [SpecialVariables.Git.UseHelmValueYamlPathFromStep] = "true", + }; + var runningDeployment = new RunningDeployment(null, variables); + runningDeployment.CurrentDirectoryProvider = DeploymentWorkingDirectory.StagingDirectory; + runningDeployment.StagingDirectory = tempDirectory; + + + var existingYamlFile = "otherRepoPath/values.yaml"; + var filesInRepo = new (string, string)[] + { + ( + existingYamlFile, + @" +image: + nginx: index.docker.io/nginx:1.0 +containerPort: 8080 +service: + type: LoadBalancer +" + ) + }; + originRepo.AddFilesToBranch(argoCDBranchName, filesInRepo); + + var argoCDAppWithHelmSource = new ArgoCDApplicationBuilder() + .WithName("App1") + .WithAnnotations(new Dictionary() + { + [ArgoCDConstants.Annotations.OctopusProjectAnnotationKey(new ApplicationSourceName("ref-source"))] = ProjectSlug, + [ArgoCDConstants.Annotations.OctopusEnvironmentAnnotationKey(new ApplicationSourceName("ref-source"))] = EnvironmentSlug, + }) + .WithSource(new ApplicationSource + { + OriginalRepoUrl = "https://github.com/org/repo", + Path = "", + TargetRevision = ArgoCDBranchFriendlyName, + Helm = new HelmConfig + { + ValueFiles = new List() + { + "$values/otherRepoPath/values.yaml" + } + }, + Name = "helm-source", + }, + SourceTypeConstants.Helm) + .WithSource(new ApplicationSource + { + Name = "ref-source", + Ref = "values", + TargetRevision = ArgoCDBranchFriendlyName, + OriginalRepoUrl = OriginPath, + }, + SourceTypeConstants.Directory) + .Build(); + + argoCdApplicationManifestParser.ParseManifest(Arg.Any()) + .Returns(argoCDAppWithHelmSource); + // Act + updater.Install(runningDeployment); + + //Assert + const string updatedYamlContent = + @" +image: + nginx: index.docker.io/nginx:1.27.1 +containerPort: 8080 +service: + type: LoadBalancer +"; + + var clonedRepoPath = RepositoryHelpers.CloneOrigin(tempDirectory, OriginPath, argoCDBranchName); + AssertFileContents(clonedRepoPath, existingYamlFile, updatedYamlContent); + } + + void AssertFileContents(string clonedRepoPath, string relativeFilePath, string expectedContent) { var absolutePath = Path.Combine(clonedRepoPath, relativeFilePath); diff --git a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs index 5d09e7bbbc..20e6d497b4 100644 --- a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs +++ b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs @@ -36,7 +36,8 @@ public UpdateArgoCDAppDeploymentConfig CreateUpdateImageConfig(RunningDeployment var commitParameters = CommitParameters(deployment); var packageHelmReference = deployment.Variables.GetContainerPackages().Select(p => new PackageAndHelmReference(ContainerImageReference.FromReferenceString(p.PackageName), p.HelmReference)).ToList(); - return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageHelmReference); + var useHelmValueYamlPathFromStep = deployment.Variables.GetFlag(SpecialVariables.Git.UseHelmValueYamlPathFromStep, false); + return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageHelmReference, useHelmValueYamlPathFromStep); } bool RequiresPullRequest(RunningDeployment deployment) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index f3367ba8bd..4703f05f78 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -6,14 +6,17 @@ namespace Calamari.ArgoCD.Conventions public class UpdateArgoCDAppDeploymentConfig { public GitCommitParameters CommitParameters { get; } - public List packageWithHelmReference { get; } + public IReadOnlyCollection PackageWithHelmReference { get; } - public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List packageWithHelmReference) + public bool UseHelmValueYamlPathFromStep { get; } + + public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List packageWithHelmReference, bool useHelmValueYamlPathFromStep) { CommitParameters = commitParameters; - this.packageWithHelmReference = packageWithHelmReference; + PackageWithHelmReference = packageWithHelmReference; + UseHelmValueYamlPathFromStep = useHelmValueYamlPathFromStep; } } - public record PackageAndHelmReference(ContainerImageReference ImageReference, string? HelmReference); + public record PackageAndHelmReference(ContainerImageReference ContainerReference, string? HelmReference); } diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index c3572e57c7..259864624a 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -17,6 +17,7 @@ using Calamari.Deployment.Conventions; using Calamari.Integration.Time; using Octopus.CoreUtilities.Extensions; +using SharpCompress; namespace Calamari.ArgoCD.Conventions { @@ -259,7 +260,7 @@ SourceUpdateResult ProcessKustomize( { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.packageWithHelmReference.Select(ph => ph.ImageReference).ToList()); + var (updatedFiles, updatedImages) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList()); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -298,20 +299,31 @@ SourceUpdateResult ProcessRef( { log.WarnFormat("The source '{0}' contains a Ref, only referenced files will be updated. Please create another source with the same URL if you wish to update files under the path.", sourceWithMetadata.SourceIdentity); } - - var helmTargetsForRefSource = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) - .GetHelmTargetsForRefSource(sourceWithMetadata); - - LogHelmSourceConfigurationProblems(helmTargetsForRefSource.Problems); - - using var repository = CreateRepository(gitCredentials, applicationSource, repositoryFactory); - return ProcessHelmUpdateTargets( - applicationFromYaml, - repository, - deploymentConfig, - sourceWithMetadata, - helmTargetsForRefSource.Targets, - gateway); + + if (!deploymentConfig.UseHelmValueYamlPathFromStep) + { + var helmTargetsForRefSource = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) + .GetHelmTargetsForRefSource(sourceWithMetadata); + + LogHelmSourceConfigurationProblems(helmTargetsForRefSource.Problems); + + using var repository = CreateRepository(gitCredentials, applicationSource, repositoryFactory); + return ProcessHelmUpdateTargets( + applicationFromYaml, + repository, + deploymentConfig, + sourceWithMetadata, + helmTargetsForRefSource.Targets, + gateway); + } + + return ProcessRefSourceUsingStepVariables(applicationFromYaml, + sourceWithMetadata, + gitCredentials, + repositoryFactory, + deploymentConfig, + defaultRegistry, + gateway); } /// Images that were updated @@ -335,7 +347,7 @@ SourceUpdateResult ProcessDirectory( { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.packageWithHelmReference.Select(ph => ph.ImageReference).ToList()); + var (updatedFiles, updatedImages) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList()); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -378,6 +390,30 @@ SourceUpdateResult ProcessHelm( return new SourceUpdateResult(new HashSet(), string.Empty); } + if (!deploymentConfig.UseHelmValueYamlPathFromStep) + { + return ProcessHelmSourceUsingAnnotations(applicationFromYaml, sourceWithMetadata, gitCredentials, repositoryFactory, + deploymentConfig, + defaultRegistry, + gateway, + applicationSource); + } + return ProcessHelmSourceUsingStepVariables(applicationFromYaml, sourceWithMetadata, gitCredentials, repositoryFactory, + deploymentConfig, + defaultRegistry, + gateway, + applicationSource); + } + + SourceUpdateResult ProcessHelmSourceUsingAnnotations(Application applicationFromYaml, + ApplicationSourceWithMetadata sourceWithMetadata, + Dictionary gitCredentials, + RepositoryFactory repositoryFactory, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + string defaultRegistry, + ArgoCDGatewayDto gateway, + ApplicationSource applicationSource) + { var explicitHelmSources = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) .GetExplicitValuesFilesToUpdate(sourceWithMetadata); @@ -391,9 +427,9 @@ SourceUpdateResult ProcessHelm( if (implicitValuesFile != null && explicitHelmSources.Targets.None(t => t.FileName == implicitValuesFile)) { var (target, problem) = AddImplicitValuesFile(applicationFromYaml, - sourceWithMetadata, - implicitValuesFile, - defaultRegistry); + sourceWithMetadata, + implicitValuesFile, + defaultRegistry); if (target != null) valuesFilesToUpdate.Add(target); @@ -404,11 +440,11 @@ SourceUpdateResult ProcessHelm( LogHelmSourceConfigurationProblems(valueFileProblems); return ProcessHelmUpdateTargets(applicationFromYaml, - repository, - deploymentConfig, - sourceWithMetadata, - valuesFilesToUpdate, - gateway); + repository, + deploymentConfig, + sourceWithMetadata, + valuesFilesToUpdate, + gateway); } /// Images that were updated @@ -422,7 +458,7 @@ SourceUpdateResult ProcessHelmUpdateTargets( { var results = targets.Select(t => UpdateHelmImageValues(repository.WorkingDirectory, t, - deploymentConfig.packageWithHelmReference.Select(ph => ph.ImageReference).ToList() + deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList() )) .ToList(); @@ -448,6 +484,110 @@ SourceUpdateResult ProcessHelmUpdateTargets( return new SourceUpdateResult(new HashSet(), string.Empty); } + // If we're in here, it means we're definitely got a helm source, not a ref + SourceUpdateResult ProcessHelmSourceUsingStepVariables(Application applicationFromYaml, + ApplicationSourceWithMetadata sourceWithMetadata, + Dictionary gitCredentials, + RepositoryFactory repositoryFactory, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + string defaultRegistry, + ArgoCDGatewayDto gateway, + ApplicationSource applicationSource) + { + var extractor = new HelmValuesFileExtractor(applicationFromYaml, defaultRegistry); + var valuesFilesInHelmSource = extractor.GetInlineValuesFilesReferencedByHelmSource(sourceWithMetadata); + + //Add the implicit value file if needed + using var repository = CreateRepository(gitCredentials, applicationSource, repositoryFactory); + var implicitValuesFile = HelmDiscovery.TryFindValuesFile(fileSystem, sourceWithMetadata.Source.Path!); + var valuesFilesToUpdate = valuesFilesInHelmSource.ToList(); + if (implicitValuesFile != null && !valuesFilesInHelmSource.Contains(implicitValuesFile)) + { + valuesFilesToUpdate.Add(implicitValuesFile); + } + + return UpdateImageTagsInValuesFiles(valuesFilesToUpdate, deploymentConfig.PackageWithHelmReference, defaultRegistry, deploymentConfig, repository, sourceWithMetadata); + } + + SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFromYaml, + ApplicationSourceWithMetadata sourceWithMetadata, + Dictionary gitCredentials, + RepositoryFactory repositoryFactory, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + string defaultRegistry, + ArgoCDGatewayDto gateway) + { + var extractor = new HelmValuesFileExtractor(applicationFromYaml, defaultRegistry); + var valuesFilesInHelmSource = extractor.GetValueFilesReferencedInRefSource(sourceWithMetadata); + + using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); + return UpdateImageTagsInValuesFiles(valuesFilesInHelmSource, deploymentConfig.PackageWithHelmReference, defaultRegistry, deploymentConfig, repository, sourceWithMetadata); + } + + SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valuesFilesToUpdate, IReadOnlyCollection stepReferencedContainers, string defaultRegistry, UpdateArgoCDAppDeploymentConfig deploymentConfig, RepositoryWrapper repository, ApplicationSourceWithMetadata sourceWithMetadata) + { + var filesUpdated = new HashSet(); + var imagesUpdated = new HashSet(); + foreach (var valuesFile in valuesFilesToUpdate) + { + var wasUpdated = false; + var repoRelativePath = Path.Combine(repository.WorkingDirectory, valuesFile); + log.Info($"Processing file at {valuesFile}."); + var fileContent = fileSystem.ReadFile(repoRelativePath); + var originalYamlParser = new HelmYamlParser(fileContent); // Parse and track the original yaml so that content can be read from it. + var flattenedYamlPathDictionary = HelmValuesEditor.CreateDictionary(originalYamlParser); + foreach (var container in stepReferencedContainers.Where(c => c.HelmReference is not null)) + { + if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var helmValue)) + { + var valueContentType = DetermineTypeOfContent(helmValue); + var cir = ContainerImageReference.FromReferenceString(helmValue, defaultRegistry); + switch (valueContentType) + { + case ValueFileContent.PlainText: + HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, container.ContainerReference.Tag); + filesUpdated.Add(valuesFile); + imagesUpdated.Add(container.ContainerReference.ToString()); + break; + case ValueFileContent.ImageNameAndTag: + case ValueFileContent.RegistryAndImageName: + case ValueFileContent.RegistryImageNameAndTag: + var comparison = container.ContainerReference.CompareWith(cir); + if (comparison.MatchesImage()) + { + if (!comparison.TagMatch) + { + var newValue = cir.WithTag(container.ContainerReference.Tag); + fileContent = HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, newValue); + wasUpdated = true; + filesUpdated.Add(valuesFile); + imagesUpdated.Add(newValue); + } + } + else + { + log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); + } + break; + } + + if (wasUpdated) + { + fileSystem.WriteAllText(repoRelativePath, fileContent); + } + } + } + + PushToRemote(repository, + GitReference.CreateFromString(sourceWithMetadata.Source.TargetRevision), //this is a hack, shouldn't need to KEEP re-converting it. + deploymentConfig.CommitParameters, + filesUpdated, + imagesUpdated); + } + + return new SourceUpdateResult(new HashSet(), string.Empty); + } + void LogHelmSourceConfigurationProblems(IReadOnlyCollection helmSourceConfigurationProblems) { foreach (var helmSourceConfigurationProblem in helmSourceConfigurationProblems) @@ -648,7 +788,48 @@ IEnumerable FindYamlFiles(string rootPath) var yamlFileGlob = "**/*.{yaml,yml}"; return fileSystem.EnumerateFilesWithGlob(rootPath, yamlFileGlob); } + + static ValueFileContent DetermineTypeOfContent(string content) + { + var lastColonIndex = content.LastIndexOf(':'); + var lastSlashIndex = content.LastIndexOf('/'); + + if (lastColonIndex == -1) + { + if (lastSlashIndex == -1) + { + return ValueFileContent.PlainText; + } + + return ValueFileContent.RegistryAndImageName; + } + + if (lastColonIndex > lastSlashIndex) + { + if (lastSlashIndex == -1) + { + return ValueFileContent.ImageNameAndTag; + } + return ValueFileContent.RegistryImageNameAndTag; + } + + //otherwise the string has a colon and a slash, but the slash is after the colon + // partA:partb/partc <-- illegal image tag. + return ValueFileContent.PlainText; + } record SourceUpdateResult(HashSet ImagesUpdated, string CommitSha); } + + + + + enum ValueFileContent + { + PlainText, + ImageNameAndTag, + RegistryAndImageName, + RegistryImageNameAndTag + } + } \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs b/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs index 9e1971c9a8..7bd29382ba 100644 --- a/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs +++ b/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Octostache; namespace Calamari.ArgoCD.Helm @@ -24,7 +25,7 @@ public static VariableDictionary GenerateVariableDictionary(HelmYamlParser parse } /// - /// Updates the value of yaml a node and returns it as a strung (preserving formatting). + /// Updates the value of yaml a node and returns it as a string (preserving formatting). /// public static string UpdateNodeValue(string yamlContent, string path, string newValue) { @@ -32,6 +33,18 @@ public static string UpdateNodeValue(string yamlContent, string path, string new var yamlProcessor = new HelmYamlParser(yamlContent); return yamlProcessor.UpdateContentForPath(path, newValue); } - } -} + public static Dictionary CreateDictionary(HelmYamlParser parsedYaml) + { + var allValuesPaths = parsedYaml.CreateDotPathsForNodes(); + var result = new Dictionary(); + foreach (var path in allValuesPaths) + { + var value = parsedYaml.GetValueAtPath(path); + result.Add(path, value); + } + + return result; + } + } +} \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Helm/HelmValuesFileExtractor.cs b/source/Calamari/ArgoCD/Helm/HelmValuesFileExtractor.cs new file mode 100644 index 0000000000..669d7abdbe --- /dev/null +++ b/source/Calamari/ArgoCD/Helm/HelmValuesFileExtractor.cs @@ -0,0 +1,43 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Calamari.ArgoCD.Conventions; +using Calamari.ArgoCD.Domain; +using Calamari.ArgoCD.Models; + +namespace Calamari.ArgoCD.Helm +{ + public class HelmValuesFileExtractor + { + readonly List helmSources; + + readonly string defaultRegistry; + + public HelmValuesFileExtractor(Application toUpdate, string defaultRegistry) + { + helmSources = toUpdate.GetSourcesWithMetadata().Where(s => s.SourceType == SourceType.Helm).ToList(); + this.defaultRegistry = defaultRegistry; + } + + public IReadOnlyCollection GetInlineValuesFilesReferencedByHelmSource(ApplicationSourceWithMetadata helmSource) + { + return helmSource.Source.Helm?.ValueFiles.Where(file => !file.StartsWith('$')) + .Select(vf => Path.Combine(helmSource.Source.Path!, vf)) + .ToList() + ?? []; + } + + public IReadOnlyCollection GetValueFilesReferencedInRefSource(ApplicationSourceWithMetadata refSource) + { + var refPrefix = $"${refSource.Source.Ref!}/"; + return helmSources + .Where(hs => hs.Source.Helm != null) + .SelectMany(hs => hs.Source.Helm!.ValueFiles.Where(f => f.StartsWith(refPrefix))) + .Distinct() + .Select(vf => vf.Substring(refPrefix.Length)) + .ToList(); + } + } +} diff --git a/source/Calamari/ArgoCD/Models/ContainerImageReference.cs b/source/Calamari/ArgoCD/Models/ContainerImageReference.cs index 8345f6740b..42ab14c22e 100644 --- a/source/Calamari/ArgoCD/Models/ContainerImageReference.cs +++ b/source/Calamari/ArgoCD/Models/ContainerImageReference.cs @@ -7,12 +7,6 @@ public class ContainerImageReference { ContainerImageReference(string registry, string imageName, string tag, string defaultRegistry) { - // Trim special case of "index.docker.io" to "docker.io" - simplifies check further down, and we never want to write out the full "index." version anyway. - if (registry.Equals($"index.{ArgoCDConstants.DefaultContainerRegistry}", StringComparison.OrdinalIgnoreCase)) - { - registry = ArgoCDConstants.DefaultContainerRegistry; - } - ImageName = imageName; Tag = tag; Registry = registry; @@ -112,9 +106,17 @@ static bool RegistriesMatch(ContainerImageReference reference1, ContainerImageRe string NormalizeRegistry(ContainerImageReference imageReference) { - return !string.IsNullOrWhiteSpace(imageReference.Registry) + var registry = !string.IsNullOrWhiteSpace(imageReference.Registry) ? imageReference.Registry : imageReference.DefaultRegistry; + + // Trim special case of "index.docker.io" to "docker.io" - simplifies check further down, and we never want to write out the full "index." version anyway. + if (registry.Equals($"index.{ArgoCDConstants.DefaultContainerRegistry}", StringComparison.OrdinalIgnoreCase)) + { + return ArgoCDConstants.DefaultContainerRegistry; + } + + return registry; } } diff --git a/source/Calamari/ArgoCD/Models/HelmValuesFile.cs b/source/Calamari/ArgoCD/Models/HelmValuesFile.cs new file mode 100644 index 0000000000..ed2a61604a --- /dev/null +++ b/source/Calamari/ArgoCD/Models/HelmValuesFile.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace Calamari.ArgoCD.Models +{ + public class HelmValuesFile + { + public HelmValuesFile(string defaultClusterRegistry, + string path, + string fileName) + { + Path = path; + DefaultClusterRegistry = defaultClusterRegistry; + FileName = fileName; + } + public string Path { get; } + public string DefaultClusterRegistry { get; } + public string FileName { get; } + } +} \ No newline at end of file diff --git a/source/Calamari/Kubernetes/SpecialVariables.cs b/source/Calamari/Kubernetes/SpecialVariables.cs index e68921237f..c746eabede 100644 --- a/source/Calamari/Kubernetes/SpecialVariables.cs +++ b/source/Calamari/Kubernetes/SpecialVariables.cs @@ -75,6 +75,8 @@ public static class Git public static readonly string InputPath = "Octopus.Action.ArgoCD.InputPath"; public static readonly string PurgeOutput = "Octopus.Action.ArgoCD.PurgeOutputFolder"; + + public static readonly string UseHelmValueYamlPathFromStep = "Octopus.Action.ArgoCD.UseHelmValueYamlPathFromStep"; public static class PullRequest { From cc0bd84943db6167e6c184e3e84f0f153e86b10e Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Wed, 18 Feb 2026 08:32:50 +1100 Subject: [PATCH 03/30] sort out containerImageReferenceTest --- .../ArgoCD/Models/ContainerImageReferenceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Calamari.Tests/ArgoCD/Models/ContainerImageReferenceTests.cs b/source/Calamari.Tests/ArgoCD/Models/ContainerImageReferenceTests.cs index 1a1d11d712..1492f8acbc 100644 --- a/source/Calamari.Tests/ArgoCD/Models/ContainerImageReferenceTests.cs +++ b/source/Calamari.Tests/ArgoCD/Models/ContainerImageReferenceTests.cs @@ -149,7 +149,7 @@ public void WithTag_WithDefaultRegistry_ReturnsImageWithTag() [Theory] [TestCase("nginx", "nginx")] [TestCase("docker.io/nginx", "docker.io/nginx")] - [TestCase("index.docker.io/nginx:latest", "docker.io/nginx:latest")] + [TestCase("index.docker.io/nginx:latest", "index.docker.io/nginx:latest")] [TestCase("nginx:latest", "nginx:latest")] public void ToString_ReturnsCorrectlyFormattedImageNameBasedOnComponents(string reference, string expected) { From 27fc49177e9924bc2eeae6fd0f5f551a77b8409b Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Wed, 18 Feb 2026 15:49:21 +1100 Subject: [PATCH 04/30] yeah that --- .../Calamari/ArgoCD/Models/HelmValuesFile.cs | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 source/Calamari/ArgoCD/Models/HelmValuesFile.cs diff --git a/source/Calamari/ArgoCD/Models/HelmValuesFile.cs b/source/Calamari/ArgoCD/Models/HelmValuesFile.cs deleted file mode 100644 index ed2a61604a..0000000000 --- a/source/Calamari/ArgoCD/Models/HelmValuesFile.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Calamari.ArgoCD.Models -{ - public class HelmValuesFile - { - public HelmValuesFile(string defaultClusterRegistry, - string path, - string fileName) - { - Path = path; - DefaultClusterRegistry = defaultClusterRegistry; - FileName = fileName; - } - public string Path { get; } - public string DefaultClusterRegistry { get; } - public string FileName { get; } - } -} \ No newline at end of file From 762377961ec1eea5b6823dd4cc3f18d62702a07f Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Wed, 18 Feb 2026 16:21:21 +1100 Subject: [PATCH 05/30] gotta smallenate this --- ...goCDAppImagesInstallConventionHelmTests.cs | 1 - .../UpdateArgoCDAppImagesInstallConvention.cs | 98 ++++++------------- 2 files changed, 30 insertions(+), 69 deletions(-) diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs index c3b97e31e8..c9f30278b3 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using Calamari.ArgoCD; using Calamari.ArgoCD.Conventions; using Calamari.ArgoCD.Domain; diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 5194a73a50..faf5153769 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -261,7 +261,7 @@ SourceUpdateResult ProcessKustomize( { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages, patchedFiles) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.ImageReferences); + var (updatedFiles, updatedImages, patchedFiles) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList()); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -348,7 +348,7 @@ SourceUpdateResult ProcessDirectory( { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages, patchedFiles) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.ImageReferences); + var (updatedFiles, updatedImages, patchedFiles) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList()); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -459,7 +459,7 @@ SourceUpdateResult ProcessHelmUpdateTargets( { var results = targets.Select(t => UpdateHelmImageValues(repository.WorkingDirectory, t, - deploymentConfig.ImageReferences + deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList() )) .ToList(); @@ -484,7 +484,7 @@ SourceUpdateResult ProcessHelmUpdateTargets( } } - return new SourceUpdateResult(new HashSet(), string.Empty); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } SourceUpdateResult ProcessHelmSourceUsingStepVariables(Application applicationFromYaml, @@ -540,39 +540,34 @@ SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valu var flattenedYamlPathDictionary = HelmValuesEditor.CreateDictionary(originalYamlParser); foreach (var container in stepReferencedContainers.Where(c => c.HelmReference is not null)) { - if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var helmValue)) + if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var valueToUpdate)) { - var valueContentType = DetermineTypeOfContent(helmValue); - var cir = ContainerImageReference.FromReferenceString(helmValue, defaultRegistry); - switch (valueContentType) + if (IsUnstructuredText(valueToUpdate)) { - case ValueFileContent.PlainText: - HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, container.ContainerReference.Tag); - filesUpdated.Add(valuesFile); - imagesUpdated.Add(container.ContainerReference.ToString()); - break; - case ValueFileContent.ImageNameAndTag: - case ValueFileContent.RegistryAndImageName: - case ValueFileContent.RegistryImageNameAndTag: - var comparison = container.ContainerReference.CompareWith(cir); - if (comparison.MatchesImage()) - { - if (!comparison.TagMatch) - { - var newValue = cir.WithTag(container.ContainerReference.Tag); - fileContent = HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, newValue); - wasUpdated = true; - filesUpdated.Add(valuesFile); - imagesUpdated.Add(newValue); - } - } - else + HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, container.ContainerReference.Tag); + filesUpdated.Add(valuesFile); + imagesUpdated.Add(container.ContainerReference.ToString()); + } + else + { + var cir = ContainerImageReference.FromReferenceString(valueToUpdate, defaultRegistry); + var comparison = container.ContainerReference.CompareWith(cir); + if (comparison.MatchesImage()) + { + if (!comparison.TagMatch) { - log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); + var newValue = cir.WithTag(container.ContainerReference.Tag); + fileContent = HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, newValue); + wasUpdated = true; + filesUpdated.Add(valuesFile); + imagesUpdated.Add(newValue); } - break; + } + else + { + log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); + } } - if (wasUpdated) { fileSystem.WriteAllText(repoRelativePath, fileContent); @@ -587,7 +582,7 @@ SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valu imagesUpdated); } - return new SourceUpdateResult(new HashSet(), string.Empty); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } void LogHelmSourceConfigurationProblems(IReadOnlyCollection helmSourceConfigurationProblems) @@ -795,47 +790,14 @@ IEnumerable FindYamlFiles(string rootPath) return fileSystem.EnumerateFilesWithGlob(rootPath, yamlFileGlob); } - static ValueFileContent DetermineTypeOfContent(string content) + static bool IsUnstructuredText(string content) { var lastColonIndex = content.LastIndexOf(':'); var lastSlashIndex = content.LastIndexOf('/'); - if (lastColonIndex == -1) - { - if (lastSlashIndex == -1) - { - return ValueFileContent.PlainText; - } - - return ValueFileContent.RegistryAndImageName; - } - - if (lastColonIndex > lastSlashIndex) - { - if (lastSlashIndex == -1) - { - return ValueFileContent.ImageNameAndTag; - } - return ValueFileContent.RegistryImageNameAndTag; - } - - //otherwise the string has a colon and a slash, but the slash is after the colon - // partA:partb/partc <-- illegal image tag. - return ValueFileContent.PlainText; + return lastColonIndex == -1 && lastSlashIndex == -1; } record SourceUpdateResult(HashSet ImagesUpdated, string CommitSha, List PatchedFiles); } - - - - - enum ValueFileContent - { - PlainText, - ImageNameAndTag, - RegistryAndImageName, - RegistryImageNameAndTag - } - } From 37f4092d6a527124938576510c9ad81c3a5222db Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Wed, 18 Feb 2026 17:17:11 +1100 Subject: [PATCH 06/30] clean it a little --- .../UpdateArgoCDAppImagesInstallConvention.cs | 2 +- .../Calamari/ArgoCD/Helm/HelmValuesEditor.cs | 20 +++++++------------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index faf5153769..6cff6aef17 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -537,7 +537,7 @@ SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valu log.Info($"Processing file at {valuesFile}."); var fileContent = fileSystem.ReadFile(repoRelativePath); var originalYamlParser = new HelmYamlParser(fileContent); // Parse and track the original yaml so that content can be read from it. - var flattenedYamlPathDictionary = HelmValuesEditor.CreateDictionary(originalYamlParser); + var flattenedYamlPathDictionary = HelmValuesEditor.CreateFlattenedDictionary(originalYamlParser); foreach (var container in stepReferencedContainers.Where(c => c.HelmReference is not null)) { if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var valueToUpdate)) diff --git a/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs b/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs index 7bd29382ba..1077512a26 100644 --- a/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs +++ b/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs @@ -1,11 +1,18 @@ using System; using System.Collections.Generic; +using System.Linq; using Octostache; namespace Calamari.ArgoCD.Helm { public class HelmValuesEditor { + public static Dictionary CreateFlattenedDictionary(HelmYamlParser parsedYaml) + { + var allValuesPaths = parsedYaml.CreateDotPathsForNodes(); + return allValuesPaths.ToDictionary(a => a, parsedYaml.GetValueAtPath); + } + /// /// Converts YAML into a Variable Dictionary that can be used to resolve the value of /// a dot-notation path and used with HelmTemplate/Octostache syntax. @@ -33,18 +40,5 @@ public static string UpdateNodeValue(string yamlContent, string path, string new var yamlProcessor = new HelmYamlParser(yamlContent); return yamlProcessor.UpdateContentForPath(path, newValue); } - - public static Dictionary CreateDictionary(HelmYamlParser parsedYaml) - { - var allValuesPaths = parsedYaml.CreateDotPathsForNodes(); - var result = new Dictionary(); - foreach (var path in allValuesPaths) - { - var value = parsedYaml.GetValueAtPath(path); - result.Add(path, value); - } - - return result; - } } } \ No newline at end of file From 2430d3ff2b1caa8e3a006a385c6f6114eceb5e92 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Wed, 18 Feb 2026 17:33:06 +1100 Subject: [PATCH 07/30] what? --- .../ArgoCD/Conventions/SourceUpdateResult.cs | 7 + .../UpdateArgoCDAppImagesInstallConvention.cs | 270 ++++++++++-------- .../ArgoCD/Helm/UpdateUsingStepVariables.cs | 86 ++++++ 3 files changed, 236 insertions(+), 127 deletions(-) create mode 100644 source/Calamari/ArgoCD/Conventions/SourceUpdateResult.cs create mode 100644 source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs diff --git a/source/Calamari/ArgoCD/Conventions/SourceUpdateResult.cs b/source/Calamari/ArgoCD/Conventions/SourceUpdateResult.cs new file mode 100644 index 0000000000..46cd017446 --- /dev/null +++ b/source/Calamari/ArgoCD/Conventions/SourceUpdateResult.cs @@ -0,0 +1,7 @@ +#nullable enable +using System; +using System.Collections.Generic; + +namespace Calamari.ArgoCD.Conventions; + +public record SourceUpdateResult(HashSet ImagesUpdated, string CommitSha, List PatchedFiles); \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 6cff6aef17..5d17b8ed43 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -63,10 +63,10 @@ public void Install(RunningDeployment deployment) var deploymentConfig = deploymentConfigFactory.CreateUpdateImageConfig(deployment); var repositoryFactory = new RepositoryFactory(log, - fileSystem, - deployment.CurrentDirectory, - gitVendorAgnosticApiAdapterFactory, - clock); + fileSystem, + deployment.CurrentDirectory, + gitVendorAgnosticApiAdapterFactory, + clock); var argoProperties = customPropertiesLoader.Load(); @@ -80,11 +80,11 @@ public void Install(RunningDeployment deployment) { var gateway = argoProperties.Gateways.Single(g => g.Id == application.GatewayId); return ProcessApplication(application, - gateway, - deploymentScope, - gitCredentials, - repositoryFactory, - deploymentConfig); + gateway, + deploymentScope, + gitCredentials, + repositoryFactory, + deploymentConfig); }) .ToList(); @@ -98,11 +98,11 @@ public void Install(RunningDeployment deployment) var gatewayIds = argoProperties.Applications.Select(a => a.GatewayId).ToHashSet(); outputVariablesWriter.WriteImageUpdateOutput(gatewayIds, - gitReposUpdated, - totalApplicationsWithSourceCounts, - updatedApplicationsWithSources, - newImagesWritten.Count - ); + gitReposUpdated, + totalApplicationsWithSourceCounts, + updatedApplicationsWithSources, + newImagesWritten.Count + ); } ProcessApplicationResult ProcessApplication( @@ -124,14 +124,14 @@ ProcessApplicationResult ProcessApplication( .Select(applicationSource => new { Updated = ProcessSource(applicationSource, - applicationFromYaml, - containsMultipleSources, - deploymentScope, - gitCredentials, - repositoryFactory, - deploymentConfig, - application.DefaultRegistry, - gateway), + applicationFromYaml, + containsMultipleSources, + deploymentScope, + gitCredentials, + repositoryFactory, + deploymentConfig, + application.DefaultRegistry, + gateway), applicationSource, }) .Where(r => r.Updated.ImagesUpdated.Any()) @@ -150,13 +150,13 @@ ProcessApplicationResult ProcessApplication( log.InfoFormat(message, linkifiedAppName); return new ProcessApplicationResult( - application.GatewayId, - applicationName.ToApplicationName(), - applicationFromYaml.Spec.Sources.Count, - applicationFromYaml.Spec.Sources.Count(s => deploymentScope.Matches(ScopingAnnotationReader.GetScopeForApplicationSource(s.Name.ToApplicationSourceName(), applicationFromYaml.Metadata.Annotations, containsMultipleSources))), - updatedSourcesResults.Select(r => new UpdatedSourceDetail(r.Updated.CommitSha, r.applicationSource.Index, [], r.Updated.PatchedFiles)).ToList(), - updatedSourcesResults.SelectMany(r => r.Updated.ImagesUpdated).ToHashSet(), - updatedSourcesResults.Select(r => r.applicationSource.Source.OriginalRepoUrl).ToHashSet()); + application.GatewayId, + applicationName.ToApplicationName(), + applicationFromYaml.Spec.Sources.Count, + applicationFromYaml.Spec.Sources.Count(s => deploymentScope.Matches(ScopingAnnotationReader.GetScopeForApplicationSource(s.Name.ToApplicationSourceName(), applicationFromYaml.Metadata.Annotations, containsMultipleSources))), + updatedSourcesResults.Select(r => new UpdatedSourceDetail(r.Updated.CommitSha, r.applicationSource.Index, [], r.Updated.PatchedFiles)).ToList(), + updatedSourcesResults.SelectMany(r => r.Updated.ImagesUpdated).ToHashSet(), + updatedSourcesResults.Select(r => r.applicationSource.Source.OriginalRepoUrl).ToHashSet()); } void ValidateApplication(Application applicationFromYaml) @@ -195,39 +195,39 @@ SourceUpdateResult ProcessSource( { return applicationSource.Ref != null ? ProcessRef(applicationFromYaml, - gitCredentials, - repositoryFactory, - deploymentConfig, - sourceWithMetadata, - defaultRegistry, - gateway) + gitCredentials, + repositoryFactory, + deploymentConfig, + sourceWithMetadata, + defaultRegistry, + gateway) : ProcessDirectory(applicationFromYaml, - gitCredentials, - repositoryFactory, - deploymentConfig, - sourceWithMetadata, - defaultRegistry, - gateway); + gitCredentials, + repositoryFactory, + deploymentConfig, + sourceWithMetadata, + defaultRegistry, + gateway); } case SourceType.Helm: { return ProcessHelm(applicationFromYaml, - sourceWithMetadata, - gitCredentials, - repositoryFactory, - deploymentConfig, - defaultRegistry, - gateway); + sourceWithMetadata, + gitCredentials, + repositoryFactory, + deploymentConfig, + defaultRegistry, + gateway); } case SourceType.Kustomize: { return ProcessKustomize(applicationFromYaml, - gitCredentials, - repositoryFactory, - deploymentConfig, - sourceWithMetadata, - defaultRegistry, - gateway); + gitCredentials, + repositoryFactory, + deploymentConfig, + sourceWithMetadata, + defaultRegistry, + gateway); } case SourceType.Plugin: { @@ -265,17 +265,17 @@ SourceUpdateResult ProcessKustomize( if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, - GitReference.CreateFromString(applicationSource.TargetRevision), - deploymentConfig.CommitParameters, - updatedFiles, - updatedImages); + GitReference.CreateFromString(applicationSource.TargetRevision), + deploymentConfig.CommitParameters, + updatedFiles, + updatedImages); if (pushResult is not null) { outputVariablesWriter.WritePushResultOutput(gateway.Name, - applicationFromYaml.Metadata.Name, - sourceWithMetadata.Index, - pushResult); + applicationFromYaml.Metadata.Name, + sourceWithMetadata.Index, + pushResult); return new SourceUpdateResult(updatedImages, pushResult.CommitSha, patchedFiles); } } @@ -300,7 +300,7 @@ SourceUpdateResult ProcessRef( { log.WarnFormat("The source '{0}' contains a Ref, only referenced files will be updated. Please create another source with the same URL if you wish to update files under the path.", sourceWithMetadata.SourceIdentity); } - + if (!deploymentConfig.UseHelmValueYamlPathFromStep) { var helmTargetsForRefSource = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) @@ -315,16 +315,16 @@ SourceUpdateResult ProcessRef( deploymentConfig, sourceWithMetadata, helmTargetsForRefSource.Targets, - gateway); + gateway); } - + return ProcessRefSourceUsingStepVariables(applicationFromYaml, sourceWithMetadata, gitCredentials, repositoryFactory, deploymentConfig, defaultRegistry, - gateway); + gateway); } /// Images that were updated @@ -352,17 +352,17 @@ SourceUpdateResult ProcessDirectory( if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, - GitReference.CreateFromString(applicationSource.TargetRevision), - deploymentConfig.CommitParameters, - updatedFiles, - updatedImages); + GitReference.CreateFromString(applicationSource.TargetRevision), + deploymentConfig.CommitParameters, + updatedFiles, + updatedImages); if (pushResult is not null) { outputVariablesWriter.WritePushResultOutput(gateway.Name, - applicationFromYaml.Metadata.Name, - sourceWithMetadata.Index, - pushResult); + applicationFromYaml.Metadata.Name, + sourceWithMetadata.Index, + pushResult); return new SourceUpdateResult(updatedImages, pushResult.CommitSha, patchedFiles); } @@ -393,13 +393,20 @@ SourceUpdateResult ProcessHelm( if (!deploymentConfig.UseHelmValueYamlPathFromStep) { - return ProcessHelmSourceUsingAnnotations(applicationFromYaml, sourceWithMetadata, gitCredentials, repositoryFactory, + return ProcessHelmSourceUsingAnnotations(applicationFromYaml, + sourceWithMetadata, + gitCredentials, + repositoryFactory, deploymentConfig, defaultRegistry, gateway, - applicationSource); + applicationSource); } - return ProcessHelmSourceUsingStepVariables(applicationFromYaml, sourceWithMetadata, gitCredentials, repositoryFactory, + + return ProcessHelmSourceUsingStepVariables(applicationFromYaml, + sourceWithMetadata, + gitCredentials, + repositoryFactory, deploymentConfig, defaultRegistry, gateway, @@ -407,13 +414,13 @@ SourceUpdateResult ProcessHelm( } SourceUpdateResult ProcessHelmSourceUsingAnnotations(Application applicationFromYaml, - ApplicationSourceWithMetadata sourceWithMetadata, - Dictionary gitCredentials, - RepositoryFactory repositoryFactory, - UpdateArgoCDAppDeploymentConfig deploymentConfig, - string defaultRegistry, - ArgoCDGatewayDto gateway, - ApplicationSource applicationSource) + ApplicationSourceWithMetadata sourceWithMetadata, + Dictionary gitCredentials, + RepositoryFactory repositoryFactory, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + string defaultRegistry, + ArgoCDGatewayDto gateway, + ApplicationSource applicationSource) { var explicitHelmSources = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) .GetExplicitValuesFilesToUpdate(sourceWithMetadata); @@ -458,9 +465,9 @@ SourceUpdateResult ProcessHelmUpdateTargets( ArgoCDGatewayDto gateway) { var results = targets.Select(t => UpdateHelmImageValues(repository.WorkingDirectory, - t, - deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList() - )) + t, + deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList() + )) .ToList(); var updatedImages = results.SelectMany(r => r.ImagesUpdated).ToHashSet(); @@ -469,17 +476,17 @@ SourceUpdateResult ProcessHelmUpdateTargets( var patchedFiles = results.Select(r => new FilePathContent(r.RelativeFilepath, r.JsonPatch)).ToList(); var pushResult = PushToRemote(repository, - GitReference.CreateFromString(sourceWithMetadata.Source.TargetRevision), - deploymentConfig.CommitParameters, - results.Where(r => r.ImagesUpdated.Any()).Select(r => r.RelativeFilepath).ToHashSet(), - updatedImages); + GitReference.CreateFromString(sourceWithMetadata.Source.TargetRevision), + deploymentConfig.CommitParameters, + results.Where(r => r.ImagesUpdated.Any()).Select(r => r.RelativeFilepath).ToHashSet(), + updatedImages); if (pushResult is not null) { outputVariablesWriter.WritePushResultOutput(gateway.Name, - applicationFromYaml.Metadata.Name, - sourceWithMetadata.Index, - pushResult); + applicationFromYaml.Metadata.Name, + sourceWithMetadata.Index, + pushResult); return new SourceUpdateResult(updatedImages, pushResult.CommitSha, patchedFiles); } } @@ -498,35 +505,50 @@ SourceUpdateResult ProcessHelmSourceUsingStepVariables(Application applicationFr { var extractor = new HelmValuesFileExtractor(applicationFromYaml, defaultRegistry); var valuesFilesInHelmSource = extractor.GetInlineValuesFilesReferencedByHelmSource(sourceWithMetadata); - + //Add the implicit value file if needed using var repository = CreateRepository(gitCredentials, applicationSource, repositoryFactory); var implicitValuesFile = HelmDiscovery.TryFindValuesFile(fileSystem, sourceWithMetadata.Source.Path!); var valuesFilesToUpdate = valuesFilesInHelmSource.ToList(); - if (implicitValuesFile != null && !valuesFilesInHelmSource.Contains(implicitValuesFile)) + if (implicitValuesFile != null && !valuesFilesInHelmSource.Contains(implicitValuesFile)) { valuesFilesToUpdate.Add(implicitValuesFile); } - - return UpdateImageTagsInValuesFiles(valuesFilesToUpdate, deploymentConfig.PackageWithHelmReference, defaultRegistry, deploymentConfig, repository, sourceWithMetadata); + + return UpdateImageTagsInValuesFiles(valuesFilesToUpdate, + deploymentConfig.PackageWithHelmReference, + defaultRegistry, + deploymentConfig, + repository, + sourceWithMetadata); } - + SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFromYaml, - ApplicationSourceWithMetadata sourceWithMetadata, - Dictionary gitCredentials, - RepositoryFactory repositoryFactory, - UpdateArgoCDAppDeploymentConfig deploymentConfig, - string defaultRegistry, - ArgoCDGatewayDto gateway) + ApplicationSourceWithMetadata sourceWithMetadata, + Dictionary gitCredentials, + RepositoryFactory repositoryFactory, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + string defaultRegistry, + ArgoCDGatewayDto gateway) { var extractor = new HelmValuesFileExtractor(applicationFromYaml, defaultRegistry); var valuesFilesInHelmSource = extractor.GetValueFilesReferencedInRefSource(sourceWithMetadata); - + using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); - return UpdateImageTagsInValuesFiles(valuesFilesInHelmSource, deploymentConfig.PackageWithHelmReference, defaultRegistry, deploymentConfig, repository, sourceWithMetadata); + return UpdateImageTagsInValuesFiles(valuesFilesInHelmSource, + deploymentConfig.PackageWithHelmReference, + defaultRegistry, + deploymentConfig, + repository, + sourceWithMetadata); } - SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valuesFilesToUpdate, IReadOnlyCollection stepReferencedContainers, string defaultRegistry, UpdateArgoCDAppDeploymentConfig deploymentConfig, RepositoryWrapper repository, ApplicationSourceWithMetadata sourceWithMetadata) + SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valuesFilesToUpdate, + IReadOnlyCollection stepReferencedContainers, + string defaultRegistry, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + RepositoryWrapper repository, + ApplicationSourceWithMetadata sourceWithMetadata) { var filesUpdated = new HashSet(); var imagesUpdated = new HashSet(); @@ -568,6 +590,7 @@ SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valu log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); } } + if (wasUpdated) { fileSystem.WriteAllText(repoRelativePath, fileContent); @@ -601,13 +624,13 @@ void LogProblem(HelmSourceConfigurationProblem helmSourceConfigurationProblem) if (helmSourceIsMissingImagePathAnnotation.RefSourceIdentity == null) { log.WarnFormat("The Helm source '{0}' is missing an annotation for the image replace path. It will not be updated.", - helmSourceIsMissingImagePathAnnotation.SourceIdentity); + helmSourceIsMissingImagePathAnnotation.SourceIdentity); } else { log.WarnFormat("The Helm source '{0}' is missing an annotation for the image replace path. The source '{1}' will not be updated.", - helmSourceIsMissingImagePathAnnotation.SourceIdentity, - helmSourceIsMissingImagePathAnnotation.RefSourceIdentity); + helmSourceIsMissingImagePathAnnotation.SourceIdentity, + helmSourceIsMissingImagePathAnnotation.RefSourceIdentity); } log.WarnFormat("Annotation creation documentation can be found {0}.", log.FormatShortLink("argo-cd-helm-image-annotations", "here")); @@ -639,18 +662,18 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede string defaultRegistry) { var imageReplacePaths = ScopingAnnotationReader.GetImageReplacePathsForApplicationSource( - applicationSource.Source.Name.ToApplicationSourceName(), - applicationFromYaml.Metadata.Annotations, - applicationFromYaml.Spec.Sources.Count > 1); + applicationSource.Source.Name.ToApplicationSourceName(), + applicationFromYaml.Metadata.Annotations, + applicationFromYaml.Spec.Sources.Count > 1); if (!imageReplacePaths.Any()) { return (null, new HelmSourceIsMissingImagePathAnnotation(applicationSource.SourceIdentity)); } return (new HelmValuesFileImageUpdateTarget(defaultRegistry, - applicationSource.Source.Path, - valuesFilename, - imageReplacePaths), null); + applicationSource.Source.Path, + valuesFilename, + imageReplacePaths), null); } (HashSet, HashSet, List) UpdateKubernetesYaml( @@ -727,6 +750,7 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede return (updatedFiles, updatedImages, jsonPatches); } + HelmRefUpdatedResult UpdateHelmImageValues( string rootPath, @@ -775,10 +799,10 @@ HelmRefUpdatedResult UpdateHelmImageValues( log.Verbose("Pushing to remote"); return repository.PushChanges(commitParameters.RequiresPr, - commitParameters.Summary, - commitDescription, - branchName, - CancellationToken.None) + commitParameters.Summary, + commitDescription, + branchName, + CancellationToken.None) .GetAwaiter() .GetResult(); } @@ -789,15 +813,7 @@ IEnumerable FindYamlFiles(string rootPath) var yamlFileGlob = "**/*.{yaml,yml}"; return fileSystem.EnumerateFilesWithGlob(rootPath, yamlFileGlob); } - - static bool IsUnstructuredText(string content) - { - var lastColonIndex = content.LastIndexOf(':'); - var lastSlashIndex = content.LastIndexOf('/'); - - return lastColonIndex == -1 && lastSlashIndex == -1; - } record SourceUpdateResult(HashSet ImagesUpdated, string CommitSha, List PatchedFiles); } -} +} \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs b/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs new file mode 100644 index 0000000000..ea8f390ada --- /dev/null +++ b/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Calamari.ArgoCD.Conventions; +using Calamari.ArgoCD.Domain; +using Calamari.ArgoCD.Dtos; +using Calamari.ArgoCD.Git; +using Calamari.ArgoCD.Models; +using Calamari.Common.Plumbing.FileSystem; +using Calamari.Common.Plumbing.Logging; + +namespace Calamari.ArgoCD.Helm; + +public class UpdateUsingStepVariables +{ + readonly ILog log; + readonly ICalamariFileSystem fileSystem; + + public UpdateUsingStepVariables(ILog log, ICalamariFileSystem fileSystem) + { + this.log = log; + this.fileSystem = fileSystem; + } + + SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valuesFilesToUpdate, IReadOnlyCollection stepReferencedContainers, string defaultRegistry, UpdateArgoCDAppDeploymentConfig deploymentConfig, RepositoryWrapper repository, ApplicationSourceWithMetadata sourceWithMetadata) + { + var filesUpdated = new HashSet(); + var imagesUpdated = new HashSet(); + foreach (var valuesFile in valuesFilesToUpdate) + { + var wasUpdated = false; + var repoRelativePath = Path.Combine(repository.WorkingDirectory, valuesFile); + log.Info($"Processing file at {valuesFile}."); + var fileContent = fileSystem.ReadFile(repoRelativePath); + var originalYamlParser = new HelmYamlParser(fileContent); // Parse and track the original yaml so that content can be read from it. + var flattenedYamlPathDictionary = HelmValuesEditor.CreateFlattenedDictionary(originalYamlParser); + foreach (var container in stepReferencedContainers.Where(c => c.HelmReference is not null)) + { + if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var valueToUpdate)) + { + if (IsUnstructuredText(valueToUpdate)) + { + HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, container.ContainerReference.Tag); + filesUpdated.Add(valuesFile); + imagesUpdated.Add(container.ContainerReference.ToString()); + } + else + { + var cir = ContainerImageReference.FromReferenceString(valueToUpdate, defaultRegistry); + var comparison = container.ContainerReference.CompareWith(cir); + if (comparison.MatchesImage()) + { + if (!comparison.TagMatch) + { + var newValue = cir.WithTag(container.ContainerReference.Tag); + fileContent = HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, newValue); + wasUpdated = true; + filesUpdated.Add(valuesFile); + imagesUpdated.Add(newValue); + } + } + else + { + log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); + } + } + if (wasUpdated) + { + fileSystem.WriteAllText(repoRelativePath, fileContent); + } + } + } + } + + return new SourceUpdateResult(new HashSet(), string.Empty, []); + } + + + static bool IsUnstructuredText(string content) + { + var lastColonIndex = content.LastIndexOf(':'); + var lastSlashIndex = content.LastIndexOf('/'); + + return lastColonIndex == -1 && lastSlashIndex == -1; + } +} \ No newline at end of file From d56544d16b989c28678beabf9685b4d2981dce12 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Wed, 18 Feb 2026 17:34:20 +1100 Subject: [PATCH 08/30] no idea --- source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs b/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs index ea8f390ada..02a2a69fe4 100644 --- a/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs +++ b/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs @@ -3,7 +3,6 @@ using System.Linq; using Calamari.ArgoCD.Conventions; using Calamari.ArgoCD.Domain; -using Calamari.ArgoCD.Dtos; using Calamari.ArgoCD.Git; using Calamari.ArgoCD.Models; using Calamari.Common.Plumbing.FileSystem; From 9cb28ec0597cf425819a5aafc777e7e605a93f60 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Thu, 19 Feb 2026 09:32:30 +1100 Subject: [PATCH 09/30] splitting it out --- .../Conventions/DeploymentConfigFactory.cs | 2 +- .../UpdateArgoCDAppDeploymentConfig.cs | 6 +- .../UpdateArgoCDAppImagesInstallConvention.cs | 92 +++---------------- .../ArgoCD/Helm/UpdateUsingStepVariables.cs | 84 +++++++++-------- 4 files changed, 62 insertions(+), 122 deletions(-) diff --git a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs index 20e6d497b4..176156c2a0 100644 --- a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs +++ b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs @@ -34,7 +34,7 @@ public ArgoCommitToGitConfig CreateCommitToGitConfig(RunningDeployment deploymen public UpdateArgoCDAppDeploymentConfig CreateUpdateImageConfig(RunningDeployment deployment) { var commitParameters = CommitParameters(deployment); - var packageHelmReference = deployment.Variables.GetContainerPackages().Select(p => new PackageAndHelmReference(ContainerImageReference.FromReferenceString(p.PackageName), + var packageHelmReference = deployment.Variables.GetContainerPackages().Select(p => new ContainerImageReferenceAndHelmReference(ContainerImageReference.FromReferenceString(p.PackageName), p.HelmReference)).ToList(); var useHelmValueYamlPathFromStep = deployment.Variables.GetFlag(SpecialVariables.Git.UseHelmValueYamlPathFromStep, false); return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageHelmReference, useHelmValueYamlPathFromStep); diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index 4703f05f78..12e73f24aa 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -6,11 +6,11 @@ namespace Calamari.ArgoCD.Conventions public class UpdateArgoCDAppDeploymentConfig { public GitCommitParameters CommitParameters { get; } - public IReadOnlyCollection PackageWithHelmReference { get; } + public IReadOnlyCollection PackageWithHelmReference { get; } public bool UseHelmValueYamlPathFromStep { get; } - public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List packageWithHelmReference, bool useHelmValueYamlPathFromStep) + public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List packageWithHelmReference, bool useHelmValueYamlPathFromStep) { CommitParameters = commitParameters; PackageWithHelmReference = packageWithHelmReference; @@ -18,5 +18,5 @@ public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, Lis } } - public record PackageAndHelmReference(ContainerImageReference ContainerReference, string? HelmReference); + public record ContainerImageReferenceAndHelmReference(ContainerImageReference ContainerReference, string? HelmReference); } diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 5d17b8ed43..d77a1e7c68 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -456,7 +456,7 @@ SourceUpdateResult ProcessHelmSourceUsingAnnotations(Application applicationFrom } /// Images that were updated - SourceUpdateResult ProcessHelmUpdateTargets( + public SourceUpdateResult ProcessHelmUpdateTargets( Application applicationFromYaml, RepositoryWrapper repository, UpdateArgoCDAppDeploymentConfig deploymentConfig, @@ -494,7 +494,7 @@ SourceUpdateResult ProcessHelmUpdateTargets( return new SourceUpdateResult(new HashSet(), string.Empty, []); } - SourceUpdateResult ProcessHelmSourceUsingStepVariables(Application applicationFromYaml, + public SourceUpdateResult ProcessHelmSourceUsingStepVariables(Application applicationFromYaml, ApplicationSourceWithMetadata sourceWithMetadata, Dictionary gitCredentials, RepositoryFactory repositoryFactory, @@ -515,12 +515,13 @@ SourceUpdateResult ProcessHelmSourceUsingStepVariables(Application applicationFr valuesFilesToUpdate.Add(implicitValuesFile); } - return UpdateImageTagsInValuesFiles(valuesFilesToUpdate, - deploymentConfig.PackageWithHelmReference, - defaultRegistry, - deploymentConfig, - repository, - sourceWithMetadata); + var replacer = new UpdateUsingStepVariables(log, fileSystem); + var result = replacer.UpdateImageTagsInValuesFiles(valuesFilesInHelmSource, + deploymentConfig.PackageWithHelmReference, + defaultRegistry, + repository.WorkingDirectory, + sourceWithMetadata); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFromYaml, @@ -535,79 +536,16 @@ SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFro var valuesFilesInHelmSource = extractor.GetValueFilesReferencedInRefSource(sourceWithMetadata); using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); - return UpdateImageTagsInValuesFiles(valuesFilesInHelmSource, + var replacer = new UpdateUsingStepVariables(log, fileSystem); + var result = replacer.UpdateImageTagsInValuesFiles(valuesFilesInHelmSource, deploymentConfig.PackageWithHelmReference, defaultRegistry, - deploymentConfig, - repository, + repository.WorkingDirectory, sourceWithMetadata); - } - - SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valuesFilesToUpdate, - IReadOnlyCollection stepReferencedContainers, - string defaultRegistry, - UpdateArgoCDAppDeploymentConfig deploymentConfig, - RepositoryWrapper repository, - ApplicationSourceWithMetadata sourceWithMetadata) - { - var filesUpdated = new HashSet(); - var imagesUpdated = new HashSet(); - foreach (var valuesFile in valuesFilesToUpdate) - { - var wasUpdated = false; - var repoRelativePath = Path.Combine(repository.WorkingDirectory, valuesFile); - log.Info($"Processing file at {valuesFile}."); - var fileContent = fileSystem.ReadFile(repoRelativePath); - var originalYamlParser = new HelmYamlParser(fileContent); // Parse and track the original yaml so that content can be read from it. - var flattenedYamlPathDictionary = HelmValuesEditor.CreateFlattenedDictionary(originalYamlParser); - foreach (var container in stepReferencedContainers.Where(c => c.HelmReference is not null)) - { - if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var valueToUpdate)) - { - if (IsUnstructuredText(valueToUpdate)) - { - HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, container.ContainerReference.Tag); - filesUpdated.Add(valuesFile); - imagesUpdated.Add(container.ContainerReference.ToString()); - } - else - { - var cir = ContainerImageReference.FromReferenceString(valueToUpdate, defaultRegistry); - var comparison = container.ContainerReference.CompareWith(cir); - if (comparison.MatchesImage()) - { - if (!comparison.TagMatch) - { - var newValue = cir.WithTag(container.ContainerReference.Tag); - fileContent = HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, newValue); - wasUpdated = true; - filesUpdated.Add(valuesFile); - imagesUpdated.Add(newValue); - } - } - else - { - log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); - } - } - - if (wasUpdated) - { - fileSystem.WriteAllText(repoRelativePath, fileContent); - } - } - } - - PushToRemote(repository, - GitReference.CreateFromString(sourceWithMetadata.Source.TargetRevision), //this is a hack, shouldn't need to KEEP re-converting it. - deploymentConfig.CommitParameters, - filesUpdated, - imagesUpdated); - } - + return new SourceUpdateResult(new HashSet(), string.Empty, []); } - + void LogHelmSourceConfigurationProblems(IReadOnlyCollection helmSourceConfigurationProblems) { foreach (var helmSourceConfigurationProblem in helmSourceConfigurationProblems) @@ -813,7 +751,5 @@ IEnumerable FindYamlFiles(string rootPath) var yamlFileGlob = "**/*.{yaml,yml}"; return fileSystem.EnumerateFilesWithGlob(rootPath, yamlFileGlob); } - - record SourceUpdateResult(HashSet ImagesUpdated, string CommitSha, List PatchedFiles); } } \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs b/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs index 02a2a69fe4..4cc5fced66 100644 --- a/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs +++ b/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs @@ -21,60 +21,64 @@ public UpdateUsingStepVariables(ILog log, ICalamariFileSystem fileSystem) this.fileSystem = fileSystem; } - SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valuesFilesToUpdate, IReadOnlyCollection stepReferencedContainers, string defaultRegistry, UpdateArgoCDAppDeploymentConfig deploymentConfig, RepositoryWrapper repository, ApplicationSourceWithMetadata sourceWithMetadata) + public SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valuesFilesToUpdate, + IReadOnlyCollection stepReferencedContainers, + string defaultRegistry, + string workingDirectory, + ApplicationSourceWithMetadata sourceWithMetadata) + { + var filesUpdated = new HashSet(); + var imagesUpdated = new HashSet(); + foreach (var valuesFile in valuesFilesToUpdate) { - var filesUpdated = new HashSet(); - var imagesUpdated = new HashSet(); - foreach (var valuesFile in valuesFilesToUpdate) + var wasUpdated = false; + var repoRelativePath = Path.Combine(workingDirectory, valuesFile); + log.Info($"Processing file at {valuesFile}."); + var fileContent = fileSystem.ReadFile(repoRelativePath); + var originalYamlParser = new HelmYamlParser(fileContent); // Parse and track the original yaml so that content can be read from it. + var flattenedYamlPathDictionary = HelmValuesEditor.CreateFlattenedDictionary(originalYamlParser); + foreach (var container in stepReferencedContainers.Where(c => c.HelmReference is not null)) { - var wasUpdated = false; - var repoRelativePath = Path.Combine(repository.WorkingDirectory, valuesFile); - log.Info($"Processing file at {valuesFile}."); - var fileContent = fileSystem.ReadFile(repoRelativePath); - var originalYamlParser = new HelmYamlParser(fileContent); // Parse and track the original yaml so that content can be read from it. - var flattenedYamlPathDictionary = HelmValuesEditor.CreateFlattenedDictionary(originalYamlParser); - foreach (var container in stepReferencedContainers.Where(c => c.HelmReference is not null)) + if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var valueToUpdate)) { - if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var valueToUpdate)) + if (IsUnstructuredText(valueToUpdate)) { - if (IsUnstructuredText(valueToUpdate)) - { - HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, container.ContainerReference.Tag); - filesUpdated.Add(valuesFile); - imagesUpdated.Add(container.ContainerReference.ToString()); - } - else + HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, container.ContainerReference.Tag); + filesUpdated.Add(valuesFile); + imagesUpdated.Add(container.ContainerReference.ToString()); + } + else + { + var cir = ContainerImageReference.FromReferenceString(valueToUpdate, defaultRegistry); + var comparison = container.ContainerReference.CompareWith(cir); + if (comparison.MatchesImage()) { - var cir = ContainerImageReference.FromReferenceString(valueToUpdate, defaultRegistry); - var comparison = container.ContainerReference.CompareWith(cir); - if (comparison.MatchesImage()) - { - if (!comparison.TagMatch) - { - var newValue = cir.WithTag(container.ContainerReference.Tag); - fileContent = HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, newValue); - wasUpdated = true; - filesUpdated.Add(valuesFile); - imagesUpdated.Add(newValue); - } - } - else + if (!comparison.TagMatch) { - log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); + var newValue = cir.WithTag(container.ContainerReference.Tag); + fileContent = HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, newValue); + wasUpdated = true; + filesUpdated.Add(valuesFile); + imagesUpdated.Add(newValue); } } - if (wasUpdated) + else { - fileSystem.WriteAllText(repoRelativePath, fileContent); + log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); } } + + if (wasUpdated) + { + fileSystem.WriteAllText(repoRelativePath, fileContent); + } } } - - return new SourceUpdateResult(new HashSet(), string.Empty, []); } - - + + return new SourceUpdateResult(new HashSet(), string.Empty, []); + } + static bool IsUnstructuredText(string content) { var lastColonIndex = content.LastIndexOf(':'); From 45bc92d25517741f9d30ee6b25a185770bc21731 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Thu, 19 Feb 2026 18:25:46 +1100 Subject: [PATCH 10/30] is this a better solution? --- .../ArgoCD/ContainerImageReplacerTests.cs | 57 ++++++------ .../ArgoCD/KustomizeImageReplacerTests.cs | 9 +- .../Calamari/ArgoCD/ContainerImageReplacer.cs | 5 +- ...ContainerImageReferenceAndHelmReference.cs | 6 ++ .../ArgoCD/Conventions/SourceUpdateResult.cs | 2 +- .../UpdateArgoCDAppDeploymentConfig.cs | 3 - .../UpdateArgoCDAppImagesInstallConvention.cs | 93 +++++++++++-------- .../ArgoCD/Helm/UpdateUsingStepVariables.cs | 89 ------------------ .../HelmValuesImageReplaceStepVariables.cs | 67 +++++++++++++ .../ArgoCD/IContainerImageReplacer.cs | 3 +- .../Calamari/ArgoCD/KustomizeImageReplacer.cs | 4 +- 11 files changed, 168 insertions(+), 170 deletions(-) create mode 100644 source/Calamari/ArgoCD/Conventions/ContainerImageReferenceAndHelmReference.cs delete mode 100644 source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs create mode 100644 source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs diff --git a/source/Calamari.Tests/ArgoCD/ContainerImageReplacerTests.cs b/source/Calamari.Tests/ArgoCD/ContainerImageReplacerTests.cs index 876433d897..99946d7b97 100644 --- a/source/Calamari.Tests/ArgoCD/ContainerImageReplacerTests.cs +++ b/source/Calamari.Tests/ArgoCD/ContainerImageReplacerTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Calamari.ArgoCD; +using Calamari.ArgoCD.Conventions; using Calamari.ArgoCD.Models; using FluentAssertions; using NUnit.Framework; @@ -11,15 +12,15 @@ namespace Calamari.Tests.ArgoCD.Commands.Conventions.UpdateArgoCdAppImages public class ContainerImageReplacerTests { readonly string DefaultContainerRegistry = "docker.io"; - readonly List imagesToUpdate; + readonly List imagesToUpdate; public ContainerImageReplacerTests() { - imagesToUpdate = new List + imagesToUpdate = new List { // We know this won't be null after parse - ContainerImageReference.FromReferenceString("nginx:1.25"), - ContainerImageReference.FromReferenceString("busybox:stable") + new(ContainerImageReference.FromReferenceString("nginx:1.25")), + new(ContainerImageReference.FromReferenceString("busybox:stable")) }; } @@ -37,7 +38,7 @@ public void UpdateImages_WithNoNewImages_ReturnsSameYaml() var imageReplacer = new ContainerImageReplacer(inputYaml, DefaultContainerRegistry); - var result = imageReplacer.UpdateImages(new List()); + var result = imageReplacer.UpdateImages(new List()); result.UpdatedContents.Should().Be(inputYaml); result.UpdatedImageReferences.Should().BeEmpty(); @@ -48,7 +49,7 @@ public void UpdateImages_WithEmptyYaml_ReturnsEmpty() { var imageReplacer = new ContainerImageReplacer(string.Empty, DefaultContainerRegistry); - var result = imageReplacer.UpdateImages(new List()); + var result = imageReplacer.UpdateImages(new List()); result.UpdatedContents.Should().BeEmpty(); result.UpdatedImageReferences.Should().BeEmpty(); @@ -63,7 +64,7 @@ public void UpdateImages_WithInvalidDocuments_IgnoresDocuments() - random: stuff "; var imageReplacer = new ContainerImageReplacer(invalidYaml, DefaultContainerRegistry); - var result = imageReplacer.UpdateImages(new List()); + var result = imageReplacer.UpdateImages(new List()); result.UpdatedContents.Should().NotBeNull(); result.UpdatedContents.Should().Be(invalidYaml); @@ -84,7 +85,7 @@ public void UpdateImages_WithResourcesWithoutImages_LeavesYamlUnchanged() var imageReplacer = new ContainerImageReplacer(yamlWithoutImages, DefaultContainerRegistry); - var result = imageReplacer.UpdateImages(new List { ContainerImageReference.FromReferenceString("nginx:1.25") }); + var result = imageReplacer.UpdateImages(new List { new(ContainerImageReference.FromReferenceString("nginx:1.25")) }); result.UpdatedContents.Should().Be(yamlWithoutImages); result.UpdatedImageReferences.Should().BeEmpty(); @@ -121,7 +122,7 @@ public void UpdateImages_WithArgoAppManifest_LeavesYamlUnchanged() var imageReplacer = new ContainerImageReplacer(argoApp, DefaultContainerRegistry); - var result = imageReplacer.UpdateImages(new List { ContainerImageReference.FromReferenceString("nginx:1.25") }); + var result = imageReplacer.UpdateImages(new List { new(ContainerImageReference.FromReferenceString("nginx:1.25")) }); result.UpdatedContents.Should().Be(argoApp); result.UpdatedImageReferences.Should().BeEmpty(); @@ -141,7 +142,7 @@ public void UpdateImages_WithYamlComments_PreservesComments() "; var imageReplacer = new ContainerImageReplacer(yamlWithComments, DefaultContainerRegistry); - var result = imageReplacer.UpdateImages(new List { ContainerImageReference.FromReferenceString("nginx:1.25") }); + var result = imageReplacer.UpdateImages(new List { new(ContainerImageReference.FromReferenceString("nginx:1.25")) }); result.UpdatedContents.Should().Be(yamlWithComments); } @@ -163,9 +164,9 @@ public void UpdateImages_WithQuotedReference_PreservesQuotes() image: ""nginx:1.25"""; var imageReplacer = new ContainerImageReplacer(inputYaml, DefaultContainerRegistry); - var updatedImage = new List + var updatedImage = new List { - ContainerImageReference.FromReferenceString("nginx:1.25") + new(ContainerImageReference.FromReferenceString("nginx:1.25")) }; var result = imageReplacer.UpdateImages(updatedImage); @@ -201,9 +202,9 @@ public void DoesNotUpdateComments() image: nginx:1.25"; var imageReplacer = new ContainerImageReplacer(inputYaml, DefaultContainerRegistry); - var updatedImage = new List + var updatedImage = new List { - ContainerImageReference.FromReferenceString("nginx:1.25") + new(ContainerImageReference.FromReferenceString("nginx:1.25")) }; var result = imageReplacer.UpdateImages(updatedImage); @@ -331,9 +332,9 @@ public void UpdateImages_WithPodWithUpdatesToMultipleInstancesOfSameImage_Return "; var imageReplacer = new ContainerImageReplacer(inputYaml, DefaultContainerRegistry); - var updatedImage = new List + var updatedImage = new List { - ContainerImageReference.FromReferenceString("nginx:1.25"), + new(ContainerImageReference.FromReferenceString("nginx:1.25")), }; var result = imageReplacer.UpdateImages(updatedImage); @@ -663,7 +664,7 @@ public void UpdateImages_WithNoChangeToTag_ReturnsNoChanges() "; var imageReplacer = new ContainerImageReplacer(inputYaml, DefaultContainerRegistry); - var result = imageReplacer.UpdateImages(new List { ContainerImageReference.FromReferenceString("nginx:1.19") }); + var result = imageReplacer.UpdateImages(new List { new(ContainerImageReference.FromReferenceString("nginx:1.19")) }); result.UpdatedContents.Should().NotBeNull(); result.UpdatedContents.Should().Be(inputYaml); @@ -707,11 +708,11 @@ public void UpdateImages_WithPodUpdatesUsingCustomRegistry_ReturnsUpdatedYaml() command: [""echo"", ""Init container added""] "; - List customRegistryImagesToUpdate = new List + var customRegistryImagesToUpdate = new List { // We know this won't be null after parse - ContainerImageReference.FromReferenceString("my-custom.io/nginx:1.25"), - ContainerImageReference.FromReferenceString("my-custom.io/busybox:stable") + new(ContainerImageReference.FromReferenceString("my-custom.io/nginx:1.25")), + new(ContainerImageReference.FromReferenceString("my-custom.io/busybox:stable")) }; var imageReplacer = new ContainerImageReplacer(inputYaml, "my-custom.io"); @@ -761,11 +762,11 @@ public void UpdateImages_WithPodUpdatesUsingCustomRegistry_OnlyUpdatesCustomMatc command: [""echo"", ""Init container added""] "; - List customRegistryImagesToUpdate = new List + var customRegistryImagesToUpdate = new List { // We know this won't be null after parse - ContainerImageReference.FromReferenceString("docker.io/nginx:1.25"), //This container should be ignored because it has a fully qualified registry that doesn't match the custom - ContainerImageReference.FromReferenceString("my-custom.io/busybox:stable") + new(ContainerImageReference.FromReferenceString("docker.io/nginx:1.25")), //This container should be ignored because it has a fully qualified registry that doesn't match the custom + new(ContainerImageReference.FromReferenceString("my-custom.io/busybox:stable")) }; var imageReplacer = new ContainerImageReplacer(inputYaml, "my-custom.io"); @@ -828,7 +829,7 @@ public void WhyDoesntGuestBookwork() var imageReplacer = new ContainerImageReplacer(inputYaml, "my-custom.io"); - var result = imageReplacer.UpdateImages(new List { ContainerImageReference.FromReferenceString("quay.io/argoprojlabs/argocd-e2e-container:0.3") }); + var result = imageReplacer.UpdateImages(new List { new(ContainerImageReference.FromReferenceString("quay.io/argoprojlabs/argocd-e2e-container:0.3")) }); result.UpdatedContents.Should().Be(expectedOutput); } @@ -845,8 +846,8 @@ public void ReplacesTagIfCaseDoesNotMatch() - image: nginx:currentversion"; var imageReplacer = new ContainerImageReplacer(inputYaml, DefaultContainerRegistry); - var upperCaseContainer = ContainerImageReference.FromReferenceString("nginx:CurrentVersion"); - var result = imageReplacer.UpdateImages(new List{upperCaseContainer}); + var upperCaseContainer = new ContainerImageReferenceAndHelmReference(ContainerImageReference.FromReferenceString("nginx:CurrentVersion")); + var result = imageReplacer.UpdateImages(new List{upperCaseContainer}); const string expectedOutput = @" apiVersion: apps/v1 @@ -874,8 +875,8 @@ public void ReplacerWillMatchImageNameInsensitivelyAndReplaceWithLowerCase() - image: NGiNX:currentversion"; var imageReplacer = new ContainerImageReplacer(inputYaml, DefaultContainerRegistry); - var upperCaseContainer = ContainerImageReference.FromReferenceString("nginx:CurrentVersion"); - var result = imageReplacer.UpdateImages(new List{upperCaseContainer}); + var upperCaseContainer = new ContainerImageReferenceAndHelmReference(ContainerImageReference.FromReferenceString("nginx:CurrentVersion")); + var result = imageReplacer.UpdateImages(new List{upperCaseContainer}); const string expectedOutput = @" apiVersion: apps/v1 diff --git a/source/Calamari.Tests/ArgoCD/KustomizeImageReplacerTests.cs b/source/Calamari.Tests/ArgoCD/KustomizeImageReplacerTests.cs index 8aaaa55d6b..8f0dbe01fe 100644 --- a/source/Calamari.Tests/ArgoCD/KustomizeImageReplacerTests.cs +++ b/source/Calamari.Tests/ArgoCD/KustomizeImageReplacerTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Calamari.ArgoCD; +using Calamari.ArgoCD.Conventions; using Calamari.ArgoCD.Models; using Calamari.Common.Plumbing.Logging; using Calamari.Testing.Helpers; @@ -12,11 +13,11 @@ namespace Calamari.Tests.ArgoCD { public class KustomizeImageReplacerTests { - readonly List imagesToUpdate = new List() + readonly List imagesToUpdate = new List() { // We know this won't be null after parse - ContainerImageReference.FromReferenceString("nginx:1.25", ArgoCDConstants.DefaultContainerRegistry), - ContainerImageReference.FromReferenceString("busybox:stable", "my-registry.com"), + new (ContainerImageReference.FromReferenceString("nginx:1.25", ArgoCDConstants.DefaultContainerRegistry)), + new (ContainerImageReference.FromReferenceString("busybox:stable", "my-registry.com")), }; ILog log = new InMemoryLog(); @@ -447,7 +448,7 @@ public void UpdateImages_FullKustomizationFile_ShouldOnlyChangeTheImagesNode() var imageReplacer = new KustomizeImageReplacer(inputYaml, ArgoCDConstants.DefaultContainerRegistry, log); - var result = imageReplacer.UpdateImages(imagesToUpdate.Append(ContainerImageReference.FromReferenceString("monopole:100")).ToList()); + var result = imageReplacer.UpdateImages(imagesToUpdate.Append(new(ContainerImageReference.FromReferenceString("monopole:100"))).ToList()); result.UpdatedContents.Should().NotBeNull(); result.UpdatedContents.Should().Be(expectedYaml); diff --git a/source/Calamari/ArgoCD/ContainerImageReplacer.cs b/source/Calamari/ArgoCD/ContainerImageReplacer.cs index f693b5d3dd..0c33a9f5d5 100644 --- a/source/Calamari/ArgoCD/ContainerImageReplacer.cs +++ b/source/Calamari/ArgoCD/ContainerImageReplacer.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Calamari.ArgoCD.Conventions; using Calamari.ArgoCD.Models; using k8s; using k8s.Models; @@ -22,7 +23,7 @@ public ContainerImageReplacer(string yamlContent, string defaultRegistry) this.defaultRegistry = defaultRegistry; } - public ImageReplacementResult UpdateImages(List imagesToUpdate) + public ImageReplacementResult UpdateImages(IReadOnlyCollection imagesToUpdate) { if (string.IsNullOrWhiteSpace(yamlContent)) { @@ -72,7 +73,7 @@ public ImageReplacementResult UpdateImages(List imagesT } var resource = resources[0]; - var (updatedDocument, changes) = UpdateImagesInKubernetesResource(document, resource, imagesToUpdate); + var (updatedDocument, changes) = UpdateImagesInKubernetesResource(document, resource, imagesToUpdate.Select(i => i.ContainerReference).ToList()); imageReplacements.UnionWith(changes); // NOTE: We don't need to check if a change has been made or not, if it hasn't, the final document will remain unchanged. updatedDocuments.Add(updatedDocument); diff --git a/source/Calamari/ArgoCD/Conventions/ContainerImageReferenceAndHelmReference.cs b/source/Calamari/ArgoCD/Conventions/ContainerImageReferenceAndHelmReference.cs new file mode 100644 index 0000000000..fab1f05805 --- /dev/null +++ b/source/Calamari/ArgoCD/Conventions/ContainerImageReferenceAndHelmReference.cs @@ -0,0 +1,6 @@ +using System; +using Calamari.ArgoCD.Models; + +namespace Calamari.ArgoCD.Conventions; + +public record ContainerImageReferenceAndHelmReference(ContainerImageReference ContainerReference, string? HelmReference = null); \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Conventions/SourceUpdateResult.cs b/source/Calamari/ArgoCD/Conventions/SourceUpdateResult.cs index 46cd017446..9f686a1cfe 100644 --- a/source/Calamari/ArgoCD/Conventions/SourceUpdateResult.cs +++ b/source/Calamari/ArgoCD/Conventions/SourceUpdateResult.cs @@ -3,5 +3,5 @@ using System.Collections.Generic; namespace Calamari.ArgoCD.Conventions; - + public record SourceUpdateResult(HashSet ImagesUpdated, string CommitSha, List PatchedFiles); \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index 12e73f24aa..2479769487 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Calamari.ArgoCD.Models; namespace Calamari.ArgoCD.Conventions { @@ -17,6 +16,4 @@ public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, Lis UseHelmValueYamlPathFromStep = useHelmValueYamlPathFromStep; } } - - public record ContainerImageReferenceAndHelmReference(ContainerImageReference ContainerReference, string? HelmReference); } diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index d77a1e7c68..dbf473baa5 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -261,7 +261,7 @@ SourceUpdateResult ProcessKustomize( { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages, patchedFiles) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList()); + var (updatedFiles, updatedImages, patchedFiles) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.PackageWithHelmReference); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -348,7 +348,7 @@ SourceUpdateResult ProcessDirectory( { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages, patchedFiles) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList()); + var (updatedFiles, updatedImages, patchedFiles) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.PackageWithHelmReference); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -403,14 +403,13 @@ SourceUpdateResult ProcessHelm( applicationSource); } - return ProcessHelmSourceUsingStepVariables(applicationFromYaml, - sourceWithMetadata, - gitCredentials, - repositoryFactory, - deploymentConfig, - defaultRegistry, - gateway, - applicationSource); + return ProcessHelmUpdateTargetsWithStepVariables(applicationFromYaml, + gitCredentials, + repositoryFactory, + deploymentConfig, + sourceWithMetadata, + defaultRegistry, + gateway); } SourceUpdateResult ProcessHelmSourceUsingAnnotations(Application applicationFromYaml, @@ -494,35 +493,57 @@ public SourceUpdateResult ProcessHelmUpdateTargets( return new SourceUpdateResult(new HashSet(), string.Empty, []); } - public SourceUpdateResult ProcessHelmSourceUsingStepVariables(Application applicationFromYaml, - ApplicationSourceWithMetadata sourceWithMetadata, - Dictionary gitCredentials, - RepositoryFactory repositoryFactory, - UpdateArgoCDAppDeploymentConfig deploymentConfig, - string defaultRegistry, - ArgoCDGatewayDto gateway, - ApplicationSource applicationSource) + + public SourceUpdateResult ProcessHelmUpdateTargetsWithStepVariables( + Application applicationFromYaml, + Dictionary gitCredentials, + RepositoryFactory repositoryFactory, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + ApplicationSourceWithMetadata sourceWithMetadata, + string defaultRegistry, + ArgoCDGatewayDto gateway) { var extractor = new HelmValuesFileExtractor(applicationFromYaml, defaultRegistry); var valuesFilesInHelmSource = extractor.GetInlineValuesFilesReferencedByHelmSource(sourceWithMetadata); //Add the implicit value file if needed - using var repository = CreateRepository(gitCredentials, applicationSource, repositoryFactory); + using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); var implicitValuesFile = HelmDiscovery.TryFindValuesFile(fileSystem, sourceWithMetadata.Source.Path!); - var valuesFilesToUpdate = valuesFilesInHelmSource.ToList(); + var filesToUpdate = valuesFilesInHelmSource.ToList(); if (implicitValuesFile != null && !valuesFilesInHelmSource.Contains(implicitValuesFile)) { - valuesFilesToUpdate.Add(implicitValuesFile); + filesToUpdate.Add(implicitValuesFile); } - var replacer = new UpdateUsingStepVariables(log, fileSystem); - var result = replacer.UpdateImageTagsInValuesFiles(valuesFilesInHelmSource, - deploymentConfig.PackageWithHelmReference, - defaultRegistry, - repository.WorkingDirectory, - sourceWithMetadata); + return UpdateHelmValuesFiles(filesToUpdate.ToHashSet(), defaultRegistry, repository, deploymentConfig, gateway, sourceWithMetadata, applicationFromYaml); + } + + SourceUpdateResult UpdateHelmValuesFiles(HashSet filesToUpdate, string defaultRegistry, RepositoryWrapper repository, UpdateArgoCDAppDeploymentConfig deploymentConfig, ArgoCDGatewayDto gateway, ApplicationSourceWithMetadata sourceWithMetadata, Application applicationFromYaml) + { + Func imageReplacerFactory = yaml => new HelmValuesImageReplaceStepVariables(yaml, defaultRegistry, log); + log.Verbose($"Found {filesToUpdate.Count} yaml files to process"); + + var (updatedFiles, updatedImages, patchedFiles) = Update(repository.WorkingDirectory, deploymentConfig.PackageWithHelmReference, filesToUpdate.ToHashSet(), imageReplacerFactory); + if (updatedImages.Count > 0) + { + var pushResult = PushToRemote(repository, + GitReference.CreateFromString(sourceWithMetadata.Source.TargetRevision), + deploymentConfig.CommitParameters, + updatedFiles, + updatedImages); + + if (pushResult is not null) + { + outputVariablesWriter.WritePushResultOutput(gateway.Name, + applicationFromYaml.Metadata.Name, + sourceWithMetadata.Index, + pushResult); + return new SourceUpdateResult(updatedImages, pushResult.CommitSha, patchedFiles); + } + } return new SourceUpdateResult(new HashSet(), string.Empty, []); } + SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFromYaml, ApplicationSourceWithMetadata sourceWithMetadata, @@ -536,16 +557,9 @@ SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFro var valuesFilesInHelmSource = extractor.GetValueFilesReferencedInRefSource(sourceWithMetadata); using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); - var replacer = new UpdateUsingStepVariables(log, fileSystem); - var result = replacer.UpdateImageTagsInValuesFiles(valuesFilesInHelmSource, - deploymentConfig.PackageWithHelmReference, - defaultRegistry, - repository.WorkingDirectory, - sourceWithMetadata); - - return new SourceUpdateResult(new HashSet(), string.Empty, []); + return UpdateHelmValuesFiles(valuesFilesInHelmSource.ToHashSet(), defaultRegistry, repository, deploymentConfig, gateway, sourceWithMetadata, applicationFromYaml); } - + void LogHelmSourceConfigurationProblems(IReadOnlyCollection helmSourceConfigurationProblems) { foreach (var helmSourceConfigurationProblem in helmSourceConfigurationProblems) @@ -618,7 +632,7 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede string rootPath, string subFolder, string defaultRegistry, - List imagesToUpdate) + IReadOnlyCollection imagesToUpdate) { var absSubFolder = Path.Combine(rootPath, subFolder); @@ -633,7 +647,7 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede string rootPath, string subFolder, string defaultRegistry, - List imagesToUpdate) + IReadOnlyCollection imagesToUpdate) { var absSubFolder = Path.Combine(rootPath, subFolder); @@ -653,7 +667,7 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede return ([], [], []); } - (HashSet, HashSet, List) Update(string rootPath, List imagesToUpdate, HashSet filesToUpdate, Func imageReplacerFactory) + (HashSet, HashSet, List) Update(string rootPath, IReadOnlyCollection imagesToUpdate, HashSet filesToUpdate, Func imageReplacerFactory) { var updatedFiles = new HashSet(); var updatedImages = new HashSet(); @@ -688,7 +702,6 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede return (updatedFiles, updatedImages, jsonPatches); } - HelmRefUpdatedResult UpdateHelmImageValues( string rootPath, diff --git a/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs b/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs deleted file mode 100644 index 4cc5fced66..0000000000 --- a/source/Calamari/ArgoCD/Helm/UpdateUsingStepVariables.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Calamari.ArgoCD.Conventions; -using Calamari.ArgoCD.Domain; -using Calamari.ArgoCD.Git; -using Calamari.ArgoCD.Models; -using Calamari.Common.Plumbing.FileSystem; -using Calamari.Common.Plumbing.Logging; - -namespace Calamari.ArgoCD.Helm; - -public class UpdateUsingStepVariables -{ - readonly ILog log; - readonly ICalamariFileSystem fileSystem; - - public UpdateUsingStepVariables(ILog log, ICalamariFileSystem fileSystem) - { - this.log = log; - this.fileSystem = fileSystem; - } - - public SourceUpdateResult UpdateImageTagsInValuesFiles(IReadOnlyCollection valuesFilesToUpdate, - IReadOnlyCollection stepReferencedContainers, - string defaultRegistry, - string workingDirectory, - ApplicationSourceWithMetadata sourceWithMetadata) - { - var filesUpdated = new HashSet(); - var imagesUpdated = new HashSet(); - foreach (var valuesFile in valuesFilesToUpdate) - { - var wasUpdated = false; - var repoRelativePath = Path.Combine(workingDirectory, valuesFile); - log.Info($"Processing file at {valuesFile}."); - var fileContent = fileSystem.ReadFile(repoRelativePath); - var originalYamlParser = new HelmYamlParser(fileContent); // Parse and track the original yaml so that content can be read from it. - var flattenedYamlPathDictionary = HelmValuesEditor.CreateFlattenedDictionary(originalYamlParser); - foreach (var container in stepReferencedContainers.Where(c => c.HelmReference is not null)) - { - if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var valueToUpdate)) - { - if (IsUnstructuredText(valueToUpdate)) - { - HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, container.ContainerReference.Tag); - filesUpdated.Add(valuesFile); - imagesUpdated.Add(container.ContainerReference.ToString()); - } - else - { - var cir = ContainerImageReference.FromReferenceString(valueToUpdate, defaultRegistry); - var comparison = container.ContainerReference.CompareWith(cir); - if (comparison.MatchesImage()) - { - if (!comparison.TagMatch) - { - var newValue = cir.WithTag(container.ContainerReference.Tag); - fileContent = HelmValuesEditor.UpdateNodeValue(fileContent, container.HelmReference!, newValue); - wasUpdated = true; - filesUpdated.Add(valuesFile); - imagesUpdated.Add(newValue); - } - } - else - { - log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); - } - } - - if (wasUpdated) - { - fileSystem.WriteAllText(repoRelativePath, fileContent); - } - } - } - } - - return new SourceUpdateResult(new HashSet(), string.Empty, []); - } - - static bool IsUnstructuredText(string content) - { - var lastColonIndex = content.LastIndexOf(':'); - var lastSlashIndex = content.LastIndexOf('/'); - - return lastColonIndex == -1 && lastSlashIndex == -1; - } -} \ No newline at end of file diff --git a/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs b/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs new file mode 100644 index 0000000000..949ae38a8c --- /dev/null +++ b/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Linq; +using Calamari.ArgoCD.Conventions; +using Calamari.ArgoCD.Helm; +using Calamari.ArgoCD.Models; +using Calamari.Common.Plumbing.Logging; + +namespace Calamari.ArgoCD; + +public class HelmValuesImageReplaceStepVariables : IContainerImageReplacer +{ + readonly string yamlContent; + readonly string defaultRegistry; + readonly ILog log; + + public HelmValuesImageReplaceStepVariables(string yamlContent, string defaultRegistry, ILog log) + { + this.yamlContent = yamlContent; + this.defaultRegistry = defaultRegistry; + this.log = log; + } + + public ImageReplacementResult UpdateImages(IReadOnlyCollection imagesToUpdate) + { + var imagesUpdated = new HashSet(); + var updatedYaml = yamlContent; + var originalYamlParser = new HelmYamlParser(yamlContent); // Parse and track the original yaml so that content can be read from it. + var flattenedYamlPathDictionary = HelmValuesEditor.CreateFlattenedDictionary(originalYamlParser); + foreach (var container in imagesToUpdate.Where(c => c.HelmReference is not null)) + { + if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var valueToUpdate)) + { + if (IsUnstructuredText(valueToUpdate)) + { + HelmValuesEditor.UpdateNodeValue(updatedYaml, container.HelmReference!, container.ContainerReference.Tag); + } + else + { + var cir = ContainerImageReference.FromReferenceString(valueToUpdate, defaultRegistry); + var comparison = container.ContainerReference.CompareWith(cir); + if (comparison.MatchesImage()) + { + if (!comparison.TagMatch) + { + var newValue = cir.WithTag(container.ContainerReference.Tag); + updatedYaml = HelmValuesEditor.UpdateNodeValue(updatedYaml, container.HelmReference!, newValue); + imagesUpdated.Add(newValue); + } + } + else + { + log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); + } + } + } + } + return new ImageReplacementResult(updatedYaml, imagesUpdated); + } + + bool IsUnstructuredText(string content) + { + var lastColonIndex = content.LastIndexOf(':'); + var lastSlashIndex = content.LastIndexOf('/'); + + return lastColonIndex == -1 && lastSlashIndex == -1; + } +} \ No newline at end of file diff --git a/source/Calamari/ArgoCD/IContainerImageReplacer.cs b/source/Calamari/ArgoCD/IContainerImageReplacer.cs index bef8616697..0443e23741 100644 --- a/source/Calamari/ArgoCD/IContainerImageReplacer.cs +++ b/source/Calamari/ArgoCD/IContainerImageReplacer.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using Calamari.ArgoCD.Conventions; using Calamari.ArgoCD.Models; namespace Calamari.ArgoCD { public interface IContainerImageReplacer { - ImageReplacementResult UpdateImages(List imagesToUpdate); + ImageReplacementResult UpdateImages(IReadOnlyCollection imagesToUpdate); } } \ No newline at end of file diff --git a/source/Calamari/ArgoCD/KustomizeImageReplacer.cs b/source/Calamari/ArgoCD/KustomizeImageReplacer.cs index 9d253a3fbf..daa4dc9a9e 100644 --- a/source/Calamari/ArgoCD/KustomizeImageReplacer.cs +++ b/source/Calamari/ArgoCD/KustomizeImageReplacer.cs @@ -34,7 +34,7 @@ public KustomizeImageReplacer(string yamlContent, string defaultRegistry, ILog l ImageReplacementResult NoChangeResult => new ImageReplacementResult(yamlContent, new HashSet()); - public ImageReplacementResult UpdateImages(List imagesToUpdate) + public ImageReplacementResult UpdateImages(IReadOnlyCollection imagesToUpdate) { if (string.IsNullOrWhiteSpace(yamlContent)) { @@ -81,7 +81,7 @@ public ImageReplacementResult UpdateImages(List imagesT var replacementsMade = new HashSet(); foreach (var imageNode in imagesSequenceNode.OfType()) { - var matchedUpdate = GetMatchedContainerToUpdate(imagesToUpdate, imageNode); + var matchedUpdate = GetMatchedContainerToUpdate(imagesToUpdate.Select(i => i.ContainerReference).ToList(), imageNode); //no match, nothing to do if (matchedUpdate is null) { From 830ed42d78ba36199d93e0783d54a90f3ba01e4c Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Thu, 19 Feb 2026 18:35:12 +1100 Subject: [PATCH 11/30] refactor is working --- .../Conventions/UpdateArgoCDAppImagesInstallConvention.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index dbf473baa5..5b816571d4 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -555,9 +555,11 @@ SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFro { var extractor = new HelmValuesFileExtractor(applicationFromYaml, defaultRegistry); var valuesFilesInHelmSource = extractor.GetValueFilesReferencedInRefSource(sourceWithMetadata); + //these files are relative to repo - so need to path them wrt repository once checked out. using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); - return UpdateHelmValuesFiles(valuesFilesInHelmSource.ToHashSet(), defaultRegistry, repository, deploymentConfig, gateway, sourceWithMetadata, applicationFromYaml); + var absFilePaths = valuesFilesInHelmSource.Select(f => Path.Combine(repository.WorkingDirectory, f)); + return UpdateHelmValuesFiles(absFilePaths.ToHashSet(), defaultRegistry, repository, deploymentConfig, gateway, sourceWithMetadata, applicationFromYaml); } void LogHelmSourceConfigurationProblems(IReadOnlyCollection helmSourceConfigurationProblems) From b3b520e7f46bec8039d9b2fd434d7e6a22d47a3b Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Fri, 20 Feb 2026 09:51:07 +1100 Subject: [PATCH 12/30] basic test in for helm source --- ...goCDAppImagesInstallConventionHelmTests.cs | 82 ++++++++++++++++++- .../UpdateArgoCDAppImagesInstallConvention.cs | 1 + .../ArgoCD/VariablesExtensionMethods.cs | 1 - 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs index c9f30278b3..f7f32a85ba 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs @@ -1374,7 +1374,7 @@ public void DirectorySource_ImageMatches_ReportsDeploymentWithNonEmptyCommitSha( [Test] - public void CanUpdateHelmSourceUsingStepBasedVariables() + public void CanUpdateRefSourceUsingStepBasedVariables() { // Arrange var updater = CreateConvention(); @@ -1458,6 +1458,86 @@ public void CanUpdateHelmSourceUsingStepBasedVariables() var clonedRepoPath = RepositoryHelpers.CloneOrigin(tempDirectory, OriginPath, argoCDBranchName); AssertFileContents(clonedRepoPath, existingYamlFile, updatedYamlContent); } + + + [Test] + public void CanUpdateHelmSourceUsingStepBasedVariables() + { + // Arrange + var updater = CreateConvention(); + var variables = new CalamariVariables + { + [PackageVariables.IndexedImage("nginx")] = "docker.io/nginx:1.27.1", // NOTE the lack of "index" + [PackageVariables.IndexedPackagePurpose("nginx")] = "DockerImageReference", + [PackageVariables.HelmValueYamlPath("nginx")] = "image.nginx", //NOTE: no .Values to start, and no leading . + [ProjectVariables.Slug] = ProjectSlug, + [DeploymentEnvironment.Slug] = EnvironmentSlug, + [SpecialVariables.Git.UseHelmValueYamlPathFromStep] = "true", + }; + var runningDeployment = new RunningDeployment(null, variables); + runningDeployment.CurrentDirectoryProvider = DeploymentWorkingDirectory.StagingDirectory; + runningDeployment.StagingDirectory = tempDirectory; + + + var existingYamlFile = "subFolder/values.yaml"; + var filesInRepo = new (string, string)[] + { + ( + existingYamlFile, + @" +image: + nginx: index.docker.io/nginx:1.0 +containerPort: 8080 +service: + type: LoadBalancer +" + ) + }; + originRepo.AddFilesToBranch(argoCDBranchName, filesInRepo); + + var argoCDAppWithHelmSource = new ArgoCDApplicationBuilder() + .WithName("App1") + .WithAnnotations(new Dictionary() + { + [ArgoCDConstants.Annotations.OctopusProjectAnnotationKey(new ApplicationSourceName("helm-source"))] = ProjectSlug, + [ArgoCDConstants.Annotations.OctopusEnvironmentAnnotationKey(new ApplicationSourceName("helm-source"))] = EnvironmentSlug, + }) + .WithSource(new ApplicationSource + { + TargetRevision = ArgoCDBranchFriendlyName, + OriginalRepoUrl = OriginPath, + Path = "", + Helm = new HelmConfig + { + ValueFiles = new List() + { + "subFolder/values.yaml" + } + }, + Name = "helm-source", + }, + SourceTypeConstants.Helm) + .Build(); + + argoCdApplicationManifestParser.ParseManifest(Arg.Any()) + .Returns(argoCDAppWithHelmSource); + // Act + updater.Install(runningDeployment); + + //Assert + const string updatedYamlContent = + @" +image: + nginx: index.docker.io/nginx:1.27.1 +containerPort: 8080 +service: + type: LoadBalancer +"; + + var clonedRepoPath = RepositoryHelpers.CloneOrigin(tempDirectory, OriginPath, argoCDBranchName); + AssertFileContents(clonedRepoPath, existingYamlFile, updatedYamlContent); + } + void AssertFileContents(string clonedRepoPath, string relativeFilePath, string expectedContent) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 5b816571d4..6a0675d264 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -515,6 +515,7 @@ public SourceUpdateResult ProcessHelmUpdateTargetsWithStepVariables( filesToUpdate.Add(implicitValuesFile); } + filesToUpdate = filesToUpdate.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); return UpdateHelmValuesFiles(filesToUpdate.ToHashSet(), defaultRegistry, repository, deploymentConfig, gateway, sourceWithMetadata, applicationFromYaml); } diff --git a/source/Calamari/ArgoCD/VariablesExtensionMethods.cs b/source/Calamari/ArgoCD/VariablesExtensionMethods.cs index 07eab60416..2320cb3628 100644 --- a/source/Calamari/ArgoCD/VariablesExtensionMethods.cs +++ b/source/Calamari/ArgoCD/VariablesExtensionMethods.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Calamari.ArgoCD.Conventions; using Calamari.ArgoCD.Dtos; using Calamari.ArgoCD.Models; using Calamari.Common.Plumbing.Variables; From f7ed7750d39526083634f64db86aa1b20ebf231d Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Mon, 23 Feb 2026 14:41:40 +1100 Subject: [PATCH 13/30] reformat --- .../UpdateArgoCDAppImagesInstallConvention.cs | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 463bcf401f..d89c54c22d 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -478,9 +478,9 @@ SourceUpdateResult ProcessHelmUpdateTargets( if (updatedImages.Count > 0) { var patchedFiles = results.Select(r => new FilePathContent( - // Replace \ with / so that Calamari running on windows doesn't cause issues when we send back to server - r.RelativeFilepath.Replace('\\', '/'), - r.JsonPatch is not null ? JsonSerializer.Serialize(r.JsonPatch) : null)) + // Replace \ with / so that Calamari running on windows doesn't cause issues when we send back to server + r.RelativeFilepath.Replace('\\', '/'), + r.JsonPatch is not null ? JsonSerializer.Serialize(r.JsonPatch) : null)) .ToList(); var pushResult = PushToRemote(repository, @@ -502,8 +502,7 @@ SourceUpdateResult ProcessHelmUpdateTargets( return new SourceUpdateResult(new HashSet(), string.Empty, []); } - - public SourceUpdateResult ProcessHelmUpdateTargetsWithStepVariables( + SourceUpdateResult ProcessHelmUpdateTargetsWithStepVariables( Application applicationFromYaml, Dictionary gitCredentials, RepositoryFactory repositoryFactory, @@ -525,15 +524,27 @@ public SourceUpdateResult ProcessHelmUpdateTargetsWithStepVariables( } filesToUpdate = filesToUpdate.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); - return UpdateHelmValuesFiles(filesToUpdate.ToHashSet(), defaultRegistry, repository, deploymentConfig, gateway, sourceWithMetadata, applicationFromYaml); + return ProcessHelmValuesFiles(filesToUpdate.ToHashSet(), + defaultRegistry, + repository, + deploymentConfig, + gateway, + sourceWithMetadata, + applicationFromYaml); } - - SourceUpdateResult UpdateHelmValuesFiles(HashSet filesToUpdate, string defaultRegistry, RepositoryWrapper repository, UpdateArgoCDAppDeploymentConfig deploymentConfig, ArgoCDGatewayDto gateway, ApplicationSourceWithMetadata sourceWithMetadata, Application applicationFromYaml) + + SourceUpdateResult ProcessHelmValuesFiles(HashSet filesToUpdate, + string defaultRegistry, + RepositoryWrapper repository, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + ArgoCDGatewayDto gateway, + ApplicationSourceWithMetadata sourceWithMetadata, + Application applicationFromYaml) { Func imageReplacerFactory = yaml => new HelmValuesImageReplaceStepVariables(yaml, defaultRegistry, log); log.Verbose($"Found {filesToUpdate.Count} yaml files to process"); - var (updatedFiles, updatedImages, patchedFiles) = Update(repository.WorkingDirectory, deploymentConfig.PackageWithHelmReference, filesToUpdate.ToHashSet(), imageReplacerFactory); + var (updatedFiles, updatedImages, patchedFiles) = Update(repository.WorkingDirectory, deploymentConfig.PackageWithHelmReference, filesToUpdate.ToHashSet(), imageReplacerFactory); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -551,9 +562,9 @@ SourceUpdateResult UpdateHelmValuesFiles(HashSet filesToUpdate, string d return new SourceUpdateResult(updatedImages, pushResult.CommitSha, patchedFiles); } } + return new SourceUpdateResult(new HashSet(), string.Empty, []); } - SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFromYaml, ApplicationSourceWithMetadata sourceWithMetadata, @@ -569,7 +580,13 @@ SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFro using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); var absFilePaths = valuesFilesInHelmSource.Select(f => Path.Combine(repository.WorkingDirectory, f)); - return UpdateHelmValuesFiles(absFilePaths.ToHashSet(), defaultRegistry, repository, deploymentConfig, gateway, sourceWithMetadata, applicationFromYaml); + return ProcessHelmValuesFiles(absFilePaths.ToHashSet(), + defaultRegistry, + repository, + deploymentConfig, + gateway, + sourceWithMetadata, + applicationFromYaml); } void LogHelmSourceConfigurationProblems(IReadOnlyCollection helmSourceConfigurationProblems) @@ -789,4 +806,4 @@ string Serialize(JsonPatchDocument patchDocument) record SourceUpdateResult(HashSet ImagesUpdated, string CommitSha, List PatchedFiles); } -} +} \ No newline at end of file From b00797958902500730dd660d7ba6b96bf42d09a2 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 12:54:23 +1100 Subject: [PATCH 14/30] unrequired change removed --- source/Calamari.Common/Plumbing/Variables/PackageVariables.cs | 2 +- source/Calamari/Kubernetes/SpecialVariables.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs b/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs index 2639e7bc7f..c220963214 100644 --- a/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs +++ b/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs @@ -51,4 +51,4 @@ public class Output public static readonly string FilePath = "Package.FilePath"; } } -} +} \ No newline at end of file diff --git a/source/Calamari/Kubernetes/SpecialVariables.cs b/source/Calamari/Kubernetes/SpecialVariables.cs index c746eabede..e68921237f 100644 --- a/source/Calamari/Kubernetes/SpecialVariables.cs +++ b/source/Calamari/Kubernetes/SpecialVariables.cs @@ -75,8 +75,6 @@ public static class Git public static readonly string InputPath = "Octopus.Action.ArgoCD.InputPath"; public static readonly string PurgeOutput = "Octopus.Action.ArgoCD.PurgeOutputFolder"; - - public static readonly string UseHelmValueYamlPathFromStep = "Octopus.Action.ArgoCD.UseHelmValueYamlPathFromStep"; public static class PullRequest { From 9b716bf08147dd899a5317052bf287f25d391f6c Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 13:21:26 +1100 Subject: [PATCH 15/30] remove the unused field --- .../Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs | 3 +-- .../ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs | 5 +---- .../Conventions/UpdateArgoCDAppImagesInstallConvention.cs | 4 ++-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs index 176156c2a0..016f009beb 100644 --- a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs +++ b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs @@ -36,8 +36,7 @@ public UpdateArgoCDAppDeploymentConfig CreateUpdateImageConfig(RunningDeployment var commitParameters = CommitParameters(deployment); var packageHelmReference = deployment.Variables.GetContainerPackages().Select(p => new ContainerImageReferenceAndHelmReference(ContainerImageReference.FromReferenceString(p.PackageName), p.HelmReference)).ToList(); - var useHelmValueYamlPathFromStep = deployment.Variables.GetFlag(SpecialVariables.Git.UseHelmValueYamlPathFromStep, false); - return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageHelmReference, useHelmValueYamlPathFromStep); + return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageHelmReference); } bool RequiresPullRequest(RunningDeployment deployment) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index 2479769487..54fe194409 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -7,13 +7,10 @@ public class UpdateArgoCDAppDeploymentConfig public GitCommitParameters CommitParameters { get; } public IReadOnlyCollection PackageWithHelmReference { get; } - public bool UseHelmValueYamlPathFromStep { get; } - - public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List packageWithHelmReference, bool useHelmValueYamlPathFromStep) + public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List packageWithHelmReference) { CommitParameters = commitParameters; PackageWithHelmReference = packageWithHelmReference; - UseHelmValueYamlPathFromStep = useHelmValueYamlPathFromStep; } } } diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index cb688fc774..caa436eb55 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -306,7 +306,7 @@ SourceUpdateResult ProcessRef( log.WarnFormat("The source '{0}' contains a Ref, only referenced files will be updated. Please create another source with the same URL if you wish to update files under the path.", sourceWithMetadata.SourceIdentity); } - if (!deploymentConfig.UseHelmValueYamlPathFromStep) + if (!deploymentConfig.PackageWithHelmReference.All(p => p.HelmReference is null)) { var helmTargetsForRefSource = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) .GetHelmTargetsForRefSource(sourceWithMetadata); @@ -396,7 +396,7 @@ SourceUpdateResult ProcessHelm( return new SourceUpdateResult(new HashSet(), string.Empty, []); } - if (!deploymentConfig.UseHelmValueYamlPathFromStep) + if (!deploymentConfig.PackageWithHelmReference.All(p => p.HelmReference is null)) { return ProcessHelmSourceUsingAnnotations(applicationFromYaml, sourceWithMetadata, From be046145a172b27666873555fc8a6a4510b1afee Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 13:41:49 +1100 Subject: [PATCH 16/30] trying to reduce footprint of change --- source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs | 1 + .../ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs index 016f009beb..a52b077b03 100644 --- a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs +++ b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs @@ -36,6 +36,7 @@ public UpdateArgoCDAppDeploymentConfig CreateUpdateImageConfig(RunningDeployment var commitParameters = CommitParameters(deployment); var packageHelmReference = deployment.Variables.GetContainerPackages().Select(p => new ContainerImageReferenceAndHelmReference(ContainerImageReference.FromReferenceString(p.PackageName), p.HelmReference)).ToList(); + return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageHelmReference); } diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index 54fe194409..b46011d2ce 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; +using Calamari.ArgoCD.Models; namespace Calamari.ArgoCD.Conventions { public class UpdateArgoCDAppDeploymentConfig { public GitCommitParameters CommitParameters { get; } - public IReadOnlyCollection PackageWithHelmReference { get; } + public List ImageReferences { get; } public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List packageWithHelmReference) { From b3a09ac6caeb017973755bbe7cb21ba669bfb38c Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 15:07:58 +1100 Subject: [PATCH 17/30] trying to shrink it --- .../UpdateArgoCDAppDeploymentConfig.cs | 2 +- .../UpdateArgoCDAppImagesInstallConvention.cs | 3 +- .../Calamari/ArgoCD/Helm/HelmValuesEditor.cs | 11 +--- .../HelmValuesImageReplaceStepVariables.cs | 54 ++++++++++++------- 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index b46011d2ce..f0cd45d32b 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -6,7 +6,7 @@ namespace Calamari.ArgoCD.Conventions public class UpdateArgoCDAppDeploymentConfig { public GitCommitParameters CommitParameters { get; } - public List ImageReferences { get; } + public List PackageWithHelmReference { get; } public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List packageWithHelmReference) { diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index caa436eb55..c535bd9d92 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -470,8 +470,7 @@ SourceUpdateResult ProcessHelmUpdateTargets( { var results = targets.Select(t => UpdateHelmImageValues(repository.WorkingDirectory, t, - deploymentConfig.PackageWithHelmReference.Select(ph => ph.ContainerReference).ToList() - )) + deploymentConfig.PackageWithHelmReference)) .ToList(); var updatedImages = results.SelectMany(r => r.ImagesUpdated).ToHashSet(); diff --git a/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs b/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs index 1077512a26..debe4fa248 100644 --- a/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs +++ b/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs @@ -1,18 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; using Octostache; namespace Calamari.ArgoCD.Helm { public class HelmValuesEditor { - public static Dictionary CreateFlattenedDictionary(HelmYamlParser parsedYaml) - { - var allValuesPaths = parsedYaml.CreateDotPathsForNodes(); - return allValuesPaths.ToDictionary(a => a, parsedYaml.GetValueAtPath); - } - /// /// Converts YAML into a Variable Dictionary that can be used to resolve the value of /// a dot-notation path and used with HelmTemplate/Octostache syntax. @@ -41,4 +33,5 @@ public static string UpdateNodeValue(string yamlContent, string path, string new return yamlProcessor.UpdateContentForPath(path, newValue); } } -} \ No newline at end of file +} + diff --git a/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs b/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs index 949ae38a8c..9316cb99e2 100644 --- a/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs +++ b/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs @@ -25,32 +25,48 @@ public ImageReplacementResult UpdateImages(IReadOnlyCollection(); var updatedYaml = yamlContent; var originalYamlParser = new HelmYamlParser(yamlContent); // Parse and track the original yaml so that content can be read from it. - var flattenedYamlPathDictionary = HelmValuesEditor.CreateFlattenedDictionary(originalYamlParser); + var flattenedYamlPathDictionary = HelmValuesEditor.GenerateVariableDictionary(originalYamlParser); + + var imagesWithNoHelmReference = imagesToUpdate.Where(c => c.HelmReference is null).ToList(); + if (imagesWithNoHelmReference.Any()) + { + foreach (var image in imagesWithNoHelmReference) + { + log.Info($"{image.ContainerReference.ToString()} will not be updated, as no helm yaml path has been specified for it in the step configuration"); + } + } + foreach (var container in imagesToUpdate.Where(c => c.HelmReference is not null)) { - if (flattenedYamlPathDictionary.TryGetValue(container.HelmReference!, out var valueToUpdate)) + var helmReference = container.HelmReference!; + var valueToUpdate = flattenedYamlPathDictionary.GetRaw(helmReference); + if (valueToUpdate == null) + { + //TODO(tmm): This is not a great piece of information, given we don't know what the filename is! + log.Verbose($"{helmReference} for image {container.ContainerReference.ToString()} was not found in your values file."); + continue; + } + + if (IsUnstructuredText(valueToUpdate)) { - if (IsUnstructuredText(valueToUpdate)) + HelmValuesEditor.UpdateNodeValue(updatedYaml, container.HelmReference!, container.ContainerReference.Tag); + } + else + { + var cir = ContainerImageReference.FromReferenceString(valueToUpdate, defaultRegistry); + var comparison = container.ContainerReference.CompareWith(cir); + if (comparison.MatchesImage()) { - HelmValuesEditor.UpdateNodeValue(updatedYaml, container.HelmReference!, container.ContainerReference.Tag); + if (!comparison.TagMatch) + { + var newValue = cir.WithTag(container.ContainerReference.Tag); + updatedYaml = HelmValuesEditor.UpdateNodeValue(updatedYaml, container.HelmReference!, newValue); + imagesUpdated.Add(newValue); + } } else { - var cir = ContainerImageReference.FromReferenceString(valueToUpdate, defaultRegistry); - var comparison = container.ContainerReference.CompareWith(cir); - if (comparison.MatchesImage()) - { - if (!comparison.TagMatch) - { - var newValue = cir.WithTag(container.ContainerReference.Tag); - updatedYaml = HelmValuesEditor.UpdateNodeValue(updatedYaml, container.HelmReference!, newValue); - imagesUpdated.Add(newValue); - } - } - else - { - log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); - } + log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); } } } From 5e08a826fe328bb600b073d8b7942e297e734719 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 15:08:57 +1100 Subject: [PATCH 18/30] remove changes to valueseditor --- source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs b/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs index debe4fa248..9e1971c9a8 100644 --- a/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs +++ b/source/Calamari/ArgoCD/Helm/HelmValuesEditor.cs @@ -24,7 +24,7 @@ public static VariableDictionary GenerateVariableDictionary(HelmYamlParser parse } /// - /// Updates the value of yaml a node and returns it as a string (preserving formatting). + /// Updates the value of yaml a node and returns it as a strung (preserving formatting). /// public static string UpdateNodeValue(string yamlContent, string path, string newValue) { From 1eb66fe15901d2d3eb72d37df77adcef7e4bfd26 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 20:18:21 +1100 Subject: [PATCH 19/30] tidying up --- .../UpdateArgoCDAppDeploymentConfig.cs | 6 ++--- .../UpdateArgoCDAppImagesInstallConvention.cs | 22 +++++++++++-------- .../Models/HelmValuesFileImageUpdateTarget.cs | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index f0cd45d32b..3525cdb5cf 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -6,12 +6,12 @@ namespace Calamari.ArgoCD.Conventions public class UpdateArgoCDAppDeploymentConfig { public GitCommitParameters CommitParameters { get; } - public List PackageWithHelmReference { get; } + public List ImageReferences { get; } - public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List packageWithHelmReference) + public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List imageReferences) { CommitParameters = commitParameters; - PackageWithHelmReference = packageWithHelmReference; + ImageReferences = imageReferences; } } } diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index c535bd9d92..8407f3e50c 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -266,7 +266,7 @@ SourceUpdateResult ProcessKustomize( { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages, patchedFiles) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.PackageWithHelmReference); + var (updatedFiles, updatedImages, patchedFiles) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.ImageReferences); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -306,7 +306,7 @@ SourceUpdateResult ProcessRef( log.WarnFormat("The source '{0}' contains a Ref, only referenced files will be updated. Please create another source with the same URL if you wish to update files under the path.", sourceWithMetadata.SourceIdentity); } - if (!deploymentConfig.PackageWithHelmReference.All(p => p.HelmReference is null)) + if (!deploymentConfig.ImageReferences.All(p => p.HelmReference is null)) { var helmTargetsForRefSource = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) .GetHelmTargetsForRefSource(sourceWithMetadata); @@ -353,7 +353,7 @@ SourceUpdateResult ProcessDirectory( { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages, patchedFiles) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.PackageWithHelmReference); + var (updatedFiles, updatedImages, patchedFiles) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.ImageReferences); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -396,7 +396,7 @@ SourceUpdateResult ProcessHelm( return new SourceUpdateResult(new HashSet(), string.Empty, []); } - if (!deploymentConfig.PackageWithHelmReference.All(p => p.HelmReference is null)) + if (!deploymentConfig.ImageReferences.All(p => p.HelmReference is null)) { return ProcessHelmSourceUsingAnnotations(applicationFromYaml, sourceWithMetadata, @@ -408,6 +408,11 @@ SourceUpdateResult ProcessHelm( applicationSource); } + if (applicationFromYaml.Metadata.Annotations.ContainsKey(ArgoCDConstants.Annotations.OctopusImageReplacementPathsKey(sourceWithMetadata.Source.Name)); + { + log.Warn($"Application {applicationFromYaml.Metadata.Name} specifies helm-value annotations which have been superseded by container-values specified in the step's configuration"); + } + return ProcessHelmUpdateTargetsWithStepVariables(applicationFromYaml, gitCredentials, repositoryFactory, @@ -470,7 +475,8 @@ SourceUpdateResult ProcessHelmUpdateTargets( { var results = targets.Select(t => UpdateHelmImageValues(repository.WorkingDirectory, t, - deploymentConfig.PackageWithHelmReference)) + deploymentConfig.ImageReferences + )) .ToList(); var updatedImages = results.SelectMany(r => r.ImagesUpdated).ToHashSet(); @@ -543,7 +549,7 @@ SourceUpdateResult ProcessHelmValuesFiles(HashSet filesToUpdate, Func imageReplacerFactory = yaml => new HelmValuesImageReplaceStepVariables(yaml, defaultRegistry, log); log.Verbose($"Found {filesToUpdate.Count} yaml files to process"); - var (updatedFiles, updatedImages, patchedFiles) = Update(repository.WorkingDirectory, deploymentConfig.PackageWithHelmReference, filesToUpdate.ToHashSet(), imageReplacerFactory); + var (updatedFiles, updatedImages, patchedFiles) = Update(repository.WorkingDirectory, deploymentConfig.ImageReferences, filesToUpdate.ToHashSet(), imageReplacerFactory); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -575,8 +581,6 @@ SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFro { var extractor = new HelmValuesFileExtractor(applicationFromYaml, defaultRegistry); var valuesFilesInHelmSource = extractor.GetValueFilesReferencedInRefSource(sourceWithMetadata); - //these files are relative to repo - so need to path them wrt repository once checked out. - using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); var absFilePaths = valuesFilesInHelmSource.Select(f => Path.Combine(repository.WorkingDirectory, f)); return ProcessHelmValuesFiles(absFilePaths.ToHashSet(), @@ -805,4 +809,4 @@ string Serialize(JsonPatchDocument patchDocument) record SourceUpdateResult(HashSet ImagesUpdated, string CommitSha, List PatchedFiles); } -} \ No newline at end of file +} diff --git a/source/Calamari/ArgoCD/Models/HelmValuesFileImageUpdateTarget.cs b/source/Calamari/ArgoCD/Models/HelmValuesFileImageUpdateTarget.cs index f887c7c087..fa0101773d 100644 --- a/source/Calamari/ArgoCD/Models/HelmValuesFileImageUpdateTarget.cs +++ b/source/Calamari/ArgoCD/Models/HelmValuesFileImageUpdateTarget.cs @@ -15,7 +15,7 @@ public HelmValuesFileImageUpdateTarget(string defaultClusterRegistry, FileName = fileName; ImagePathDefinitions = imagePathDefinitions; } - + public string Path { get; } public string DefaultClusterRegistry { get; } From 7d9aaca134dfa2f304bb68236b5696292c4d8b9d Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 20:52:05 +1100 Subject: [PATCH 20/30] don't think I can clean better --- ...goCDAppImagesInstallConventionHelmTests.cs | 2 - .../UpdateArgoCDAppDeploymentConfig.cs | 7 ++ .../UpdateArgoCDAppImagesInstallConvention.cs | 116 +++++++++--------- .../ArgoCD/Helm/HelmValuesFileExtractor.cs | 5 +- 4 files changed, 65 insertions(+), 65 deletions(-) diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs index f19addd84c..ed82968df6 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs @@ -1395,7 +1395,6 @@ public void CanUpdateRefSourceUsingStepBasedVariables() [PackageVariables.HelmValueYamlPath("nginx")] = "image.nginx", //NOTE: no .Values to start, and no leading . [ProjectVariables.Slug] = ProjectSlug, [DeploymentEnvironment.Slug] = EnvironmentSlug, - [SpecialVariables.Git.UseHelmValueYamlPathFromStep] = "true", }; var runningDeployment = new RunningDeployment(null, variables); runningDeployment.CurrentDirectoryProvider = DeploymentWorkingDirectory.StagingDirectory; @@ -1482,7 +1481,6 @@ public void CanUpdateHelmSourceUsingStepBasedVariables() [PackageVariables.HelmValueYamlPath("nginx")] = "image.nginx", //NOTE: no .Values to start, and no leading . [ProjectVariables.Slug] = ProjectSlug, [DeploymentEnvironment.Slug] = EnvironmentSlug, - [SpecialVariables.Git.UseHelmValueYamlPathFromStep] = "true", }; var runningDeployment = new RunningDeployment(null, variables); runningDeployment.CurrentDirectoryProvider = DeploymentWorkingDirectory.StagingDirectory; diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index 3525cdb5cf..29f33c6a47 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using System.Net.Mime; using Calamari.ArgoCD.Models; namespace Calamari.ArgoCD.Conventions @@ -13,5 +15,10 @@ public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, Lis CommitParameters = commitParameters; ImageReferences = imageReferences; } + + public bool HasStepBasedHelmValueReferences() + { + return ImageReferences.Any(ir => ir.HelmReference is not null); + } } } diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 8407f3e50c..0e5c0e9e21 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -306,30 +306,34 @@ SourceUpdateResult ProcessRef( log.WarnFormat("The source '{0}' contains a Ref, only referenced files will be updated. Please create another source with the same URL if you wish to update files under the path.", sourceWithMetadata.SourceIdentity); } - if (!deploymentConfig.ImageReferences.All(p => p.HelmReference is null)) + using var repository = CreateRepository(gitCredentials, applicationSource, repositoryFactory); + if (deploymentConfig.HasStepBasedHelmValueReferences()) { - var helmTargetsForRefSource = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) - .GetHelmTargetsForRefSource(sourceWithMetadata); - - LogHelmSourceConfigurationProblems(helmTargetsForRefSource.Problems); - - using var repository = CreateRepository(gitCredentials, applicationSource, repositoryFactory); - return ProcessHelmUpdateTargets( - applicationFromYaml, - repository, - deploymentConfig, - sourceWithMetadata, - helmTargetsForRefSource.Targets, - gateway); + if (applicationFromYaml.Metadata.Annotations.ContainsKey(ArgoCDConstants.Annotations.OctopusImageReplacementPathsKey(new ApplicationSourceName(sourceWithMetadata.Source.Name)))) + { + log.Warn($"Application {applicationFromYaml.Metadata.Name} specifies helm-value annotations which have been superseded by container-values specified in the step's configuration"); + } + + return ProcessRefSource(applicationFromYaml, + sourceWithMetadata, + repository, + deploymentConfig, + defaultRegistry, + gateway); } - return ProcessRefSourceUsingStepVariables(applicationFromYaml, - sourceWithMetadata, - gitCredentials, - repositoryFactory, - deploymentConfig, - defaultRegistry, - gateway); + var helmTargetsForRefSource = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) + .GetHelmTargetsForRefSource(sourceWithMetadata); + LogHelmSourceConfigurationProblems(helmTargetsForRefSource.Problems); + + + return ProcessHelmUpdateTargets( + applicationFromYaml, + repository, + deploymentConfig, + sourceWithMetadata, + helmTargetsForRefSource.Targets, + gateway); } /// Images that were updated @@ -396,30 +400,30 @@ SourceUpdateResult ProcessHelm( return new SourceUpdateResult(new HashSet(), string.Empty, []); } - if (!deploymentConfig.ImageReferences.All(p => p.HelmReference is null)) + if (deploymentConfig.HasStepBasedHelmValueReferences()) { - return ProcessHelmSourceUsingAnnotations(applicationFromYaml, - sourceWithMetadata, - gitCredentials, - repositoryFactory, - deploymentConfig, - defaultRegistry, - gateway, - applicationSource); - } + if (applicationFromYaml.Metadata.Annotations.ContainsKey(ArgoCDConstants.Annotations.OctopusImageReplacementPathsKey(new ApplicationSourceName(sourceWithMetadata.Source.Name)))) + { + log.Warn($"Application {applicationFromYaml.Metadata.Name} specifies helm-value annotations which have been superseded by container-values specified in the step's configuration"); + } - if (applicationFromYaml.Metadata.Annotations.ContainsKey(ArgoCDConstants.Annotations.OctopusImageReplacementPathsKey(sourceWithMetadata.Source.Name)); - { - log.Warn($"Application {applicationFromYaml.Metadata.Name} specifies helm-value annotations which have been superseded by container-values specified in the step's configuration"); + return ProcessHelmUpdateTargetsWithStepVariables(applicationFromYaml, + gitCredentials, + repositoryFactory, + deploymentConfig, + sourceWithMetadata, + defaultRegistry, + gateway); } - return ProcessHelmUpdateTargetsWithStepVariables(applicationFromYaml, - gitCredentials, - repositoryFactory, - deploymentConfig, - sourceWithMetadata, - defaultRegistry, - gateway); + return ProcessHelmSourceUsingAnnotations(applicationFromYaml, + sourceWithMetadata, + gitCredentials, + repositoryFactory, + deploymentConfig, + defaultRegistry, + gateway, + applicationSource); } SourceUpdateResult ProcessHelmSourceUsingAnnotations(Application applicationFromYaml, @@ -516,17 +520,13 @@ SourceUpdateResult ProcessHelmUpdateTargetsWithStepVariables( string defaultRegistry, ArgoCDGatewayDto gateway) { - var extractor = new HelmValuesFileExtractor(applicationFromYaml, defaultRegistry); + var extractor = new HelmValuesFileExtractor(applicationFromYaml); var valuesFilesInHelmSource = extractor.GetInlineValuesFilesReferencedByHelmSource(sourceWithMetadata); //Add the implicit value file if needed using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); var implicitValuesFile = HelmDiscovery.TryFindValuesFile(fileSystem, sourceWithMetadata.Source.Path!); - var filesToUpdate = valuesFilesInHelmSource.ToList(); - if (implicitValuesFile != null && !valuesFilesInHelmSource.Contains(implicitValuesFile)) - { - filesToUpdate.Add(implicitValuesFile); - } + var filesToUpdate = implicitValuesFile == null ? valuesFilesInHelmSource : valuesFilesInHelmSource.Append(implicitValuesFile); filesToUpdate = filesToUpdate.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); return ProcessHelmValuesFiles(filesToUpdate.ToHashSet(), @@ -571,19 +571,17 @@ SourceUpdateResult ProcessHelmValuesFiles(HashSet filesToUpdate, return new SourceUpdateResult(new HashSet(), string.Empty, []); } - SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFromYaml, - ApplicationSourceWithMetadata sourceWithMetadata, - Dictionary gitCredentials, - RepositoryFactory repositoryFactory, - UpdateArgoCDAppDeploymentConfig deploymentConfig, - string defaultRegistry, - ArgoCDGatewayDto gateway) + SourceUpdateResult ProcessRefSource(Application applicationFromYaml, + ApplicationSourceWithMetadata sourceWithMetadata, + RepositoryWrapper repository, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + string defaultRegistry, + ArgoCDGatewayDto gateway) { - var extractor = new HelmValuesFileExtractor(applicationFromYaml, defaultRegistry); - var valuesFilesInHelmSource = extractor.GetValueFilesReferencedInRefSource(sourceWithMetadata); - using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); - var absFilePaths = valuesFilesInHelmSource.Select(f => Path.Combine(repository.WorkingDirectory, f)); - return ProcessHelmValuesFiles(absFilePaths.ToHashSet(), + var extractor = new HelmValuesFileExtractor(applicationFromYaml); + var valuesFiles = extractor.GetValueFilesReferencedInRefSource(sourceWithMetadata) + .Select(file => Path.Combine(repository.WorkingDirectory, file)); + return ProcessHelmValuesFiles(valuesFiles.ToHashSet(), defaultRegistry, repository, deploymentConfig, diff --git a/source/Calamari/ArgoCD/Helm/HelmValuesFileExtractor.cs b/source/Calamari/ArgoCD/Helm/HelmValuesFileExtractor.cs index 669d7abdbe..f9a6944bef 100644 --- a/source/Calamari/ArgoCD/Helm/HelmValuesFileExtractor.cs +++ b/source/Calamari/ArgoCD/Helm/HelmValuesFileExtractor.cs @@ -12,13 +12,10 @@ namespace Calamari.ArgoCD.Helm public class HelmValuesFileExtractor { readonly List helmSources; - - readonly string defaultRegistry; - public HelmValuesFileExtractor(Application toUpdate, string defaultRegistry) + public HelmValuesFileExtractor(Application toUpdate) { helmSources = toUpdate.GetSourcesWithMetadata().Where(s => s.SourceType == SourceType.Helm).ToList(); - this.defaultRegistry = defaultRegistry; } public IReadOnlyCollection GetInlineValuesFilesReferencedByHelmSource(ApplicationSourceWithMetadata helmSource) From b4682b3c3e5bd37c61a49b90fedf59c05bc1cf05 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 21:08:37 +1100 Subject: [PATCH 21/30] after personal review --- ...goCDAppImagesInstallConventionHelmTests.cs | 3 - .../Conventions/DeploymentConfigFactory.cs | 1 - .../UpdateArgoCDAppDeploymentConfig.cs | 2 - .../UpdateArgoCDAppImagesInstallConvention.cs | 100 ++++++++++-------- .../HelmValuesImageReplaceStepVariables.cs | 26 ++--- .../Models/HelmValuesFileImageUpdateTarget.cs | 2 +- 6 files changed, 64 insertions(+), 70 deletions(-) diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs index ed82968df6..97e87849d3 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs @@ -1382,7 +1382,6 @@ public void DirectorySource_ImageMatches_ReportsDeploymentWithNonEmptyCommitSha( ]); } - [Test] public void CanUpdateRefSourceUsingStepBasedVariables() { @@ -1545,8 +1544,6 @@ public void CanUpdateHelmSourceUsingStepBasedVariables() var clonedRepoPath = RepositoryHelpers.CloneOrigin(tempDirectory, OriginPath, argoCDBranchName); AssertFileContents(clonedRepoPath, existingYamlFile, updatedYamlContent); } - - void AssertFileContents(string clonedRepoPath, string relativeFilePath, string expectedContent) { diff --git a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs index a52b077b03..016f009beb 100644 --- a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs +++ b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs @@ -36,7 +36,6 @@ public UpdateArgoCDAppDeploymentConfig CreateUpdateImageConfig(RunningDeployment var commitParameters = CommitParameters(deployment); var packageHelmReference = deployment.Variables.GetContainerPackages().Select(p => new ContainerImageReferenceAndHelmReference(ContainerImageReference.FromReferenceString(p.PackageName), p.HelmReference)).ToList(); - return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageHelmReference); } diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index 29f33c6a47..73cb741220 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Net.Mime; -using Calamari.ArgoCD.Models; namespace Calamari.ArgoCD.Conventions { diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 0e5c0e9e21..3ccb4a73d8 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -67,6 +67,15 @@ public void Install(RunningDeployment deployment) log.Verbose("Executing Update Argo CD Application Images"); var deploymentConfig = deploymentConfigFactory.CreateUpdateImageConfig(deployment); + var imagesWithNoHelmReference = deploymentConfig.ImageReferences.Where(c => c.HelmReference is null).ToList(); + if (imagesWithNoHelmReference.Any()) + { + foreach (var image in imagesWithNoHelmReference) + { + log.Info($"{image.ContainerReference.ToString()} will not be updated, as no helm yaml path has been specified for it in the step configuration"); + } + } + var repositoryFactory = new RepositoryFactory(log, fileSystem, deployment.CurrentDirectory, @@ -314,7 +323,7 @@ SourceUpdateResult ProcessRef( log.Warn($"Application {applicationFromYaml.Metadata.Name} specifies helm-value annotations which have been superseded by container-values specified in the step's configuration"); } - return ProcessRefSource(applicationFromYaml, + return ProcessRefSourceUsingStepVariables(applicationFromYaml, sourceWithMetadata, repository, deploymentConfig, @@ -324,9 +333,9 @@ SourceUpdateResult ProcessRef( var helmTargetsForRefSource = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) .GetHelmTargetsForRefSource(sourceWithMetadata); + LogHelmSourceConfigurationProblems(helmTargetsForRefSource.Problems); - return ProcessHelmUpdateTargets( applicationFromYaml, repository, @@ -407,13 +416,13 @@ SourceUpdateResult ProcessHelm( log.Warn($"Application {applicationFromYaml.Metadata.Name} specifies helm-value annotations which have been superseded by container-values specified in the step's configuration"); } - return ProcessHelmUpdateTargetsWithStepVariables(applicationFromYaml, - gitCredentials, - repositoryFactory, - deploymentConfig, - sourceWithMetadata, - defaultRegistry, - gateway); + return ProcessHelmSourceUsingStepVariables(applicationFromYaml, + gitCredentials, + repositoryFactory, + deploymentConfig, + sourceWithMetadata, + defaultRegistry, + gateway); } return ProcessHelmSourceUsingAnnotations(applicationFromYaml, @@ -479,8 +488,8 @@ SourceUpdateResult ProcessHelmUpdateTargets( { var results = targets.Select(t => UpdateHelmImageValues(repository.WorkingDirectory, t, - deploymentConfig.ImageReferences - )) + deploymentConfig.ImageReferences + )) .ToList(); var updatedImages = results.SelectMany(r => r.ImagesUpdated).ToHashSet(); @@ -511,7 +520,7 @@ SourceUpdateResult ProcessHelmUpdateTargets( return new SourceUpdateResult(new HashSet(), string.Empty, []); } - SourceUpdateResult ProcessHelmUpdateTargetsWithStepVariables( + SourceUpdateResult ProcessHelmSourceUsingStepVariables( Application applicationFromYaml, Dictionary gitCredentials, RepositoryFactory repositoryFactory, @@ -523,28 +532,48 @@ SourceUpdateResult ProcessHelmUpdateTargetsWithStepVariables( var extractor = new HelmValuesFileExtractor(applicationFromYaml); var valuesFilesInHelmSource = extractor.GetInlineValuesFilesReferencedByHelmSource(sourceWithMetadata); - //Add the implicit value file if needed + using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); var implicitValuesFile = HelmDiscovery.TryFindValuesFile(fileSystem, sourceWithMetadata.Source.Path!); - var filesToUpdate = implicitValuesFile == null ? valuesFilesInHelmSource : valuesFilesInHelmSource.Append(implicitValuesFile); + var filesToUpdate = implicitValuesFile == null ? valuesFilesInHelmSource : valuesFilesInHelmSource.Append(implicitValuesFile); filesToUpdate = filesToUpdate.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); return ProcessHelmValuesFiles(filesToUpdate.ToHashSet(), - defaultRegistry, - repository, - deploymentConfig, - gateway, - sourceWithMetadata, - applicationFromYaml); + defaultRegistry, + repository, + deploymentConfig, + gateway, + sourceWithMetadata, + applicationFromYaml); + } + + + SourceUpdateResult ProcessRefSourceUsingStepVariables(Application applicationFromYaml, + ApplicationSourceWithMetadata sourceWithMetadata, + RepositoryWrapper repository, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + string defaultRegistry, + ArgoCDGatewayDto gateway) + { + var extractor = new HelmValuesFileExtractor(applicationFromYaml); + var valuesFiles = extractor.GetValueFilesReferencedInRefSource(sourceWithMetadata) + .Select(file => Path.Combine(repository.WorkingDirectory, file)); + return ProcessHelmValuesFiles(valuesFiles.ToHashSet(), + defaultRegistry, + repository, + deploymentConfig, + gateway, + sourceWithMetadata, + applicationFromYaml); } SourceUpdateResult ProcessHelmValuesFiles(HashSet filesToUpdate, - string defaultRegistry, - RepositoryWrapper repository, - UpdateArgoCDAppDeploymentConfig deploymentConfig, - ArgoCDGatewayDto gateway, - ApplicationSourceWithMetadata sourceWithMetadata, - Application applicationFromYaml) + string defaultRegistry, + RepositoryWrapper repository, + UpdateArgoCDAppDeploymentConfig deploymentConfig, + ArgoCDGatewayDto gateway, + ApplicationSourceWithMetadata sourceWithMetadata, + Application applicationFromYaml) { Func imageReplacerFactory = yaml => new HelmValuesImageReplaceStepVariables(yaml, defaultRegistry, log); log.Verbose($"Found {filesToUpdate.Count} yaml files to process"); @@ -571,25 +600,6 @@ SourceUpdateResult ProcessHelmValuesFiles(HashSet filesToUpdate, return new SourceUpdateResult(new HashSet(), string.Empty, []); } - SourceUpdateResult ProcessRefSource(Application applicationFromYaml, - ApplicationSourceWithMetadata sourceWithMetadata, - RepositoryWrapper repository, - UpdateArgoCDAppDeploymentConfig deploymentConfig, - string defaultRegistry, - ArgoCDGatewayDto gateway) - { - var extractor = new HelmValuesFileExtractor(applicationFromYaml); - var valuesFiles = extractor.GetValueFilesReferencedInRefSource(sourceWithMetadata) - .Select(file => Path.Combine(repository.WorkingDirectory, file)); - return ProcessHelmValuesFiles(valuesFiles.ToHashSet(), - defaultRegistry, - repository, - deploymentConfig, - gateway, - sourceWithMetadata, - applicationFromYaml); - } - void LogHelmSourceConfigurationProblems(IReadOnlyCollection helmSourceConfigurationProblems) { foreach (var helmSourceConfigurationProblem in helmSourceConfigurationProblems) diff --git a/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs b/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs index 9316cb99e2..24bd71c1ba 100644 --- a/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs +++ b/source/Calamari/ArgoCD/HelmValuesImageReplaceStepVariables.cs @@ -27,46 +27,36 @@ public ImageReplacementResult UpdateImages(IReadOnlyCollection c.HelmReference is null).ToList(); - if (imagesWithNoHelmReference.Any()) + foreach (var newImageTag in imagesToUpdate.Where(c => c.HelmReference is not null)) { - foreach (var image in imagesWithNoHelmReference) - { - log.Info($"{image.ContainerReference.ToString()} will not be updated, as no helm yaml path has been specified for it in the step configuration"); - } - } - - foreach (var container in imagesToUpdate.Where(c => c.HelmReference is not null)) - { - var helmReference = container.HelmReference!; + var helmReference = newImageTag.HelmReference!; var valueToUpdate = flattenedYamlPathDictionary.GetRaw(helmReference); if (valueToUpdate == null) { - //TODO(tmm): This is not a great piece of information, given we don't know what the filename is! - log.Verbose($"{helmReference} for image {container.ContainerReference.ToString()} was not found in your values file."); + log.Verbose($"{helmReference} for image {newImageTag.ContainerReference.ToString()} was not found in your values file."); continue; } if (IsUnstructuredText(valueToUpdate)) { - HelmValuesEditor.UpdateNodeValue(updatedYaml, container.HelmReference!, container.ContainerReference.Tag); + HelmValuesEditor.UpdateNodeValue(updatedYaml, helmReference, newImageTag.ContainerReference.Tag); } else { var cir = ContainerImageReference.FromReferenceString(valueToUpdate, defaultRegistry); - var comparison = container.ContainerReference.CompareWith(cir); + var comparison = newImageTag.ContainerReference.CompareWith(cir); if (comparison.MatchesImage()) { if (!comparison.TagMatch) { - var newValue = cir.WithTag(container.ContainerReference.Tag); - updatedYaml = HelmValuesEditor.UpdateNodeValue(updatedYaml, container.HelmReference!, newValue); + var newValue = cir.WithTag(newImageTag.ContainerReference.Tag); + updatedYaml = HelmValuesEditor.UpdateNodeValue(updatedYaml, helmReference, newValue); imagesUpdated.Add(newValue); } } else { - log.Warn($"Attempted to update value entry '{container.HelmReference}', however it contains a mismatched image name and registry."); + log.Warn($"Attempted to update value entry '{helmReference}', however it contains a mismatched image name and registry."); } } } diff --git a/source/Calamari/ArgoCD/Models/HelmValuesFileImageUpdateTarget.cs b/source/Calamari/ArgoCD/Models/HelmValuesFileImageUpdateTarget.cs index fa0101773d..f887c7c087 100644 --- a/source/Calamari/ArgoCD/Models/HelmValuesFileImageUpdateTarget.cs +++ b/source/Calamari/ArgoCD/Models/HelmValuesFileImageUpdateTarget.cs @@ -15,7 +15,7 @@ public HelmValuesFileImageUpdateTarget(string defaultClusterRegistry, FileName = fileName; ImagePathDefinitions = imagePathDefinitions; } - + public string Path { get; } public string DefaultClusterRegistry { get; } From 914037cc2b5201976a306b4df367bca25472403e Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 21:21:29 +1100 Subject: [PATCH 22/30] minor renames --- source/Calamari.Common/Plumbing/Variables/PackageVariables.cs | 2 +- .../UpdateArgoCDAppImagesInstallConventionHelmTests.cs | 4 ++-- source/Calamari/ArgoCD/VariablesExtensionMethods.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs b/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs index c220963214..24dd7b0187 100644 --- a/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs +++ b/source/Calamari.Common/Plumbing/Variables/PackageVariables.cs @@ -27,7 +27,7 @@ public static class PackageVariables public static string IndexedPackagePurpose(string packageReferenceName) => $"Octopus.Action.Package[{packageReferenceName}].Purpose"; - public static string HelmValueYamlPath(string packageReferenceName) => $"Octopus.Action.Package[{packageReferenceName}].HelmReplacementPath"; + public static string HelmReplacementPath(string packageReferenceName) => $"Octopus.Action.Package[{packageReferenceName}].HelmReplacementPath"; public static string IndexedOriginalPath(string packageReferenceName) { diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs index 97e87849d3..9ef191da7e 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs @@ -1391,7 +1391,7 @@ public void CanUpdateRefSourceUsingStepBasedVariables() { [PackageVariables.IndexedImage("nginx")] = "index.docker.io/nginx:1.27.1", [PackageVariables.IndexedPackagePurpose("nginx")] = "DockerImageReference", - [PackageVariables.HelmValueYamlPath("nginx")] = "image.nginx", //NOTE: no .Values to start, and no leading . + [PackageVariables.HelmReplacementPath("nginx")] = "image.nginx", //NOTE: no .Values to start, and no leading . [ProjectVariables.Slug] = ProjectSlug, [DeploymentEnvironment.Slug] = EnvironmentSlug, }; @@ -1477,7 +1477,7 @@ public void CanUpdateHelmSourceUsingStepBasedVariables() { [PackageVariables.IndexedImage("nginx")] = "docker.io/nginx:1.27.1", // NOTE the lack of "index" [PackageVariables.IndexedPackagePurpose("nginx")] = "DockerImageReference", - [PackageVariables.HelmValueYamlPath("nginx")] = "image.nginx", //NOTE: no .Values to start, and no leading . + [PackageVariables.HelmReplacementPath("nginx")] = "image.nginx", //NOTE: no .Values to start, and no leading . [ProjectVariables.Slug] = ProjectSlug, [DeploymentEnvironment.Slug] = EnvironmentSlug, }; diff --git a/source/Calamari/ArgoCD/VariablesExtensionMethods.cs b/source/Calamari/ArgoCD/VariablesExtensionMethods.cs index 2320cb3628..f11872a09d 100644 --- a/source/Calamari/ArgoCD/VariablesExtensionMethods.cs +++ b/source/Calamari/ArgoCD/VariablesExtensionMethods.cs @@ -16,7 +16,7 @@ public static IList GetContainerPackages(this IVariable var packageReferences = (from packageIndex in packageIndexes let image = variables.Get(PackageVariables.IndexedImage(packageIndex), string.Empty) let purpose = variables.Get(PackageVariables.IndexedPackagePurpose(packageIndex), string.Empty) - let helmValueYamlPath = variables.Get(PackageVariables.HelmValueYamlPath(packageIndex), null) + let helmValueYamlPath = variables.Get(PackageVariables.HelmReplacementPath(packageIndex), null) where purpose.Equals("DockerImageReference", StringComparison.Ordinal) select new PackageAndHelmReference(image, helmValueYamlPath)) .ToList(); From 5776f62180cd510d97ec9ac7bc4076fea8ef1767 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 21:32:13 +1100 Subject: [PATCH 23/30] factor in the featuretoggle --- source/Calamari.Common/FeatureToggles/FeatureToggle.cs | 3 ++- .../Calamari.Common/FeatureToggles/OctopusFeatureToggle.cs | 4 +++- .../Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs | 3 ++- .../ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs | 6 ++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/source/Calamari.Common/FeatureToggles/FeatureToggle.cs b/source/Calamari.Common/FeatureToggles/FeatureToggle.cs index 84d24f5499..3869465e2f 100644 --- a/source/Calamari.Common/FeatureToggles/FeatureToggle.cs +++ b/source/Calamari.Common/FeatureToggles/FeatureToggle.cs @@ -10,6 +10,7 @@ public enum FeatureToggle { OidcAccountsFeatureToggle, KubernetesAuthAwsCliWithExecFeatureToggle, ForceUtf8ZipFileDecodingFeatureToggle, - BashParametersArrayFeatureToggle + BashParametersArrayFeatureToggle, + argo-cd-helm-replace-path-from-container-reference, } } \ No newline at end of file diff --git a/source/Calamari.Common/FeatureToggles/OctopusFeatureToggle.cs b/source/Calamari.Common/FeatureToggles/OctopusFeatureToggle.cs index ef680155d2..d34c5ccf45 100644 --- a/source/Calamari.Common/FeatureToggles/OctopusFeatureToggle.cs +++ b/source/Calamari.Common/FeatureToggles/OctopusFeatureToggle.cs @@ -7,9 +7,11 @@ public static class OctopusFeatureToggles public static class KnownSlugs { public const string AnsiColorsInTaskLogFeatureToggle = "ansi-colors"; + public const string ArgoCDHelmReplacePathFromContainerReferenceFeatureToggle = "argo-cd-helm-replace-path-from-container-reference"; }; - public static readonly OctopusFeatureToggle AnsiColorsInTaskLogFeatureToggle = new OctopusFeatureToggle(KnownSlugs.AnsiColorsInTaskLogFeatureToggle); + public static readonly OctopusFeatureToggle AnsiColorsInTaskLogFeatureToggle = new(KnownSlugs.AnsiColorsInTaskLogFeatureToggle); + public static readonly OctopusFeatureToggle ArgoCDHelmReplacePathFromContainerReferenceFeatureToggle = new(KnownSlugs.ArgoCDHelmReplacePathFromContainerReferenceFeatureToggle); public class OctopusFeatureToggle { diff --git a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs index 016f009beb..475c7360d6 100644 --- a/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs +++ b/source/Calamari/ArgoCD/Conventions/DeploymentConfigFactory.cs @@ -36,7 +36,8 @@ public UpdateArgoCDAppDeploymentConfig CreateUpdateImageConfig(RunningDeployment var commitParameters = CommitParameters(deployment); var packageHelmReference = deployment.Variables.GetContainerPackages().Select(p => new ContainerImageReferenceAndHelmReference(ContainerImageReference.FromReferenceString(p.PackageName), p.HelmReference)).ToList(); - return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageHelmReference); + var useHelmReferenceFromContainer = OctopusFeatureToggles.ArgoCDHelmReplacePathFromContainerReferenceFeatureToggle.IsEnabled(deployment.Variables); + return new UpdateArgoCDAppDeploymentConfig(commitParameters, packageHelmReference, useHelmReferenceFromContainer); } bool RequiresPullRequest(RunningDeployment deployment) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs index 73cb741220..82503f14dc 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppDeploymentConfig.cs @@ -7,16 +7,18 @@ public class UpdateArgoCDAppDeploymentConfig { public GitCommitParameters CommitParameters { get; } public List ImageReferences { get; } + public bool UseHelmReferenceFromContainer { get; } - public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List imageReferences) + public UpdateArgoCDAppDeploymentConfig(GitCommitParameters commitParameters, List imageReferences, bool useHelmReferenceFromContainer) { CommitParameters = commitParameters; ImageReferences = imageReferences; + UseHelmReferenceFromContainer = useHelmReferenceFromContainer; } public bool HasStepBasedHelmValueReferences() { - return ImageReferences.Any(ir => ir.HelmReference is not null); + return ImageReferences.Any(ir => ir.HelmReference is not null) && UseHelmReferenceFromContainer; } } } From 219cc69f16ccfda695703f0e528d5bc427c43836 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Tue, 24 Feb 2026 21:45:06 +1100 Subject: [PATCH 24/30] fixup the feature-toggle stuffs --- source/Calamari.Common/FeatureToggles/FeatureToggle.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/Calamari.Common/FeatureToggles/FeatureToggle.cs b/source/Calamari.Common/FeatureToggles/FeatureToggle.cs index 3869465e2f..84d24f5499 100644 --- a/source/Calamari.Common/FeatureToggles/FeatureToggle.cs +++ b/source/Calamari.Common/FeatureToggles/FeatureToggle.cs @@ -10,7 +10,6 @@ public enum FeatureToggle { OidcAccountsFeatureToggle, KubernetesAuthAwsCliWithExecFeatureToggle, ForceUtf8ZipFileDecodingFeatureToggle, - BashParametersArrayFeatureToggle, - argo-cd-helm-replace-path-from-container-reference, + BashParametersArrayFeatureToggle } } \ No newline at end of file From 24765577356dc372c417bcf745d5cd175d670a50 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Wed, 25 Feb 2026 08:09:23 +1100 Subject: [PATCH 25/30] fix up the feature toggle in tests --- .../UpdateArgoCDAppImagesInstallConventionHelmTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs index 9ef191da7e..92fbc51af0 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs @@ -11,6 +11,7 @@ using Calamari.ArgoCD.Git.GitVendorApiAdapters; using Calamari.ArgoCD.Models; using Calamari.Common.Commands; +using Calamari.Common.FeatureToggles; using Calamari.Common.Plumbing.Deployment; using Calamari.Common.Plumbing.FileSystem; using Calamari.Common.Plumbing.Variables; @@ -1348,6 +1349,7 @@ public void DirectorySource_ImageMatches_ReportsDeploymentWithNonEmptyCommitSha( [PackageVariables.IndexedPackagePurpose("nginx")] = "DockerImageReference", [PackageVariables.IndexedImage("alpine")] = "alpine:2.2", [PackageVariables.IndexedPackagePurpose("alpine")] = "DockerImageReference", + [KnownVariables.EnabledFeatureToggles] = OctopusFeatureToggles.KnownSlugs.ArgoCDHelmReplacePathFromContainerReferenceFeatureToggle, }; //Act @@ -1394,6 +1396,7 @@ public void CanUpdateRefSourceUsingStepBasedVariables() [PackageVariables.HelmReplacementPath("nginx")] = "image.nginx", //NOTE: no .Values to start, and no leading . [ProjectVariables.Slug] = ProjectSlug, [DeploymentEnvironment.Slug] = EnvironmentSlug, + [KnownVariables.EnabledFeatureToggles] = OctopusFeatureToggles.KnownSlugs.ArgoCDHelmReplacePathFromContainerReferenceFeatureToggle, }; var runningDeployment = new RunningDeployment(null, variables); runningDeployment.CurrentDirectoryProvider = DeploymentWorkingDirectory.StagingDirectory; From 940b2b7d7307eddfcea46eeb572bb25dbd021b07 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Wed, 25 Feb 2026 14:16:10 +1100 Subject: [PATCH 26/30] no idea --- .../UpdateArgoCDAppImagesInstallConvention.cs | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 3ccb4a73d8..9d3fdcc362 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -411,9 +411,10 @@ SourceUpdateResult ProcessHelm( if (deploymentConfig.HasStepBasedHelmValueReferences()) { - if (applicationFromYaml.Metadata.Annotations.ContainsKey(ArgoCDConstants.Annotations.OctopusImageReplacementPathsKey(new ApplicationSourceName(sourceWithMetadata.Source.Name)))) + var appName = sourceWithMetadata.Source.Name.IsNullOrEmpty() ? null : new ApplicationSourceName(sourceWithMetadata.Source.Name); + if (applicationFromYaml.Metadata.Annotations.ContainsKey(ArgoCDConstants.Annotations.OctopusImageReplacementPathsKey(appName))) { - log.Warn($"Application {applicationFromYaml.Metadata.Name} specifies helm-value annotations which have been superseded by container-values specified in the step's configuration"); + log.Warn($"Application '{applicationFromYaml.Metadata.Name}' specifies helm-value annotations which have been superseded by values specified in the step's configuration"); } return ProcessHelmSourceUsingStepVariables(applicationFromYaml, @@ -532,19 +533,28 @@ SourceUpdateResult ProcessHelmSourceUsingStepVariables( var extractor = new HelmValuesFileExtractor(applicationFromYaml); var valuesFilesInHelmSource = extractor.GetInlineValuesFilesReferencedByHelmSource(sourceWithMetadata); - - using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); - var implicitValuesFile = HelmDiscovery.TryFindValuesFile(fileSystem, sourceWithMetadata.Source.Path!); - var filesToUpdate = implicitValuesFile == null ? valuesFilesInHelmSource : valuesFilesInHelmSource.Append(implicitValuesFile); - - filesToUpdate = filesToUpdate.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); - return ProcessHelmValuesFiles(filesToUpdate.ToHashSet(), - defaultRegistry, - repository, - deploymentConfig, - gateway, - sourceWithMetadata, - applicationFromYaml); + using (var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory)) + { + Log.Info("1"); + var filesToUpdate = valuesFilesInHelmSource.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); + var implicitValuesFile = HelmDiscovery.TryFindValuesFile(fileSystem, Path.Combine(repository.WorkingDirectory, sourceWithMetadata.Source.Path!)); + if (implicitValuesFile != null) + { + filesToUpdate.Add(implicitValuesFile); + } + Log.Info("5"); + filesToUpdate = filesToUpdate.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); + Log.Info("6"); + var result = ProcessHelmValuesFiles(filesToUpdate.ToHashSet(), + defaultRegistry, + repository, + deploymentConfig, + gateway, + sourceWithMetadata, + applicationFromYaml); + Log.Info("10"); + return result; + } } @@ -581,6 +591,7 @@ SourceUpdateResult ProcessHelmValuesFiles(HashSet filesToUpdate, var (updatedFiles, updatedImages, patchedFiles) = Update(repository.WorkingDirectory, deploymentConfig.ImageReferences, filesToUpdate.ToHashSet(), imageReplacerFactory); if (updatedImages.Count > 0) { + Log.Info("Trying to push up changes"); var pushResult = PushToRemote(repository, GitReference.CreateFromString(sourceWithMetadata.Source.TargetRevision), deploymentConfig.CommitParameters, From 1bc9315c6be8e24ad8071eb4f1b5398cbbc2befe Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Wed, 25 Feb 2026 16:34:53 +1100 Subject: [PATCH 27/30] fixed issues when actually running --- .../UpdateArgoCDAppImagesInstallConvention.cs | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 9d3fdcc362..e6d2304299 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -533,28 +533,23 @@ SourceUpdateResult ProcessHelmSourceUsingStepVariables( var extractor = new HelmValuesFileExtractor(applicationFromYaml); var valuesFilesInHelmSource = extractor.GetInlineValuesFilesReferencedByHelmSource(sourceWithMetadata); - using (var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory)) + using var repository = CreateRepository(gitCredentials, sourceWithMetadata.Source, repositoryFactory); + var filesToUpdate = valuesFilesInHelmSource.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); + var implicitValuesFile = HelmDiscovery.TryFindValuesFile(fileSystem, Path.Combine(repository.WorkingDirectory, sourceWithMetadata.Source.Path!)); + if (implicitValuesFile != null) { - Log.Info("1"); - var filesToUpdate = valuesFilesInHelmSource.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); - var implicitValuesFile = HelmDiscovery.TryFindValuesFile(fileSystem, Path.Combine(repository.WorkingDirectory, sourceWithMetadata.Source.Path!)); - if (implicitValuesFile != null) - { - filesToUpdate.Add(implicitValuesFile); - } - Log.Info("5"); - filesToUpdate = filesToUpdate.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); - Log.Info("6"); - var result = ProcessHelmValuesFiles(filesToUpdate.ToHashSet(), - defaultRegistry, - repository, - deploymentConfig, - gateway, - sourceWithMetadata, - applicationFromYaml); - Log.Info("10"); - return result; + implicitValuesFile = Path.Combine(repository.WorkingDirectory, sourceWithMetadata.Source.Path!, implicitValuesFile); + filesToUpdate.Add(implicitValuesFile); } + filesToUpdate = filesToUpdate.Select(file => Path.Combine(repository.WorkingDirectory, file)).ToList(); + var result = ProcessHelmValuesFiles(filesToUpdate.ToHashSet(), + defaultRegistry, + repository, + deploymentConfig, + gateway, + sourceWithMetadata, + applicationFromYaml); + return result; } From 5d09206803fbb155556db0a907116bc61fd4ccf4 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Wed, 25 Feb 2026 17:12:13 +1100 Subject: [PATCH 28/30] move logging --- .../UpdateArgoCDAppImagesInstallConvention.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index e6d2304299..017798268f 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -66,15 +66,6 @@ public void Install(RunningDeployment deployment) { log.Verbose("Executing Update Argo CD Application Images"); var deploymentConfig = deploymentConfigFactory.CreateUpdateImageConfig(deployment); - - var imagesWithNoHelmReference = deploymentConfig.ImageReferences.Where(c => c.HelmReference is null).ToList(); - if (imagesWithNoHelmReference.Any()) - { - foreach (var image in imagesWithNoHelmReference) - { - log.Info($"{image.ContainerReference.ToString()} will not be updated, as no helm yaml path has been specified for it in the step configuration"); - } - } var repositoryFactory = new RepositoryFactory(log, fileSystem, @@ -134,6 +125,15 @@ ProcessApplicationResult ProcessApplication( ValidateApplication(applicationFromYaml); + var imagesWithNoHelmReference = deploymentConfig.ImageReferences.Where(c => c.HelmReference is null).ToList(); + if (imagesWithNoHelmReference.Any() && applicationFromYaml.GetSourcesWithMetadata().Any(src => src.SourceType == SourceType.Helm)) + { + foreach (var image in imagesWithNoHelmReference) + { + log.Info($"{image.ContainerReference.ToString()} will not be updated in helm sources, as no helm yaml path has been specified for it in the step configuration."); + } + } + var updatedSourcesResults = applicationFromYaml.GetSourcesWithMetadata() .Select(applicationSource => new { From 4a644705a1f48a180eb1c963b6e9d524717bc5a9 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Thu, 26 Feb 2026 07:58:12 +1100 Subject: [PATCH 29/30] reduce to verbose --- .../Conventions/UpdateArgoCDAppImagesInstallConvention.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 017798268f..3e67dde1ef 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -130,7 +130,7 @@ ProcessApplicationResult ProcessApplication( { foreach (var image in imagesWithNoHelmReference) { - log.Info($"{image.ContainerReference.ToString()} will not be updated in helm sources, as no helm yaml path has been specified for it in the step configuration."); + log.Verbose($"{image.ContainerReference.ToString()} will not be updated in helm sources, as no helm yaml path has been specified for it in the step configuration."); } } From 2f089c2e998695aeb0c19609a4f08a9ecf074547 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Thu, 26 Feb 2026 08:07:33 +1100 Subject: [PATCH 30/30] include the featuretoggle in the tests --- .../UpdateArgoCDAppImagesInstallConventionHelmTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs index 92fbc51af0..aafcf2f421 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs @@ -1483,6 +1483,7 @@ public void CanUpdateHelmSourceUsingStepBasedVariables() [PackageVariables.HelmReplacementPath("nginx")] = "image.nginx", //NOTE: no .Values to start, and no leading . [ProjectVariables.Slug] = ProjectSlug, [DeploymentEnvironment.Slug] = EnvironmentSlug, + [KnownVariables.EnabledFeatureToggles] = OctopusFeatureToggles.KnownSlugs.ArgoCDHelmReplacePathFromContainerReferenceFeatureToggle, }; var runningDeployment = new RunningDeployment(null, variables); runningDeployment.CurrentDirectoryProvider = DeploymentWorkingDirectory.StagingDirectory;