diff --git a/source/Calamari.Tests/ArgoCD/ArgoCDFilesUpdatedReporterTests.cs b/source/Calamari.Tests/ArgoCD/ArgoCDFilesUpdatedReporterTests.cs index 7bcdeff3f..81dfa4ae8 100644 --- a/source/Calamari.Tests/ArgoCD/ArgoCDFilesUpdatedReporterTests.cs +++ b/source/Calamari.Tests/ArgoCD/ArgoCDFilesUpdatedReporterTests.cs @@ -21,7 +21,7 @@ public void ReportDeployments_WithNoUpdatedApplications_WritesNoServiceMessages( new("gateway1", new ApplicationName("app1"), 2, 2, [], [], []) }; - reporter.ReportDeployments(applicationResults); + reporter.ReportFilesUpdated(applicationResults); var messages = log.ServiceMessages; messages.Should().BeEmpty(); @@ -38,7 +38,7 @@ public void ReportDeployments_WithSingleUpdatedApplication_WritesOneServiceMessa new("gateway1", new ApplicationName("app1"), 2, 2, [new UpdatedSourceDetail("abc123", 0, [], [])], [], []) }; - reporter.ReportDeployments(applicationResults); + reporter.ReportFilesUpdated(applicationResults); log.ServiceMessages.Should().ContainSingle().Which.Should().BeEquivalentTo(new { @@ -64,7 +64,7 @@ public void ReportDeployments_WithMultipleUpdatedApplications_WritesMultipleServ new("gateway2", new ApplicationName("app2"), 1, 1, [new UpdatedSourceDetail("def456", 0, [], [])], [], []) }; - reporter.ReportDeployments(applicationResults); + reporter.ReportFilesUpdated(applicationResults); log.ServiceMessages.Should().BeEquivalentTo([ new @@ -90,6 +90,154 @@ public void ReportDeployments_WithMultipleUpdatedApplications_WritesMultipleServ ]); } + [Test] + public void ReportDeployments_WithReplacedFiles_IncludesReplacedFilesInSources() + { + var log = new InMemoryLog(); + var reporter = new ArgoCDFilesUpdatedReporter(log); + + var applicationResults = new List + { + new("gateway1", new ApplicationName("app1"), 2, 2, + [new UpdatedSourceDetail("abc123", 0, [new FilePathContent("values.yaml", "image: nginx:latest")], [])], + [], []) + }; + + reporter.ReportFilesUpdated(applicationResults); + + log.ServiceMessages.Should().ContainSingle().Which.Should().BeEquivalentTo(new + { + Name = "argocd-files-updated", + Properties = new Dictionary + { + ["gatewayId"] = "gateway1", + ["applicationName"] = "app1", + ["sources"] = "[{\"CommitSha\":\"abc123\",\"SourceIndex\":0,\"ReplacedFiles\":[{\"FilePath\":\"values.yaml\",\"Content\":\"image: nginx:latest\"}],\"PatchedFiles\":[]}]" + } + }); + } + + [Test] + public void ReportDeployments_WithPatchedFiles_IncludesPatchedFilesInSources() + { + var log = new InMemoryLog(); + var reporter = new ArgoCDFilesUpdatedReporter(log); + + var applicationResults = new List + { + new("gateway1", new ApplicationName("app1"), 1, 1, + [new UpdatedSourceDetail("def456", 0, [], [new FilePathContent("kustomization.yaml", "images:\n- name: nginx")])], + [], []) + }; + + reporter.ReportFilesUpdated(applicationResults); + + log.ServiceMessages.Should().ContainSingle().Which.Should().BeEquivalentTo(new + { + Name = "argocd-files-updated", + Properties = new Dictionary + { + ["gatewayId"] = "gateway1", + ["applicationName"] = "app1", + ["sources"] = "[{\"CommitSha\":\"def456\",\"SourceIndex\":0,\"ReplacedFiles\":[],\"PatchedFiles\":[{\"FilePath\":\"kustomization.yaml\",\"Content\":\"images:\\n- name: nginx\"}]}]" + } + }); + } + + [Test] + public void ReportDeployments_WithBothReplacedAndPatchedFiles_IncludesBothInSources() + { + var log = new InMemoryLog(); + var reporter = new ArgoCDFilesUpdatedReporter(log); + + var applicationResults = new List + { + new("gateway1", new ApplicationName("app1"), 2, 2, + [new UpdatedSourceDetail("abc123", 0, + [new FilePathContent("values.yaml", "image: nginx:latest")], + [new FilePathContent("kustomization.yaml", "images:\n- name: nginx")])], + [], []) + }; + + reporter.ReportFilesUpdated(applicationResults); + + log.ServiceMessages.Should().ContainSingle().Which.Should().BeEquivalentTo(new + { + Name = "argocd-files-updated", + Properties = new Dictionary + { + ["gatewayId"] = "gateway1", + ["applicationName"] = "app1", + ["sources"] = "[{\"CommitSha\":\"abc123\",\"SourceIndex\":0,\"ReplacedFiles\":[{\"FilePath\":\"values.yaml\",\"Content\":\"image: nginx:latest\"}],\"PatchedFiles\":[{\"FilePath\":\"kustomization.yaml\",\"Content\":\"images:\\n- name: nginx\"}]}]" + } + }); + } + + [Test] + public void ReportDeployments_WithMultipleReplacedAndPatchedFiles_IncludesAllFilesInSources() + { + var log = new InMemoryLog(); + var reporter = new ArgoCDFilesUpdatedReporter(log); + + var applicationResults = new List + { + new("gateway1", new ApplicationName("app1"), 2, 2, + [new UpdatedSourceDetail("abc123", 0, + [ + new FilePathContent("values.yaml", "image: nginx:latest"), + new FilePathContent("values-prod.yaml", "replicas: 3") + ], + [ + new FilePathContent("kustomization.yaml", "images:\n- name: nginx"), + new FilePathContent("patch.yaml", "spec:\n replicas: 3") + ])], + [], []) + }; + + reporter.ReportFilesUpdated(applicationResults); + + log.ServiceMessages.Should().ContainSingle().Which.Should().BeEquivalentTo(new + { + Name = "argocd-files-updated", + Properties = new Dictionary + { + ["gatewayId"] = "gateway1", + ["applicationName"] = "app1", + ["sources"] = "[{\"CommitSha\":\"abc123\",\"SourceIndex\":0,\"ReplacedFiles\":[{\"FilePath\":\"values.yaml\",\"Content\":\"image: nginx:latest\"},{\"FilePath\":\"values-prod.yaml\",\"Content\":\"replicas: 3\"}],\"PatchedFiles\":[{\"FilePath\":\"kustomization.yaml\",\"Content\":\"images:\\n- name: nginx\"},{\"FilePath\":\"patch.yaml\",\"Content\":\"spec:\\n replicas: 3\"}]}]" + } + }); + } + + [Test] + public void ReportDeployments_WithMultipleSourcesWithFiles_IncludesAllSourcesInSources() + { + var log = new InMemoryLog(); + var reporter = new ArgoCDFilesUpdatedReporter(log); + + var applicationResults = new List + { + new("gateway1", new ApplicationName("app1"), 2, 2, + [ + new UpdatedSourceDetail("abc123", 0, [new FilePathContent("values.yaml", "image: nginx:latest")], []), + new UpdatedSourceDetail("abc123", 1, [], [new FilePathContent("kustomization.yaml", "images:\n- name: redis")]) + ], + [], []) + }; + + reporter.ReportFilesUpdated(applicationResults); + + log.ServiceMessages.Should().ContainSingle().Which.Should().BeEquivalentTo(new + { + Name = "argocd-files-updated", + Properties = new Dictionary + { + ["gatewayId"] = "gateway1", + ["applicationName"] = "app1", + ["sources"] = "[{\"CommitSha\":\"abc123\",\"SourceIndex\":0,\"ReplacedFiles\":[{\"FilePath\":\"values.yaml\",\"Content\":\"image: nginx:latest\"}],\"PatchedFiles\":[]},{\"CommitSha\":\"abc123\",\"SourceIndex\":1,\"ReplacedFiles\":[],\"PatchedFiles\":[{\"FilePath\":\"kustomization.yaml\",\"Content\":\"images:\\n- name: redis\"}]}]" + } + }); + } + [Test] public void ReportDeployments_WithMixedUpdatedAndNonUpdatedApplications_WritesOnlyUpdatedMessages() { @@ -103,7 +251,7 @@ public void ReportDeployments_WithMixedUpdatedAndNonUpdatedApplications_WritesOn new("gateway3", new ApplicationName("app3"), 1, 1, [], [], []) }; - reporter.ReportDeployments(applicationResults); + reporter.ReportFilesUpdated(applicationResults); log.ServiceMessages.Should().ContainSingle().Which.Should().BeEquivalentTo(new { diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs index 60775f647..f21c5eb1a 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionHelmTests.cs @@ -51,6 +51,7 @@ public class UpdateArgoCDAppImagesInstallConventionHelmTests "; const string GatewayId = "Gateway1"; + IArgoCDFilesUpdatedReporter deploymentReporter; UpdateArgoCDAppImagesInstallConvention CreateConvention() { @@ -63,7 +64,7 @@ UpdateArgoCDAppImagesInstallConvention CreateConvention() argoCdApplicationManifestParser, Substitute.For(), new SystemClock(), - Substitute.For(), + deploymentReporter, new ArgoCDOutputVariablesWriter(log, nonSensitiveCalamariVariables)); } @@ -71,6 +72,7 @@ UpdateArgoCDAppImagesInstallConvention CreateConvention() public void Init() { log = new InMemoryLog(); + deploymentReporter = Substitute.For(); tempDirectory = fileSystem.CreateTemporaryDirectory(); originRepo = RepositoryHelpers.CreateBareRepository(OriginPath); @@ -1319,6 +1321,57 @@ public void RefSourceWithHelmImageMatchesAndPath_IgnoresFilesUnderPath() AssertOutputVariables(matchingApplicationTotalSourceCounts: "2"); } + [Test] + public void DirectorySource_ImageMatches_ReportsDeploymentWithNonEmptyCommitSha() + { + //Arrange + const string multiImageValuesFile = """ + image1: + name: nginx:1.22 + image2: + name: alpine + tag: latest + """; + originRepo.AddFilesToBranch(argoCDBranchName, ("files/values.yml", multiImageValuesFile)); + + argoCdApplicationFromYaml.Metadata.Annotations[ArgoCDConstants.Annotations.OctopusImageReplacementPathsKey(null)] = "{{ .Values.image1.name }}, {{ .Values.image2.name }}:{{ .Values.image2.tag }}"; + + var updater = CreateConvention(); + var variables = new CalamariVariables + { + [ProjectVariables.Slug] = ProjectSlug, + [DeploymentEnvironment.Slug] = EnvironmentSlug, + [PackageVariables.IndexedImage("nginx")] = "nginx:1.27.1", + [PackageVariables.IndexedPackagePurpose("nginx")] = "DockerImageReference", + [PackageVariables.IndexedImage("alpine")] = "alpine:2.2", + [PackageVariables.IndexedPackagePurpose("alpine")] = "DockerImageReference", + }; + + //Act + var runningDeployment = new RunningDeployment(null, variables); + runningDeployment.CurrentDirectoryProvider = DeploymentWorkingDirectory.StagingDirectory; + runningDeployment.StagingDirectory = tempDirectory; + + IReadOnlyList capturedResults = null; + deploymentReporter.ReportFilesUpdated(Arg.Do>(x => capturedResults = x)); + + updater.Install(runningDeployment); + + // Assert + using var scope = new AssertionScope(); + capturedResults.Should().NotBeNull(); + var actual = capturedResults.Single(); + actual.UpdatedImages.Should().BeEquivalentTo(["{ Reference = nginx:1.27.1, Comparison = ContainerImageComparison { RegistryMatch = True, ImageNameMatch = True, TagMatch = False } }", "{ Reference = alpine:2.2, Comparison = ContainerImageComparison { RegistryMatch = True, ImageNameMatch = True, TagMatch = False } }"]); + actual.GitReposUpdated.Should().HaveCount(1); + actual.UpdatedSourceDetails.Should().HaveCount(1); + + var sourceDetails = actual.UpdatedSourceDetails.First(); + sourceDetails.CommitSha.Should().HaveLength(40); + sourceDetails.ReplacedFiles.Should().BeEmpty(); + // TODO: fill in with json patch + // sourceDetails.PatchedFiles.Should().BeEquivalentTo([new FilePathContent("", "")]); + } + void AssertFileContents(string clonedRepoPath, string relativeFilePath, string expectedContent) { var absolutePath = Path.Combine(clonedRepoPath, relativeFilePath); diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionTest.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionTest.cs index 3208df0f6..e44b2c31b 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionTest.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDAppImagesInstallConventionTest.cs @@ -8,6 +8,7 @@ using Calamari.ArgoCD.Dtos; using Calamari.ArgoCD.Git; using Calamari.ArgoCD.Git.GitVendorApiAdapters; +using Calamari.ArgoCD.Models; using Calamari.Common.Commands; using Calamari.Common.Plumbing.Deployment; using Calamari.Common.Plumbing.FileSystem; @@ -130,11 +131,11 @@ public void PluginSourceType_DontUpdate() runningDeployment.StagingDirectory = tempDirectory; var kustomizeFile = "kustomization.yaml"; - var kustomizeFileContents = @" -images: -- name: ""docker.io/nginx"" - newTag: ""1.25"" -"; + var kustomizeFileContents = """ + images: + - name: "docker.io/nginx" + newTag: "1.25" + """; var filesInRepo = new (string, string)[] { (kustomizeFile, @@ -253,27 +254,27 @@ public void DirectorySource_ImageMatches_Update() { ( yamlFilename, - @" -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sample-deployment -spec: - replicas: 1 - selector: - matchLabels: - app: sample-deployment - template: - metadata: - labels: - app: sample-deployment - spec: - containers: - - name: nginx - image: nginx:1.19 - - name: alpine - image: alpine:3.21 -" + """ + apiVersion: apps/v1 + kind: Deployment + metadata: + name: sample-deployment + spec: + replicas: 1 + selector: + matchLabels: + app: sample-deployment + template: + metadata: + labels: + app: sample-deployment + spec: + containers: + - name: nginx + image: nginx:1.19 + - name: alpine + image: alpine:3.21 + """ ) }; originRepo.AddFilesToBranch(argoCDBranchName, filesInRepo); @@ -283,27 +284,27 @@ public void DirectorySource_ImageMatches_Update() //Assert const string updatedYamlContent = - @" -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sample-deployment -spec: - replicas: 1 - selector: - matchLabels: - app: sample-deployment - template: - metadata: - labels: - app: sample-deployment - spec: - containers: - - name: nginx - image: nginx:1.27.1 - - name: alpine - image: alpine:3.21 -"; + """ + apiVersion: apps/v1 + kind: Deployment + metadata: + name: sample-deployment + spec: + replicas: 1 + selector: + matchLabels: + app: sample-deployment + template: + metadata: + labels: + app: sample-deployment + spec: + containers: + - name: nginx + image: nginx:1.27.1 + - name: alpine + image: alpine:3.21 + """; var clonedRepoPath = RepositoryHelpers.CloneOrigin(tempDirectory, OriginPath, argoCDBranchName); AssertFileContents(clonedRepoPath, yamlFilename, updatedYamlContent); @@ -328,27 +329,27 @@ public void DirectorySource_NoPath_DontUpdate() runningDeployment.StagingDirectory = tempDirectory; var yamlFilename = "include/file1.yaml"; - var fileContents = @" -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sample-deployment -spec: - replicas: 1 - selector: - matchLabels: - app: sample-deployment - template: - metadata: - labels: - app: sample-deployment - spec: - containers: - - name: nginx - image: nginx:1.19 - - name: alpine - image: alpine:3.21 -"; + var fileContents = """ + apiVersion: apps/v1 + kind: Deployment + metadata: + name: sample-deployment + spec: + replicas: 1 + selector: + matchLabels: + app: sample-deployment + template: + metadata: + labels: + app: sample-deployment + spec: + containers: + - name: nginx + image: nginx:1.19 + - name: alpine + image: alpine:3.21 + """; var filesInRepo = new (string, string)[] { ( @@ -405,11 +406,11 @@ public void KustomizeSource_NoPath_DontUpdate() runningDeployment.StagingDirectory = tempDirectory; var kustomizeFile = "kustomization.yaml"; - var kustomizeFileContents = @" -images: -- name: ""docker.io/nginx"" - newTag: ""1.25"" -"; + var kustomizeFileContents = """ + images: + - name: "docker.io/nginx" + newTag: "1.25" + """; var filesInRepo = new (string, string)[] { (kustomizeFile, @@ -468,11 +469,11 @@ public void KustomizeSource_HasKustomizationFile_Update() var filesInRepo = new (string, string)[] { (kustomizeFile, - @" -images: -- name: ""docker.io/nginx"" - newTag: ""1.25"" -") + """ + images: + - name: "docker.io/nginx" + newTag: "1.25" + """) }; originRepo.AddFilesToBranch(argoCDBranchName, filesInRepo); @@ -499,11 +500,11 @@ public void KustomizeSource_HasKustomizationFile_Update() updater.Install(runningDeployment); // Assert - var updatedYamlContent = @" -images: -- name: ""docker.io/nginx"" - newTag: ""1.27.1"" -"; + var updatedYamlContent = """ + images: + - name: "docker.io/nginx" + newTag: "1.27.1" + """; var clonedRepoPath = RepositoryHelpers.CloneOrigin(tempDirectory, OriginPath, argoCDBranchName); AssertFileContents(clonedRepoPath, kustomizeFile, updatedYamlContent); @@ -527,27 +528,27 @@ public void KustomizeSource_NoKustomizationFile_DontUpdate() runningDeployment.StagingDirectory = tempDirectory; var existingYamlFile = "include/file1.yaml"; - var existingYamlContent = @" -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sample-deployment -spec: - replicas: 1 - selector: - matchLabels: - app: sample-deployment - template: - metadata: - labels: - app: sample-deployment - spec: - containers: - - name: nginx - image: nginx:1.19 - - name: alpine - image: alpine:3.21 -"; + var existingYamlContent = """ + apiVersion: apps/v1 + kind: Deployment + metadata: + name: sample-deployment + spec: + replicas: 1 + selector: + matchLabels: + app: sample-deployment + template: + metadata: + labels: + app: sample-deployment + spec: + containers: + - name: nginx + image: nginx:1.19 + - name: alpine + image: alpine:3.21 + """; var filesInRepo = new (string, string)[] { ( @@ -610,38 +611,50 @@ public void DirectorySource_ImageMatches_ReportsDeploymentWithNonEmptyCommitSha( { ( yamlFilename, - @" -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sample-deployment -spec: - replicas: 1 - selector: - matchLabels: - app: sample-deployment - template: - metadata: - labels: - app: sample-deployment - spec: - containers: - - name: nginx - image: nginx:1.19 - - name: alpine - image: alpine:3.21 -" + """ + apiVersion: apps/v1 + kind: Deployment + metadata: + name: sample-deployment + spec: + replicas: 1 + selector: + matchLabels: + app: sample-deployment + template: + metadata: + labels: + app: sample-deployment + spec: + containers: + - name: nginx + image: nginx:1.19 + - name: alpine + image: alpine:3.21 + """ ) }; originRepo.AddFilesToBranch(argoCDBranchName, filesInRepo); + IReadOnlyList capturedResults = null; + deploymentReporter.ReportFilesUpdated(Arg.Do>(x => capturedResults = x)); + // Act updater.Install(runningDeployment); // Assert - deploymentReporter.Received(1) - .ReportDeployments(Arg.Is>(results => - results.Count == 1)); + using var scope = new AssertionScope(); + capturedResults.Should().NotBeNull(); + var actual = capturedResults.Single(); + actual.UpdatedImages.Should().BeEquivalentTo(["nginx:1.27.1"]); + actual.GitReposUpdated.Should().HaveCount(1); + actual.UpdatedSourceDetails.Should().HaveCount(1); + + var sourceDetails = actual.UpdatedSourceDetails.First(); + sourceDetails.CommitSha.Should().HaveLength(40); + sourceDetails.ReplacedFiles.Should().BeEmpty(); + // TODO: fill in with json patch + // sourceDetails.PatchedFiles.Should().BeEquivalentTo([new FilePathContent("", "")]); } void AssertFileContents(string clonedRepoPath, string relativeFilePath, string expectedContent) diff --git a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDApplicationManifestsInstallConventionTests.cs b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDApplicationManifestsInstallConventionTests.cs index 295248329..c5014ae25 100644 --- a/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDApplicationManifestsInstallConventionTests.cs +++ b/source/Calamari.Tests/ArgoCD/Commands/Conventions/UpdateArgoCDApplicationManifestsInstallConventionTests.cs @@ -424,12 +424,23 @@ public void ExecuteCopiesFiles_ReportsDeploymentWithNonEmptyCommitSha() runningDeployment.CurrentDirectoryProvider = DeploymentWorkingDirectory.StagingDirectory; runningDeployment.StagingDirectory = WorkingDirectory; + IReadOnlyList capturedResults = null; + deploymentReporter.ReportFilesUpdated(Arg.Do>(x => capturedResults = x)); + var convention = CreateConvention(nonSensitiveCalamariVariables); convention.Install(runningDeployment); - deploymentReporter.Received(1) - .ReportDeployments(Arg.Is>(results => - results.Count == 1)); + using var scope = new AssertionScope(); + capturedResults.Should().NotBeNull(); + var actual = capturedResults.Single(); + actual.UpdatedImages.Should().BeEmpty(); + actual.GitReposUpdated.Should().HaveCount(1); + actual.UpdatedSourceDetails.Should().HaveCount(1); + + var sourceDetails = actual.UpdatedSourceDetails.First(); + sourceDetails.CommitSha.Should().HaveLength(40); + sourceDetails.ReplacedFiles.Should().BeEquivalentTo([new FilePathContent("first.yaml", "22c0df2cceca5273e4dc569dda52805d27df3360")]); + sourceDetails.PatchedFiles.Should().BeEmpty(); } [Test] diff --git a/source/Calamari/ArgoCD/ArgoCDFilesUpdatedReporter.cs b/source/Calamari/ArgoCD/ArgoCDFilesUpdatedReporter.cs index 3884fc644..8665ee935 100644 --- a/source/Calamari/ArgoCD/ArgoCDFilesUpdatedReporter.cs +++ b/source/Calamari/ArgoCD/ArgoCDFilesUpdatedReporter.cs @@ -10,7 +10,7 @@ namespace Calamari.ArgoCD { public interface IArgoCDFilesUpdatedReporter { - void ReportDeployments(IReadOnlyList applicationResults); + void ReportFilesUpdated(IReadOnlyList applicationResults); } public class ArgoCDFilesUpdatedReporter : IArgoCDFilesUpdatedReporter @@ -22,7 +22,7 @@ public ArgoCDFilesUpdatedReporter(ILog log) this.log = log; } - public void ReportDeployments(IReadOnlyList applicationResults) + public void ReportFilesUpdated(IReadOnlyList applicationResults) { foreach (var appResult in applicationResults.Where(r => r.Updated)) { diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 88a454221..5a81df482 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -88,7 +88,7 @@ public void Install(RunningDeployment deployment) }) .ToList(); - reporter.ReportDeployments(applicationResults); + reporter.ReportFilesUpdated(applicationResults); var totalApplicationsWithSourceCounts = applicationResults.Select(r => (r.ApplicationName, r.TotalSourceCount, r.MatchingSourceCount)).ToList(); var updatedApplications = applicationResults.Where(r => r.Updated).ToList(); @@ -154,7 +154,7 @@ ProcessApplicationResult ProcessApplication( 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, [], [])).ToList(), + 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()); } @@ -185,7 +185,9 @@ SourceUpdateResult ProcessSource( log.LogApplicationSourceScopeStatus(annotatedScope, applicationSource.Name.ToApplicationSourceName(), deploymentScope); if (!deploymentScope.Matches(annotatedScope)) - return new SourceUpdateResult(new HashSet(), string.Empty); + { + return new SourceUpdateResult(new HashSet(), string.Empty, []); + } switch (sourceWithMetadata.SourceType) { @@ -230,7 +232,7 @@ SourceUpdateResult ProcessSource( case SourceType.Plugin: { log.WarnFormat("Unable to update source '{0}' as Plugin sources aren't currently supported.", sourceWithMetadata.SourceIdentity); - return new SourceUpdateResult(new HashSet(), string.Empty); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } default: throw new ArgumentOutOfRangeException(); @@ -252,14 +254,14 @@ SourceUpdateResult ProcessKustomize( if (applicationSource.Path == null) { log.WarnFormat("Unable to update source '{0}' as a path has not been specified.", sourceWithMetadata.SourceIdentity); - return new SourceUpdateResult(new HashSet(), string.Empty); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } using (var repository = CreateRepository(gitCredentials, applicationSource, repositoryFactory)) { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.ImageReferences); + var (updatedFiles, updatedImages, patchedFiles) = UpdateKustomizeYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.ImageReferences); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -274,12 +276,12 @@ SourceUpdateResult ProcessKustomize( applicationFromYaml.Metadata.Name, sourceWithMetadata.Index, pushResult); - return new SourceUpdateResult(updatedImages, pushResult.CommitSha); + return new SourceUpdateResult(updatedImages, pushResult.CommitSha, patchedFiles); } } } - return new SourceUpdateResult(new HashSet(), string.Empty); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } /// Images that were updated @@ -328,14 +330,14 @@ SourceUpdateResult ProcessDirectory( if (applicationSource.Path == null) { log.WarnFormat("Unable to update source '{0}' as a path has not been specified.", sourceWithMetadata.SourceIdentity); - return new SourceUpdateResult(new HashSet(), string.Empty); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } using (var repository = CreateRepository(gitCredentials, applicationSource, repositoryFactory)) { log.Verbose($"Reading files from {applicationSource.Path}"); - var (updatedFiles, updatedImages) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.ImageReferences); + var (updatedFiles, updatedImages, patchedFiles) = UpdateKubernetesYaml(repository.WorkingDirectory, applicationSource.Path!, defaultRegistry, deploymentConfig.ImageReferences); if (updatedImages.Count > 0) { var pushResult = PushToRemote(repository, @@ -350,14 +352,14 @@ SourceUpdateResult ProcessDirectory( applicationFromYaml.Metadata.Name, sourceWithMetadata.Index, pushResult); - return new SourceUpdateResult(updatedImages, pushResult.CommitSha); + return new SourceUpdateResult(updatedImages, pushResult.CommitSha, patchedFiles); } - return new SourceUpdateResult(new HashSet(), string.Empty); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } } - return new SourceUpdateResult(new HashSet(), string.Empty); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } /// Images that were updated @@ -375,7 +377,7 @@ SourceUpdateResult ProcessHelm( if (applicationSource.Path == null) { log.WarnFormat("Unable to update source '{0}' as a path has not been specified.", sourceWithMetadata.SourceIdentity); - return new SourceUpdateResult(new HashSet(), string.Empty); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } var explicitHelmSources = new HelmValuesFileUpdateTargetParser(applicationFromYaml, defaultRegistry) @@ -429,6 +431,8 @@ SourceUpdateResult ProcessHelmUpdateTargets( var updatedImages = results.SelectMany(r => r.ImagesUpdated).ToHashSet(); if (updatedImages.Count > 0) { + var patchedFiles = results.Select(r => new FilePathContent(r.RelativeFilepath, r.JsonPatch)).ToList(); + var pushResult = PushToRemote(repository, GitReference.CreateFromString(sourceWithMetadata.Source.TargetRevision), deploymentConfig.CommitParameters, @@ -441,11 +445,11 @@ SourceUpdateResult ProcessHelmUpdateTargets( applicationFromYaml.Metadata.Name, sourceWithMetadata.Index, pushResult); - return new SourceUpdateResult(updatedImages, pushResult.CommitSha); + return new SourceUpdateResult(updatedImages, pushResult.CommitSha, patchedFiles); } } - return new SourceUpdateResult(new HashSet(), string.Empty); + return new SourceUpdateResult(new HashSet(), string.Empty, []); } void LogHelmSourceConfigurationProblems(IReadOnlyCollection helmSourceConfigurationProblems) @@ -516,7 +520,7 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede imageReplacePaths), null); } - (HashSet, HashSet) UpdateKubernetesYaml( + (HashSet, HashSet, List) UpdateKubernetesYaml( string rootPath, string subFolder, string defaultRegistry, @@ -531,7 +535,7 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede return Update(rootPath, imagesToUpdate, filesToUpdate, imageReplacerFactory); } - (HashSet, HashSet) UpdateKustomizeYaml( + (HashSet, HashSet, List) UpdateKustomizeYaml( string rootPath, string subFolder, string defaultRegistry, @@ -552,13 +556,14 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede } log.Warn("kustomization file not found, no files will be updated"); - return (new HashSet(), new HashSet()); + return ([], [], []); } - (HashSet, HashSet) Update(string rootPath, List imagesToUpdate, HashSet filesToUpdate, Func imageReplacerFactory) + (HashSet, HashSet, List) Update(string rootPath, List imagesToUpdate, HashSet filesToUpdate, Func imageReplacerFactory) { var updatedFiles = new HashSet(); var updatedImages = new HashSet(); + var jsonPatches = new List(); foreach (var file in filesToUpdate) { var relativePath = Path.GetRelativePath(rootPath, file); @@ -568,6 +573,8 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede var imageReplacer = imageReplacerFactory(content); var imageReplacementResult = imageReplacer.UpdateImages(imagesToUpdate); + // TODO: Generate JSON patch from changes and add to jsonPatches + if (imageReplacementResult.UpdatedImageReferences.Count > 0) { fileSystem.OverwriteFile(file, imageReplacementResult.UpdatedContents); @@ -585,7 +592,7 @@ RepositoryWrapper CreateRepository(Dictionary gitCrede } } - return (updatedFiles, updatedImages); + return (updatedFiles, updatedImages, jsonPatches); } HelmRefUpdatedResult UpdateHelmImageValues( @@ -604,7 +611,8 @@ HelmRefUpdatedResult UpdateHelmImageValues( fileSystem.OverwriteFile(filepath, imageUpdateResult.UpdatedContents); try { - return new HelmRefUpdatedResult(imageUpdateResult.UpdatedImageReferences, Path.Combine(target.Path, target.FileName)); + // TODO: Fill in JSON patch + return new HelmRefUpdatedResult(imageUpdateResult.UpdatedImageReferences, Path.Combine(target.Path, target.FileName), ""); } catch (Exception ex) { @@ -613,7 +621,7 @@ HelmRefUpdatedResult UpdateHelmImageValues( } } - return new HelmRefUpdatedResult(new HashSet(), Path.Combine(target.Path, target.FileName)); + return new HelmRefUpdatedResult(new HashSet(), Path.Combine(target.Path, target.FileName), string.Empty); } PushResult? PushToRemote( @@ -649,6 +657,6 @@ IEnumerable FindYamlFiles(string rootPath) return fileSystem.EnumerateFilesWithGlob(rootPath, yamlFileGlob); } - record SourceUpdateResult(HashSet ImagesUpdated, string CommitSha); + record SourceUpdateResult(HashSet ImagesUpdated, string CommitSha, List PatchedFiles); } } \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs index 4de860d4e..e60e22149 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs @@ -10,6 +10,7 @@ using Calamari.ArgoCD.Git.GitVendorApiAdapters; using Calamari.ArgoCD.Models; using Calamari.Common.Commands; +using Calamari.Common.Plumbing.Extensions; using Calamari.Common.Plumbing.FileSystem; using Calamari.Common.Plumbing.Logging; using Calamari.Common.Plumbing.Variables; @@ -92,7 +93,7 @@ public void Install(RunningDeployment deployment) }) .ToList(); - reporter.ReportDeployments(applicationResults); + reporter.ReportFilesUpdated(applicationResults); var gitReposUpdated = applicationResults.SelectMany(r => r.GitReposUpdated).ToHashSet(); var totalApplicationsWithSourceCounts = applicationResults.Select(r => (r.ApplicationName, r.TotalSourceCount, r.MatchingSourceCount)).ToList(); @@ -162,7 +163,7 @@ ProcessApplicationResult ProcessApplication( 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.UpdateResult.CommitSha, r.applicationSource.Index, [], [])).ToList(), + updatedSourcesResults.Select(r => new UpdatedSourceDetail(r.UpdateResult.CommitSha, r.applicationSource.Index, r.UpdateResult.ReplacedFiles, [])).ToList(), [], updatedSourcesResults.Select(r => r.applicationSource.Source.OriginalRepoUrl).ToHashSet()); } @@ -194,13 +195,13 @@ ManifestUpdateResult ProcessSource( log.LogApplicationSourceScopeStatus(annotatedScope, applicationSource.Name.ToApplicationSourceName(), deploymentScope); if (!deploymentScope.Matches(annotatedScope)) - return new ManifestUpdateResult(false, string.Empty); + return new ManifestUpdateResult(false, string.Empty, []); log.Info($"Writing files to repository '{applicationSource.OriginalRepoUrl}' for '{applicationName}'"); if (!TryCalculateOutputPath(applicationSource, out var outputPath)) { - return new ManifestUpdateResult(false, string.Empty); + return new ManifestUpdateResult(false, string.Empty, []); } var gitCredential = gitCredentials.GetValueOrDefault(applicationSource.OriginalRepoUrl); @@ -223,14 +224,14 @@ ManifestUpdateResult ProcessSource( var filesToCopy = packageFiles.Select(f => new FileCopySpecification(f, repository.WorkingDirectory, outputPath)).ToList(); CopyFiles(filesToCopy); + var fileHashes = filesToCopy.Select(f => new FilePathContent(f.DestinationRelativePath, HashCalculator.Hash(f.DestinationAbsolutePath))).ToList(); + log.Info("Staging files in repository"); repository.StageFiles(filesToCopy.Select(fcs => fcs.DestinationRelativePath).ToArray()); log.Info("Commiting changes"); if (repository.CommitChanges(deploymentConfig.CommitParameters.Summary, deploymentConfig.CommitParameters.Description)) { - var commitSha = repository.GetCommitSha(); - log.Info("Changes were commited, pushing to remote"); var pushResult = repository.PushChanges(deploymentConfig.CommitParameters.RequiresPr, deploymentConfig.CommitParameters.Summary, @@ -246,14 +247,16 @@ ManifestUpdateResult ProcessSource( applicationFromYaml.Metadata.Name, sourceWithMetadata.Index, pushResult); + + return new ManifestUpdateResult(true, pushResult.CommitSha, fileHashes); } - return new ManifestUpdateResult(true, commitSha); + return new ManifestUpdateResult(false, string.Empty, []); } log.Info("No changes were commited"); - return new ManifestUpdateResult(false, string.Empty); + return new ManifestUpdateResult(false, string.Empty, []); } bool TryCalculateOutputPath(ApplicationSource sourceToUpdate, out string outputPath) @@ -331,6 +334,6 @@ IPackageRelativeFile[] GetReferencedPackageFiles(ArgoCommitToGitConfig config) IPackageRelativeFile[] SelectFiles(string pathToExtractedPackageFiles, ArgoCommitToGitConfig config) => argoCDManifestsFileMatcher.FindMatchingPackageFiles(pathToExtractedPackageFiles, config.InputSubPath); - record ManifestUpdateResult(bool Updated, string CommitSha); + record ManifestUpdateResult(bool Updated, string CommitSha, List ReplacedFiles); } } \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Helm/HelmUpdateImageResult.cs b/source/Calamari/ArgoCD/Helm/HelmUpdateImageResult.cs index 5838f5f82..68c108cf7 100644 --- a/source/Calamari/ArgoCD/Helm/HelmUpdateImageResult.cs +++ b/source/Calamari/ArgoCD/Helm/HelmUpdateImageResult.cs @@ -3,16 +3,6 @@ namespace Calamari.ArgoCD.Helm { - public class HelmRefUpdatedResult - { - public HelmRefUpdatedResult(HashSet imagesUpdated, string relativeFilepath) - { - ImagesUpdated = imagesUpdated; - RelativeFilepath = relativeFilepath; - } - - public HashSet ImagesUpdated { get; } - public string RelativeFilepath { get; } - } + public record HelmRefUpdatedResult(HashSet ImagesUpdated, string RelativeFilepath, string JsonPatch); }