diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index bf0a3b58..40e55bc6 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -34,7 +34,6 @@ "CleanProjects", "PackBinaries", "PreRelease", - "PreReleaseContainer", "PublishBinaries", "PublishContainer", "Release", diff --git a/Directory.Packages.props b/Directory.Packages.props index 7e33ca09..efad11c6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,8 @@ true - + + diff --git a/build-utils/Build.Utilities/Build.Utilities.csproj b/build-utils/Build.Utilities/Build.Utilities.csproj index b09e7820..00736451 100644 --- a/build-utils/Build.Utilities/Build.Utilities.csproj +++ b/build-utils/Build.Utilities/Build.Utilities.csproj @@ -1,7 +1,8 @@  - + + diff --git a/build-utils/Build.Utilities/ContainerImage/NukeExtensions.cs b/build-utils/Build.Utilities/ContainerImage/NukeExtensions.cs deleted file mode 100644 index f23386d4..00000000 --- a/build-utils/Build.Utilities/ContainerImage/NukeExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using HLabs.Containers; -using Nuke.Common.Tools.Docker; - -namespace Drift.Build.Utilities.ContainerImage; - -public static class NukeExtensions { - public static DockerBuildSettings SetTag( this DockerBuildSettings settings, ImageReference imageReference ) { - return settings.SetTag( imageReference.ToString() ); - } - - public static DockerTagSettings SetSourceImage( this DockerTagSettings settings, ImageReference imageReference ) { - return settings.SetSourceImage( imageReference.ToString() ); - } - - public static DockerTagSettings SetTargetImage( this DockerTagSettings settings, ImageReference imageReference ) { - return settings.SetTargetImage( imageReference.ToString() ); - } - - public static DockerLoginSettings SetServer( this DockerLoginSettings settings, ContainerRegistry registry ) { - return settings.SetServer( registry.ToString() ); - } - - public static DockerPushSettings SetName( this DockerPushSettings settings, ImageReference imageReference ) { - return settings.SetName( imageReference.ToString() ); - } -} \ No newline at end of file diff --git a/build-utils/Build.Utilities/Versioning/Abstractions/IVersioningStrategy.cs b/build-utils/Build.Utilities/Versioning/Abstractions/IVersioningStrategy.cs index bc0c6b45..9717cdec 100644 --- a/build-utils/Build.Utilities/Versioning/Abstractions/IVersioningStrategy.cs +++ b/build-utils/Build.Utilities/Versioning/Abstractions/IVersioningStrategy.cs @@ -1,4 +1,4 @@ -using HLabs.Containers; +using HLabs.ImageReferences; using Semver; namespace Drift.Build.Utilities.Versioning.Abstractions; @@ -16,5 +16,5 @@ public interface IReleaseInfo { Task GetGitTagAsync(); - Task> GetImageReferences(); + Task> GetImageReferences(); } \ No newline at end of file diff --git a/build-utils/Build.Utilities/Versioning/ReleaseVersioningBase.cs b/build-utils/Build.Utilities/Versioning/ReleaseVersioningBase.cs index cb4cf4a7..171c2dc1 100644 --- a/build-utils/Build.Utilities/Versioning/ReleaseVersioningBase.cs +++ b/build-utils/Build.Utilities/Versioning/ReleaseVersioningBase.cs @@ -1,5 +1,5 @@ using Drift.Build.Utilities.Versioning.Abstractions; -using HLabs.Containers; +using HLabs.ImageReferences; using JetBrains.Annotations; using Nuke.Common; using Nuke.Common.Git; @@ -51,9 +51,9 @@ public async Task GetGitTagAsync() { return _cachedGitTag; } - public virtual async Task> GetImageReferences() { + public virtual async Task> GetImageReferences() { return [ - ImageReference.DockerIo( "hojmark", "drift", await GetVersionAsync() ) + "hojmark/drift".Image().Qualify( await GetVersionAsync() ) ]; } diff --git a/build-utils/Build.Utilities/Versioning/Strategies/ReleaseVersioning.cs b/build-utils/Build.Utilities/Versioning/Strategies/ReleaseVersioning.cs index ca8c57f5..f4d62a19 100644 --- a/build-utils/Build.Utilities/Versioning/Strategies/ReleaseVersioning.cs +++ b/build-utils/Build.Utilities/Versioning/Strategies/ReleaseVersioning.cs @@ -1,6 +1,6 @@ using System.Globalization; using Drift.Build.Utilities.Versioning.Abstractions; -using HLabs.Containers; +using HLabs.ImageReferences; using Nuke.Common.Git; using Nuke.Common.Tools.GitHub; using Octokit; @@ -51,9 +51,9 @@ public override async Task GetNameAsync() { return CreateReleaseName( await GetVersionAsync(), includeMetadata: false ); } - public override async Task> GetImageReferences() { + public override async Task> GetImageReferences() { return [ - ImageReference.DockerIo( "hojmark", "drift", Tag.Latest ), + "hojmark/drift".Image().Qualify( Tag.Latest ), ..await base.GetImageReferences() ]; } diff --git a/build/NukeBuild.Container.cs b/build/NukeBuild.Container.cs index 1057e5c9..75c155b6 100644 --- a/build/NukeBuild.Container.cs +++ b/build/NukeBuild.Container.cs @@ -1,10 +1,13 @@ +using System; using System.Linq; using Drift.Build.Utilities; -using Drift.Build.Utilities.ContainerImage; -using HLabs.Containers; +using HLabs.ImageReferences; +using HLabs.ImageReferences.Extensions.Nuke; +using JetBrains.Annotations; using Nuke.Common; using Nuke.Common.Tools.Docker; using Serilog; +using Versioning; // ReSharper disable VariableHidesOuterVariable // ReSharper disable AllUnderscoreLocalParameterName @@ -23,23 +26,28 @@ partial class NukeBuild { [Parameter( "DockerHubPassword - Required for releasing container images to Docker Hub" )] public readonly string DockerHubPassword; + private static readonly PartialImageRef DriftImage = new("drift"); + private static readonly PartialImageRef LocalDriftImage = DriftImage.With( Registry.Localhost ); + private static readonly PartialImageRef DockerHubDriftImage = DriftImage.With( Registry.DockerHub, "hojmark" ); + [CanBeNull] private QualifiedImageRef _driftImageRef; + Target PublishContainer => _ => _ .DependsOn( PublishBinaries, CleanArtifacts ) .Requires( () => Commit ) .Executes( async () => { using var _ = new OperationTimer( nameof(PublishContainer) ); - // TODO use GetContainerImageReferences (.WithRepo(repo)) var version = await Versioning.Value.GetVersionAsync(); - var localTagVersion = ImageReference.Localhost( "drift", version ); - // var created = DateTime.UtcNow.ToString( "o", CultureInfo.InvariantCulture ); // o = round-trip format / ISO 8601 + var tag = new Tag( Guid.NewGuid().ToString( "N" ) ); + _driftImageRef = LocalDriftImage.Qualify( tag ); - Log.Information( "Building container image {Tag}", localTagVersion ); + Log.Information( "Building container image..." ); + // var created = DateTime.UtcNow.ToString( "o", CultureInfo.InvariantCulture ); // o = round-trip format / ISO 8601 DockerTasks.DockerBuild( s => s .SetPath( RootDirectory ) + .SetTag( _driftImageRef ) .SetFile( "Containerfile" ) - .SetTag( localTagVersion ) .SetLabel( // Timestamping prevents the build from being idempotent // $"\"org.opencontainers.image.created={created}\"", @@ -48,12 +56,14 @@ partial class NukeBuild { ) ); - // For convenience, tag the image with dev as well - var localTagDev = localTagVersion.WithTag( Tag.Dev ); - Log.Information( "Re-tagging {LocalTagVersion} -> {LocalTagDev}", localTagVersion, localTagDev ); + Log.Information( "Built image: {ImageRef}", _driftImageRef ); + + Log.Information( "Tagging image..." ); + // For convenience, tag the image with dev + var devTag = LocalDriftImage.Qualify( Tag.Dev ); DockerTasks.DockerTag( s => s - .SetSourceImage( localTagVersion ) - .SetTargetImage( localTagDev ) + .SetSourceImage( _driftImageRef ) + .SetTargetImage( devTag ) ); } ); @@ -67,20 +77,18 @@ partial class NukeBuild { .Executes( async () => { using var _ = new OperationTimer( nameof(ReleaseContainer) ); - var version = await Versioning.Value.GetVersionAsync(); - var local = ImageReference.Localhost( "drift", version ); - var @public = ( await Versioning.Value.Release!.GetImageReferences() ) + var publicReferences = ( await Versioning.Value.Release!.GetImageReferences() ) .OrderBy( LatestLast ) // Pushing 'latest' last will make sure it appears as the most recent tag on Docker Hub .ToArray(); - Push( local, @public.ToArray() ); + Push( _driftImageRef, publicReferences.ToArray() ); - var repos = @public.Select( r => r.Host ).Distinct(); + var registries = publicReferences.Select( r => r.Registry ).Distinct(); - Log.Information( "🐋 Released to {Repositories}!", string.Join( " and ", repos ) ); + Log.Information( "🐋 Released to {Registries}!", string.Join( " and ", registries ) ); } ); - +/* /// /// Releases container image to public Docker Hub! /// @@ -90,45 +98,45 @@ partial class NukeBuild { .Executes( async () => { using var _ = new OperationTimer( nameof(PreReleaseContainer) ); - var version = await Versioning.Value.GetVersionAsync(); - var local = ImageReference.Localhost( "drift", version ); - var publicc = await Versioning.Value.Release!.GetImageReferences(); + var publicReferences = await Versioning.Value.Release!.GetImageReferences(); - Push( local, publicc.ToArray() ); + Push( ImageIdDrift, publicReferences.ToArray() ); - var repos = publicc.DistinctBy( r => r.Repository ); + var registries = publicReferences.DistinctBy( r => r.Registry ); - Log.Information( "🐋 Released to {Repositories}!", string.Join( " and ", repos ) ); + Log.Information( "🐋 Released to {Registries}!", string.Join( " and ", registries ) ); } - ); + );*/ - private static int LatestLast( ImageReference r ) { - return r.Tag is LatestTag ? 1 : 0; + private static int LatestLast( ImageRef r ) { + return r.Tag == Tag.Latest ? 1 : 0; } - private void Push( ImageReference source, params ImageReference[] targets ) { - ImageReference[] allReferences = [source, ..targets]; - var loginToDockerHub = allReferences.Any( reference => reference.Host == DockerIoRegistry.Instance ); + private void Push( QualifiedImageRef source, params QualifiedImageRef[] targets ) { + var logInToDockerHub = targets.Any( r => r.Registry == Registry.DockerHub ); try { - if ( loginToDockerHub ) { + if ( logInToDockerHub ) { DockerHubLogin(); } Log.Debug( - "Pushing {SourceTag} to: {TargetTags}", + "Pushing {Source} to: {Targets}", source, string.Join( ", ", targets.Select( t => t.ToString() ) ) ); foreach ( var target in targets ) { - Log.Debug( "Re-tagging {SourceTag} -> {TargetTag}", source, target ); + // Unfortunately, Docker (unlike Podman) does not support pushing an image selected by image ID directly to a + // registry tag (image ID → registry:tag). A local tag must be created first, which prevents this from being a + // single, atomic operation. + Log.Debug( "Tagging {Source} with {Target}", source, target ); DockerTasks.DockerTag( s => s .SetSourceImage( source ) .SetTargetImage( target ) ); - Log.Information( "Pushing {TargetTag}", target ); + Log.Information( "Pushing {Target}", target ); DockerTasks.DockerPush( s => s .SetName( target ) ); @@ -137,7 +145,7 @@ private void Push( ImageReference source, params ImageReference[] targets ) { } } finally { - if ( loginToDockerHub ) { + if ( logInToDockerHub ) { DockerHubLogout(); } } @@ -145,19 +153,15 @@ private void Push( ImageReference source, params ImageReference[] targets ) { private void DockerHubLogin() { Log.Debug( "Logging in to Docker Hub" ); - DockerTasks.DockerLogin( s => s + .SetServer( Registry.DockerHub ) .SetUsername( DockerHubUsername ) .SetPassword( DockerHubPassword ) - .SetServer( DockerIoRegistry.Instance ) ); } private static void DockerHubLogout() { Log.Debug( "Logging out of Docker Hub" ); - - DockerTasks.DockerLogout( s => s - .SetServer( "docker.io" ) - ); + DockerTasks.DockerLogout( s => s.SetServer( Registry.DockerHub ) ); } } \ No newline at end of file diff --git a/build/NukeBuild.Release.cs b/build/NukeBuild.Release.cs index 0795b8e5..1993a483 100644 --- a/build/NukeBuild.Release.cs +++ b/build/NukeBuild.Release.cs @@ -29,7 +29,7 @@ internal partial class NukeBuild { ); public Target PreRelease => _ => _ - .DependsOn( PackBinaries, PreReleaseContainer, Test ) + .DependsOn( PackBinaries, ReleaseContainer, Test ) .Executes( async () => { using var _ = new OperationTimer( nameof(PreRelease) ); diff --git a/build/NukeBuild.Test.cs b/build/NukeBuild.Test.cs index dfb2e038..dfdbc08c 100644 --- a/build/NukeBuild.Test.cs +++ b/build/NukeBuild.Test.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Drift.Build.Utilities; using Drift.Build.Utilities.MsBuild; -using HLabs.Containers; using JetBrains.Annotations; using Nuke.Common; using Nuke.Common.Tooling; @@ -71,17 +70,16 @@ sealed partial class NukeBuild { .Executes( async () => { using var _ = new OperationTimer( nameof(TestE2E) ); - var version = await Versioning.Value.GetVersionAsync(); + var imageRef = _driftImageRef ?? throw new ArgumentNullException( nameof(_driftImageRef) ); + Log.Information( "Using image {ImageRef}", imageRef ); foreach ( var runtime in SupportedRuntimes ) { var driftBinary = Paths.PublishDirectoryForRuntime( runtime ) / "drift"; var envVars = new Dictionary { // { nameof(EnvVar.DRIFT_BINARY_PATH), driftBinary }, - { "DRIFT_BINARY_PATH", driftBinary }, - // TODO use this! - // { "DRIFT_CONTAINER_IMAGE_REF", ImageReference.Localhost( "drift", version ).ToString() } - { "DRIFT_CONTAINER_IMAGE_REF", ImageReference.Localhost( "drift", version ).ToString() } + { "DRIFT_BINARY_PATH", driftBinary }, // + { "DRIFT_CONTAINER_IMAGE_REF", imageRef.ToString() } }; var alternateDockerHost = await FindAlternateDockerHostAsync(); diff --git a/build/Versioning/SemVersionExtensions.cs b/build/Versioning/SemVersionExtensions.cs index 68b22572..ddbb0b12 100644 --- a/build/Versioning/SemVersionExtensions.cs +++ b/build/Versioning/SemVersionExtensions.cs @@ -4,6 +4,9 @@ namespace Versioning; internal static class SemVersionExtensions { + /* + * NUKE build extensions + */ internal static DotNetBuildSettings SetVersionProperties( this DotNetBuildSettings settings, SemVersion version @@ -27,7 +30,6 @@ SemVersion version /* * Below methods skips metadata because .NET add the commit hash by itself (somewhere?) */ - internal static string ToDotNetAssemblyVersion( this SemVersion version ) { // Takes major.minor.patch and optional build number return version.WithoutPrereleaseOrMetadata().ToString(); diff --git a/build/Versioning/TagExtensions.cs b/build/Versioning/TagExtensions.cs new file mode 100644 index 00000000..93ea7457 --- /dev/null +++ b/build/Versioning/TagExtensions.cs @@ -0,0 +1,9 @@ +using HLabs.ImageReferences; + +namespace Versioning; + +internal static class TagExtensions { + extension( Tag ) { + public static Tag Dev => new("dev"); + } +} \ No newline at end of file diff --git a/build/_build.csproj b/build/_build.csproj index ec2c6995..6b7e5a43 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Cli.E2ETests/Cli.E2ETests.csproj b/src/Cli.E2ETests/Cli.E2ETests.csproj index a80e4cdf..edd8554e 100644 --- a/src/Cli.E2ETests/Cli.E2ETests.csproj +++ b/src/Cli.E2ETests/Cli.E2ETests.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Cli.E2ETests/DriftImageFixture.cs b/src/Cli.E2ETests/DriftImageFixture.cs index 39597418..1821460f 100644 --- a/src/Cli.E2ETests/DriftImageFixture.cs +++ b/src/Cli.E2ETests/DriftImageFixture.cs @@ -1,12 +1,12 @@ using System.Text.Json; using Drift.Cli.E2ETests.Abstractions; -using HLabs.Containers; +using HLabs.ImageReferences; using Nuke.Common.Tools.Docker; namespace Drift.Cli.E2ETests; internal abstract class DriftImageFixture { - protected static ImageReference DriftImage { + protected static QualifiedImageRef DriftImage { get; private set; } @@ -14,10 +14,8 @@ protected static ImageReference DriftImage { [OneTimeSetUp] public void Setup() { try { - // TODO get from env var - // var reference = EnvironmentVariable.GetOrThrow( EnvVar.DRIFT_CONTAINER_IMAGE_REF ); - // DriftImage = ImageReference.Parse( reference ); - DriftImage = ImageReference.Localhost( "drift", Tag.Dev ); + var reference = EnvironmentVariable.GetOrThrow( EnvVar.DRIFT_CONTAINER_IMAGE_REF ); + DriftImage = reference.QualifiedImage(); } catch ( Exception e ) { if ( !TestUtilities.Environment.IsCi() ) {