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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,11 @@ public DateTime GetCreationTime(string filePath)
return File.GetCreationTime(filePath);
}

public DateTime GetLastWriteTime(string filePath)
{
return File.GetLastWriteTime(filePath);
}

public string GetFileName(string filePath)
{
return new FileInfo(filePath).Name;
Expand Down
6 changes: 6 additions & 0 deletions source/Calamari.Common/Plumbing/FileSystem/FileOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public interface IFile
void Move(string sourceFile, string destination);
void SetAttributes(string path, FileAttributes normal);
DateTime GetCreationTime(string filePath);
DateTime GetLastWriteTime(string filePath);
Stream Open(string filePath, FileMode fileMode, FileAccess fileAccess, FileShare none);
}

Expand Down Expand Up @@ -73,6 +74,11 @@ public DateTime GetCreationTime(string path)
return File.GetCreationTime(path);
}

public DateTime GetLastWriteTime(string path)
{
return File.GetLastWriteTime(path);
}

public Stream Open(string path, FileMode mode, FileAccess access, FileShare share)
{
return File.Open(path, mode, access, share);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public interface ICalamariFileSystem
string GetRelativePath(string fromFile, string toFile);
Stream OpenFileExclusively(string filePath, FileMode fileMode, FileAccess fileAccess);
DateTime GetCreationTime(string filePath);
DateTime GetLastWriteTime(string filePath);
string GetFileName(string filePath);
string GetDirectoryName(string directoryPath);
byte[] ReadAllBytes(string filePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,21 @@ public IEnumerable<PackagePhysicalFileMetadata> GetNearestPackages(string packag
{
fileSystem.EnsureDirectoryExists(GetPackagesDirectory());

var zipPackages =
var allMatchingPackages =
from filePath in PackageFiles(packageId)
let zip = PackageMetadata(filePath)
where zip != null && string.Equals(zip.PackageId, packageId, StringComparison.OrdinalIgnoreCase) && zip.Version.CompareTo(version) <= 0
orderby zip.Version descending
select new {zip, filePath};
where zip != null && string.Equals(zip.PackageId, packageId, StringComparison.OrdinalIgnoreCase)
select new { zip, filePath };

// For SemVer versions, find packages with version <= target ordered by version descending (closest lower version first).
// For non-SemVer versions (Docker tags, build numbers etc.) SemVer comparison is meaningless, so fall back to
// ordering by file creation time — the most recently cached package is the best delta candidate.
var zipPackages = version.Format == VersionFormat.Semver
? allMatchingPackages
.Where(x => x.zip.Version.CompareTo(version) <= 0)
.OrderByDescending(x => x.zip.Version)
: allMatchingPackages
.OrderByDescending(x => fileSystem.GetLastWriteTime(x.filePath));

return
from zipPackage in zipPackages.Take(take)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Calamari.Testing.Helpers;
using Calamari.Tests.Fixtures.Deployment.Packages;
using NUnit.Framework;
using Octopus.Versioning;
using Octopus.Versioning.Semver;

namespace Calamari.Tests.Fixtures.Integration.FileSystem
Expand Down Expand Up @@ -90,6 +91,58 @@ public void IgnoresInvalidFiles()
}
}

[Test]
public void NonSemVerVersionsReturnPackagesOrderedByLastWriteTime()
{
// For non-SemVer tags (Docker tags, build numbers etc.) version comparison is unreliable.
// GetNearestPackages should fall back to file last-write time so the most recently
// cached package is offered as the delta candidate.
//
// We bypass PackageBuilder.BuildSamplePackage here because `dotnet pack /p:Version=...`
// rejects non-SemVer versions. We also use .zip rather than .nupkg so PackageName.FromFile
// does not try to read NuGet metadata from the file content.
var v1Path = CreateDummyCachedFile("feature-login-100");
var v2Path = CreateDummyCachedFile("main-9999");
var v3Path = CreateDummyCachedFile("feature-signup-42");

// Set deterministic last-write times: v2 is most recent, then v3, then v1.
// Using SetLastWriteTimeUtc rather than SetCreationTimeUtc because creation time
// is read-only on Linux filesystems.
File.SetLastWriteTimeUtc(v1Path, DateTime.UtcNow.AddMinutes(-30));
File.SetLastWriteTimeUtc(v3Path, DateTime.UtcNow.AddMinutes(-15));
File.SetLastWriteTimeUtc(v2Path, DateTime.UtcNow.AddMinutes(-5));

using (new TemporaryFile(v1Path))
using (new TemporaryFile(v2Path))
using (new TemporaryFile(v3Path))
{
var store = new PackageStore(
CreatePackageExtractor(),
CalamariPhysicalFileSystem.GetPhysicalFileSystem()
);

var target = VersionFactory.TryCreateDockerTag("feature-new-99");
var packages = store.GetNearestPackages("Acme.Web", target).ToList();

// All three are returned (no version filter), ordered by last-write time descending
Assert.That(packages.Count, Is.EqualTo(3));
Assert.That(packages[0].Version.ToString(), Is.EqualTo("main-9999"));
Assert.That(packages[1].Version.ToString(), Is.EqualTo("feature-signup-42"));
Assert.That(packages[2].Version.ToString(), Is.EqualTo("feature-login-100"));
}
}

private string CreateDummyCachedFile(string version)
{
var dockerVersion = VersionFactory.TryCreateDockerTag(version)!;
// Use .zip rather than .nupkg — .nupkg files trigger NuGet metadata parsing inside
// PackageName.FromFile, which would fail on these dummy files. The PackageStore only
// reads file metadata (hash, size, last-write time), not the file contents themselves.
var destinationPath = Path.Combine(PackagePath, PackageName.ToCachedFileName("Acme.Web", dockerVersion, ".zip"));
File.WriteAllText(destinationPath, "dummy content for " + version);
return destinationPath;
}

private string CreateEmptyFile(string version)
{
var destinationPath = Path.Combine(PackagePath, PackageName.ToCachedFileName("Acme.Web", new SemanticVersion(version), ".nupkg"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class TestFile : IFile
public bool Exists(string path) => File.Exists(WithBase(path));
public byte[] ReadAllBytes(string path) => File.ReadAllBytes(WithBase(path));
public DateTime GetCreationTime(string filePath) => File.GetCreationTime(WithBase(filePath));
public DateTime GetLastWriteTime(string filePath) => File.GetLastWriteTime(WithBase(filePath));
public Stream Open(string filePath, FileMode fileMode, FileAccess fileAccess, FileShare none) =>
File.Open(WithBase(filePath), fileMode, fileAccess, none);

Expand Down