Skip to content

Commit e82564a

Browse files
committed
Enhance HasUnresolvedVariables method to support double underscore and hash-delimited tokens in image references
1 parent 89ea7c6 commit e82564a

2 files changed

Lines changed: 67 additions & 3 deletions

File tree

src/Microsoft.ComponentDetection.Common/DockerReference/DockerReferenceUtility.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ namespace Microsoft.ComponentDetection.Common;
2828

2929
using System;
3030
using System.Diagnostics.CodeAnalysis;
31+
using System.Text.RegularExpressions;
3132
using Microsoft.ComponentDetection.Contracts;
3233
using Microsoft.Extensions.Logging;
3334

@@ -39,14 +40,29 @@ public static class DockerReferenceUtility
3940
private const string LEGACYDEFAULTDOMAIN = "index.docker.io";
4041
private const string OFFICIALREPOSITORYNAME = "library";
4142

43+
// Characters that only appear in an image reference as part of an unresolved templating
44+
// token. '$', '{' and '}' cover shell / Helm / Go-template placeholders (e.g. ${VAR},
45+
// {{ .Values.tag }}); '#' covers Azure DevOps and other token-replacement placeholders
46+
// (e.g. #imageTag#) and is never valid in a resolved docker reference.
47+
private static readonly char[] TemplateDelimiters = ['$', '{', '}', '#'];
48+
49+
// Matches token-replacement placeholders that wrap an identifier in double underscores,
50+
// e.g. __IMAGE_TAG__ or __MCR_ENDPOINT__. Without this they parse as an uppercase repository
51+
// name and surface as a noisy parse failure instead of being skipped as a templated value.
52+
private static readonly Regex DoubleUnderscoreTokenRegex = new(@"__\w+__");
53+
4254
/// <summary>
43-
/// Returns true if the reference contains unresolved variable placeholders (e.g., ${VAR}, {{ .Values.tag }}).
44-
/// Such references should be skipped before calling <see cref="ParseFamiliarName"/> or <see cref="ParseQualifiedName"/>.
55+
/// Returns true if the reference contains unresolved variable or templating placeholders,
56+
/// e.g. <c>${VAR}</c>, <c>{{ .Values.tag }}</c>, <c>#imageTag#</c>, or <c>__IMAGE_TAG__</c>.
57+
/// Such references are not real, resolvable images, so they should be skipped before calling
58+
/// <see cref="ParseFamiliarName"/> or <see cref="ParseQualifiedName"/> and treated as
59+
/// unresolved values rather than reported as parse failures.
4560
/// </summary>
4661
/// <param name="reference">The image reference string to check.</param>
4762
/// <returns><c>true</c> if the reference contains variable placeholder characters; otherwise <c>false</c>.</returns>
4863
public static bool HasUnresolvedVariables(string reference) =>
49-
reference.IndexOfAny(['$', '{', '}']) >= 0;
64+
reference.IndexOfAny(TemplateDelimiters) >= 0 ||
65+
DoubleUnderscoreTokenRegex.IsMatch(reference);
5066

5167
/// <summary>
5268
/// Attempts to parse an image reference string into a <see cref="DockerReference"/>.

test/Microsoft.ComponentDetection.Common.Tests/DockerReferenceUtilityTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,18 +284,66 @@ public void HasUnresolvedVariables_ReturnsTrueForBraces()
284284
DockerReferenceUtility.HasUnresolvedVariables("{{ .Values.image }}").Should().BeTrue();
285285
}
286286

287+
[TestMethod]
288+
public void HasUnresolvedVariables_ReturnsTrueForDoubleUnderscoreTokens()
289+
{
290+
DockerReferenceUtility.HasUnresolvedVariables("__MCR_ENDPOINT__/aks/devinfra/helm3sample:__IMAGE_TAG__").Should().BeTrue();
291+
}
292+
293+
[TestMethod]
294+
public void HasUnresolvedVariables_ReturnsTrueForHashDelimitedTokens()
295+
{
296+
DockerReferenceUtility.HasUnresolvedVariables("#cs_containerRegistryLoginServerUrl#/coreservicesaksservice_#cs_aks_workloadName#_#cs_aks_serviceTrackIdentifier#/#serviceName#:#imageTag#").Should().BeTrue();
297+
}
298+
287299
[TestMethod]
288300
public void HasUnresolvedVariables_ReturnsFalseForPlainReference()
289301
{
290302
DockerReferenceUtility.HasUnresolvedVariables("docker.io/library/nginx:latest").Should().BeFalse();
291303
}
292304

305+
[TestMethod]
306+
public void HasUnresolvedVariables_ReturnsFalseForReferenceWithUnderscores()
307+
{
308+
DockerReferenceUtility.HasUnresolvedVariables("mcr.microsoft.com/some_repo/my_image:1.0").Should().BeFalse();
309+
}
310+
293311
[TestMethod]
294312
public void TryParseImageReference_ReturnsNullForUnresolvedVariables()
295313
{
296314
DockerReferenceUtility.TryParseImageReference("${IMAGE}:latest").Should().BeNull();
297315
}
298316

317+
[TestMethod]
318+
public void TryParseImageReference_ReturnsNullForDoubleUnderscoreTokens()
319+
{
320+
DockerReferenceUtility.TryParseImageReference("__MCR_ENDPOINT__/aks/devinfra/helm3sample:__IMAGE_TAG__").Should().BeNull();
321+
}
322+
323+
[TestMethod]
324+
public void TryParseImageReference_ReturnsNullForHashDelimitedTokens()
325+
{
326+
DockerReferenceUtility.TryParseImageReference("#cs_containerRegistryLoginServerUrl#/svc/#serviceName#:#imageTag#").Should().BeNull();
327+
}
328+
329+
[TestMethod]
330+
public void TryParseImageReference_DoesNotLogWarningForTemplatedReference()
331+
{
332+
var logger = new Mock<ILogger>();
333+
334+
var result = DockerReferenceUtility.TryParseImageReference("__MCR_ENDPOINT__/aks/devinfra/helm3sample:__IMAGE_TAG__", logger.Object);
335+
336+
result.Should().BeNull();
337+
logger.Verify(
338+
l => l.Log(
339+
It.IsAny<LogLevel>(),
340+
It.IsAny<EventId>(),
341+
It.IsAny<It.IsAnyType>(),
342+
It.IsAny<Exception>(),
343+
It.IsAny<Func<It.IsAnyType, Exception, string>>()),
344+
Times.Never);
345+
}
346+
299347
[TestMethod]
300348
public void TryParseImageReference_ReturnsNullForInvalidReference()
301349
{

0 commit comments

Comments
 (0)