dotnet / sdk-container-builds

Libraries and build tooling to create container images from .NET projects using MSBuild

Home Page:https://learn.microsoft.com/en-us/dotnet/core/docker/publish-as-container

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Using ContainerImageTags with compile-time variables (GitVersion)

vyruz1986 opened this issue · comments

I like to use GitVersion to version my .NET applications, but I'm hitting a wall getting it to play nice with container builds.

So GitVersion calculates the semantic version according to git history and accompanying (optional) config file, their MSBuild Task integration inserts an MSBuild task which calculates the version and (among other things) exposes a set of version-related variables to MSBuild, and also sets the assembly version to the calculated semantic version.

Since one of the variables it sets is the standard Version variable, one would expect to be able to do something like this in the csproj file:

<ContainerImageTags>latest;$(Version)</ContainerImageTags>

Yet it does not seem to work, the build output is as follows:

Building image 'test-image' with tags latest on top of base image mcr.microsoft.com/dotnet/aspnet:7.0

Note the lacking actual version, it's only building 'with tags latest'
So first I was thinking it was a generic issue with compile-time variables, however I decided to test some more and tried this:

<ContainerImageTags>$(Version)</ContainerImageTags>

This did work:

Building image 'test-image' with tags 2.11.0 on top of base image mcr.microsoft.com/dotnet/aspnet:7.0

Note the 'with tags 2.11.0'

So I'm a bit stumped here, and beginning to suspect a bug in this repo.

Why is the $(Version) tag dropped from a semicolon-separated list of build tags while it does correctly get recognized when it's the sole tag?

Hi @vyruz1986 -

Before, when we set a default of $(Version), we set this default just before we actually made the container (in the ComputeContainerConfig target). This meant that various other build targets that might be overridden by a user (like PrepareForBuild) could be called to fill-in or generate the $(Version), and we'd just pick that up. With the move to use latest as the tag, a user might be tempted to put something like this directly in their project file - exactly like you have:

<PropertyGroup>
  <ContainerImageTag>$(Version)</ContainerImageTag>
</PropertyGroup>

This won't work unless $(Version) is being passed in on the command line as part of the build - because this property will be set before any of those automatic versioning targets are run. (Think tools like NerdBank.GitVersioning, or GitVersion, etc). The workaround is to put a target somewhere after the version is determined by GitVersion but before the container publish occurs into either your project file, or a Directory.Build.targets file in your repo. That target would be very simple and look something like this:

<Target Name="ApplyVersionToContainerTag" AfterTargets="PrepareForBuild">
 <PropertyGroup>
    <ContainerImageTag>$(Version)</ContainerImageTag>
  </PropertyGroup>
</Target>

I've used PrepareForBuild here, but any time after GitVersion's targets occur would also work.

Can you give that a try and see if that fixes the problem for you? We may need to document this pattern in the documentation updates we're planning for .NET 8 cc @IEvangelist.

We may need some work/thought here to see if there's a way to detect if the version was set explicitly vs implicitly in the MSBuild Common Targets - if so we could maybe add the Version to the list of tags to push?

@baronfel Thanks for the explanation. This is more or less (I lack even basic knowledge of MSBuild...) what I would have concluded in case <ContainerImageTags>$(Version)</ContainerImageTags> would have actually failed, but that was not the case.

As I mentioned, <ContainerImageTags>$(Version)</ContainerImageTags> works.
While <ContainerImageTags>$(Version);latest</ContainerImageTags> does not.

Only adding another version to the ContainerImageTags attribute should not have impact on the order of build tasks, I think?

I did try adding

<Target Name="ApplyVersionToContainerTag" AfterTargets="PrepareForBuild">
 <PropertyGroup>
    <ContainerImageTag>$(Version)</ContainerImageTag>
  </PropertyGroup>
</Target>

To my csproj file, this correcly builds latest and a version, however the version always is 1.0.0, instead of what GitVersion is generating, so it seems like it's using some kind of default

@vyruz1986 if you have an https://aka.ms/binlog you can share I could take a look - what I expect is that the ordering of GitVersion targets is such that GitVersion's changes are applying after your target.

Try the same Target, but set AfterTargets="GetVersion" instead - this is the target that actually is used by GitVersion to compute the version.

@baronfel that did the trick. I won't take up more of your time to analyze my binlogs, I also admit I'm not really interested right now in finding out exactly why it does work 'out of the box' with only the $(Version) tag. Thanks for offering though!