diff --git a/.gitignore b/.gitignore index 533090632..1a86942c2 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ launchSettings.json # Dev nupkgs dev-source +.nuget/ diff --git a/docs/detectors/dockerfile.md b/docs/detectors/dockerfile.md index 8143c031a..9206d3d20 100644 --- a/docs/detectors/dockerfile.md +++ b/docs/detectors/dockerfile.md @@ -29,4 +29,4 @@ The detector attempts to resolve Dockerfile variables using the `ResolveVariable - **DefaultOff Status**: This detector must be explicitly enabled using `--DetectorArgs DockerReference=EnableIfDefaultOff` - **Variable Resolution**: Image references containing unresolved Dockerfile `ARG` or `ENV` variables are not reported, which may lead to under-reporting in Dockerfiles that heavily use build-time variables - **No Version Pinning Validation**: The detector does not warn about unpinned image versions (e.g., `latest` tags), which are generally discouraged in production Dockerfiles -- **No Digest Support**: While Docker supports content-addressable image references using SHA256 digests (e.g., `ubuntu@sha256:abc...`), the parsing and reporting of these references depends on the underlying `DockerReferenceUtility.ParseFamiliarName()` implementation +- **No Digest Support**: While Docker supports content-addressable image references using SHA256 digests (e.g., `ubuntu@sha256:abc...`), the parsing and reporting of these references depends on the underlying `ContainerImageReferenceUtility.ParseFamiliarName()` implementation diff --git a/docs/schema/manifest.schema.json b/docs/schema/manifest.schema.json index 0db3bdc7d..5bce9dc0d 100644 --- a/docs/schema/manifest.schema.json +++ b/docs/schema/manifest.schema.json @@ -150,6 +150,7 @@ "Conda", "Spdx", "Vcpkg", + "ContainerImageReference", "DockerReference", "Conan", "Swift", @@ -424,6 +425,7 @@ "Conda", "Spdx", "Vcpkg", + "ContainerImageReference", "DockerReference", "Conan", "Swift", diff --git a/src/Microsoft.ComponentDetection.Common/DockerReference/DockerReferenceException.cs b/src/Microsoft.ComponentDetection.Common/ContainerImageReference/ContainerImageReferenceException.cs similarity index 79% rename from src/Microsoft.ComponentDetection.Common/DockerReference/DockerReferenceException.cs rename to src/Microsoft.ComponentDetection.Common/ContainerImageReference/ContainerImageReferenceException.cs index 1ef1fec97..47c2d76e1 100644 --- a/src/Microsoft.ComponentDetection.Common/DockerReference/DockerReferenceException.cs +++ b/src/Microsoft.ComponentDetection.Common/ContainerImageReference/ContainerImageReferenceException.cs @@ -3,30 +3,30 @@ namespace Microsoft.ComponentDetection.Common; using System; -internal class DockerReferenceException : Exception +internal class ContainerImageReferenceException : Exception { - public DockerReferenceException(string reference, string exceptionErrorMessage) - : base($"Error while parsing docker reference {reference} : {exceptionErrorMessage}") + public ContainerImageReferenceException(string reference, string exceptionErrorMessage) + : base($"Error while parsing container image reference {reference} : {exceptionErrorMessage}") { } - public DockerReferenceException() + public ContainerImageReferenceException() { } - public DockerReferenceException(string message) + public ContainerImageReferenceException(string message) : base(message) { } - public DockerReferenceException(string message, Exception innerException) + public ContainerImageReferenceException(string message, Exception innerException) : base(message, innerException) { } } // ReferenceInvalidFormat represents an error while trying to parse a string as a reference. -internal class ReferenceInvalidFormatException : DockerReferenceException +internal class ReferenceInvalidFormatException : ContainerImageReferenceException { private const string ErrorMessage = "invalid reference format"; @@ -46,7 +46,7 @@ public ReferenceInvalidFormatException(string message, Exception innerException) } // TagInvalidFormat represents an error while trying to parse a string as a tag. -internal class ReferenceTagInvalidFormatException : DockerReferenceException +internal class ReferenceTagInvalidFormatException : ContainerImageReferenceException { private const string ErrorMessage = "invalid tag format"; @@ -66,7 +66,7 @@ public ReferenceTagInvalidFormatException(string message, Exception innerExcepti } // DigestInvalidFormat represents an error while trying to parse a string as a tag. -internal class ReferenceDigestInvalidFormatException : DockerReferenceException +internal class ReferenceDigestInvalidFormatException : ContainerImageReferenceException { private const string ErrorMessage = "invalid digest format"; @@ -86,7 +86,7 @@ public ReferenceDigestInvalidFormatException(string message, Exception innerExce } // NameContainsUppercase is returned for invalid repository names that contain uppercase characters. -internal class ReferenceNameContainsUppercaseException : DockerReferenceException +internal class ReferenceNameContainsUppercaseException : ContainerImageReferenceException { private const string ErrorMessage = "repository name must be lowercase"; @@ -106,7 +106,7 @@ public ReferenceNameContainsUppercaseException(string message, Exception innerEx } // NameEmpty is returned for empty, invalid repository names. -internal class ReferenceNameEmptyException : DockerReferenceException +internal class ReferenceNameEmptyException : ContainerImageReferenceException { private const string ErrorMessage = "repository name must have at least one component"; @@ -126,7 +126,7 @@ public ReferenceNameEmptyException(string message, Exception innerException) } // ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax. -internal class ReferenceNameTooLongException : DockerReferenceException +internal class ReferenceNameTooLongException : ContainerImageReferenceException { private const string ErrorMessage = "repository name must not be more than 255 characters"; @@ -146,7 +146,7 @@ public ReferenceNameTooLongException(string message, Exception innerException) } // ErrNameNotCanonical is returned when a name is not canonical. -internal class ReferenceNameNotCanonicalException : DockerReferenceException +internal class ReferenceNameNotCanonicalException : ContainerImageReferenceException { private const string ErrorMessage = "repository name must be canonical"; @@ -165,7 +165,7 @@ public ReferenceNameNotCanonicalException(string message, Exception innerExcepti } } -internal class InvalidDigestFormatError : DockerReferenceException +internal class InvalidDigestFormatError : ContainerImageReferenceException { private const string ErrorMessage = "invalid digest format"; @@ -184,7 +184,7 @@ public InvalidDigestFormatError(string message, Exception innerException) } } -internal class UnsupportedAlgorithmError : DockerReferenceException +internal class UnsupportedAlgorithmError : ContainerImageReferenceException { private const string ErrorMessage = "unsupported digest algorithm"; @@ -203,7 +203,7 @@ public UnsupportedAlgorithmError(string message, Exception innerException) } } -internal class InvalidDigestLengthError : DockerReferenceException +internal class InvalidDigestLengthError : ContainerImageReferenceException { private const string ErrorMessage = "invalid checksum digest length"; diff --git a/src/Microsoft.ComponentDetection.Common/DockerReference/DockerReferenceUtility.cs b/src/Microsoft.ComponentDetection.Common/ContainerImageReference/ContainerImageReferenceUtility.cs similarity index 83% rename from src/Microsoft.ComponentDetection.Common/DockerReference/DockerReferenceUtility.cs rename to src/Microsoft.ComponentDetection.Common/ContainerImageReference/ContainerImageReferenceUtility.cs index 022f8b612..1a088292c 100644 --- a/src/Microsoft.ComponentDetection.Common/DockerReference/DockerReferenceUtility.cs +++ b/src/Microsoft.ComponentDetection.Common/ContainerImageReference/ContainerImageReferenceUtility.cs @@ -30,7 +30,7 @@ namespace Microsoft.ComponentDetection.Common; using System.Diagnostics.CodeAnalysis; using Microsoft.ComponentDetection.Contracts; -public static class DockerReferenceUtility +public static class ContainerImageReferenceUtility { // NameTotalLengthMax is the maximum total number of characters in a repository name. private const int NameTotalLengthMax = 255; @@ -38,9 +38,9 @@ public static class DockerReferenceUtility private const string LEGACYDEFAULTDOMAIN = "index.docker.io"; private const string OFFICIALREPOSITORYNAME = "library"; - public static DockerReference ParseQualifiedName(string qualifiedName) + public static ContainerImageReference ParseQualifiedName(string qualifiedName) { - var regexp = DockerRegex.ReferenceRegexp; + var regexp = ContainerImageRegex.ReferenceRegexp; if (!regexp.IsMatch(qualifiedName)) { if (string.IsNullOrWhiteSpace(qualifiedName)) @@ -66,7 +66,7 @@ public static DockerReference ParseQualifiedName(string qualifiedName) var reference = new Reference(); - var nameMatch = DockerRegex.AnchoredNameRegexp.Match(name).Groups; + var nameMatch = ContainerImageRegex.AnchoredNameRegexp.Match(name).Groups; if (nameMatch.Count == 3) { reference.Domain = nameMatch[1].Value; @@ -86,7 +86,7 @@ public static DockerReference ParseQualifiedName(string qualifiedName) reference.Digest = matches[3].Value; } - return CreateDockerReference(reference); + return CreateContainerImageReference(reference); } public static (string Domain, string Remainder) SplitDockerDomain(string name) @@ -123,9 +123,9 @@ public static (string Domain, string Remainder) SplitDockerDomain(string name) } [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Explicitly checks for character case.")] - public static DockerReference ParseFamiliarName(string name) + public static ContainerImageReference ParseFamiliarName(string name) { - if (DockerRegex.AnchoredIdentifierRegexp.IsMatch(name)) + if (ContainerImageRegex.AnchoredIdentifierRegexp.IsMatch(name)) { throw new ReferenceNameNotCanonicalException(name); } @@ -151,23 +151,23 @@ public static DockerReference ParseFamiliarName(string name) return ParseQualifiedName($"{domain}/{remainder}"); } - public static DockerReference ParseAll(string name) + public static ContainerImageReference ParseAll(string name) { - if (DockerRegex.AnchoredIdentifierRegexp.IsMatch(name)) + if (ContainerImageRegex.AnchoredIdentifierRegexp.IsMatch(name)) { - return CreateDockerReference(new Reference { Digest = $"sha256:{name}" }); + return CreateContainerImageReference(new Reference { Digest = $"sha256:{name}" }); } if (DigestUtility.CheckDigest(name, false)) { - return CreateDockerReference(new Reference { Digest = name }); + return CreateContainerImageReference(new Reference { Digest = name }); } return ParseFamiliarName(name); } - private static DockerReference CreateDockerReference(Reference options) + private static ContainerImageReference CreateContainerImageReference(Reference options) { - return DockerReference.CreateDockerReference(options.Repository, options.Domain, options.Digest, options.Tag); + return ContainerImageReference.CreateContainerImageReference(options.Repository, options.Domain, options.Digest, options.Tag); } } diff --git a/src/Microsoft.ComponentDetection.Common/DockerReference/DockerRegex.cs b/src/Microsoft.ComponentDetection.Common/ContainerImageReference/ContainerImageRegex.cs similarity index 99% rename from src/Microsoft.ComponentDetection.Common/DockerReference/DockerRegex.cs rename to src/Microsoft.ComponentDetection.Common/ContainerImageReference/ContainerImageRegex.cs index 4e9af5a68..ba95c2fab 100644 --- a/src/Microsoft.ComponentDetection.Common/DockerReference/DockerRegex.cs +++ b/src/Microsoft.ComponentDetection.Common/ContainerImageReference/ContainerImageRegex.cs @@ -3,7 +3,7 @@ namespace Microsoft.ComponentDetection.Common; using System.Linq; using System.Text.RegularExpressions; -public static class DockerRegex +public static class ContainerImageRegex { public static readonly Regex AlphaNumericRegexp = new Regex("[a-z0-9]+"); public static readonly Regex SeparatorRegexp = new Regex("(?:[._]|__|[-]*)"); diff --git a/src/Microsoft.ComponentDetection.Common/DockerReference/DigestUtility.cs b/src/Microsoft.ComponentDetection.Common/ContainerImageReference/DigestUtility.cs similarity index 94% rename from src/Microsoft.ComponentDetection.Common/DockerReference/DigestUtility.cs rename to src/Microsoft.ComponentDetection.Common/ContainerImageReference/DigestUtility.cs index b1871c487..f8c83e7bf 100644 --- a/src/Microsoft.ComponentDetection.Common/DockerReference/DigestUtility.cs +++ b/src/Microsoft.ComponentDetection.Common/ContainerImageReference/DigestUtility.cs @@ -16,7 +16,7 @@ public static bool CheckDigest(string digest, bool throwError = true) var indexOfColon = digest.IndexOf(':'); if (indexOfColon < 0 || indexOfColon + 1 == digest.Length || - !DockerRegex.AnchoredDigestRegexp.IsMatch(digest)) + !ContainerImageRegex.AnchoredDigestRegexp.IsMatch(digest)) { if (throwError) { diff --git a/src/Microsoft.ComponentDetection.Contracts/BcdeModels/TypedComponentMapping.cs b/src/Microsoft.ComponentDetection.Contracts/BcdeModels/TypedComponentMapping.cs index 84661e658..bce3f06ba 100644 --- a/src/Microsoft.ComponentDetection.Contracts/BcdeModels/TypedComponentMapping.cs +++ b/src/Microsoft.ComponentDetection.Contracts/BcdeModels/TypedComponentMapping.cs @@ -31,7 +31,8 @@ internal static class TypedComponentMapping { nameof(ComponentType.Pod), typeof(PodComponent) }, { nameof(ComponentType.Linux), typeof(LinuxComponent) }, { nameof(ComponentType.Conda), typeof(CondaComponent) }, - { nameof(ComponentType.DockerReference), typeof(DockerReferenceComponent) }, + { nameof(ComponentType.ContainerImageReference), typeof(ContainerImageReferenceComponent) }, + { "DockerReference", typeof(ContainerImageReferenceComponent) }, { nameof(ComponentType.Vcpkg), typeof(VcpkgComponent) }, { nameof(ComponentType.Spdx), typeof(SpdxComponent) }, { nameof(ComponentType.DotNet), typeof(DotNetComponent) }, diff --git a/src/Microsoft.ComponentDetection.Contracts/DockerReference.cs b/src/Microsoft.ComponentDetection.Contracts/ContainerImageReference.cs similarity index 64% rename from src/Microsoft.ComponentDetection.Contracts/DockerReference.cs rename to src/Microsoft.ComponentDetection.Contracts/ContainerImageReference.cs index 044a3bbfd..70579cc92 100644 --- a/src/Microsoft.ComponentDetection.Contracts/DockerReference.cs +++ b/src/Microsoft.ComponentDetection.Contracts/ContainerImageReference.cs @@ -1,7 +1,7 @@ #nullable disable namespace Microsoft.ComponentDetection.Contracts; -public enum DockerReferenceKind +public enum ContainerImageReferenceKind { Canonical = 0, Repository = 1, @@ -11,11 +11,11 @@ public enum DockerReferenceKind } #pragma warning disable SA1402 -public class DockerReference +public class ContainerImageReference { - public virtual DockerReferenceKind Kind { get; } + public virtual ContainerImageReferenceKind Kind { get; } - public static DockerReference CreateDockerReference(string repository, string domain, string digest, string tag) + public static ContainerImageReference CreateContainerImageReference(string repository, string domain, string digest, string tag) { if (!string.IsNullOrEmpty(repository) && string.IsNullOrEmpty(domain)) { @@ -72,7 +72,7 @@ public static DockerReference CreateDockerReference(string repository, string do } } - public virtual TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent() + public virtual TypedComponent.ContainerImageReferenceComponent ToTypedContainerImageReferenceComponent() { throw new System.NotImplementedException(); } @@ -90,20 +90,20 @@ public class Reference } // sha256:abc123... -public class DigestReference : DockerReference +public class DigestReference : ContainerImageReference { public string Digest { get; set; } - public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Digest; + public override ContainerImageReferenceKind Kind { get; } = ContainerImageReferenceKind.Digest; public override string ToString() { return $"{this.Digest}"; } - public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent() + public override TypedComponent.ContainerImageReferenceComponent ToTypedContainerImageReferenceComponent() { - return new TypedComponent.DockerReferenceComponent(this) + return new TypedComponent.ContainerImageReferenceComponent(this) { Digest = this.Digest, }; @@ -111,7 +111,7 @@ public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceCo } // docker.io/library/ubuntu@sha256:abc123... -public class CanonicalReference : DockerReference +public class CanonicalReference : ContainerImageReference { public string Domain { get; set; } @@ -119,16 +119,16 @@ public class CanonicalReference : DockerReference public string Digest { get; set; } - public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Canonical; + public override ContainerImageReferenceKind Kind { get; } = ContainerImageReferenceKind.Canonical; public override string ToString() { return $"{this.Domain}/{this.Repository}@${this.Digest}"; } - public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent() + public override TypedComponent.ContainerImageReferenceComponent ToTypedContainerImageReferenceComponent() { - return new TypedComponent.DockerReferenceComponent(this) + return new TypedComponent.ContainerImageReferenceComponent(this) { Domain = this.Domain, Digest = this.Digest, @@ -138,22 +138,22 @@ public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceCo } // docker.io/library/ubuntu -public class RepositoryReference : DockerReference +public class RepositoryReference : ContainerImageReference { public string Domain { get; set; } public string Repository { get; set; } - public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Repository; + public override ContainerImageReferenceKind Kind { get; } = ContainerImageReferenceKind.Repository; public override string ToString() { return $"{this.Repository}"; } - public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent() + public override TypedComponent.ContainerImageReferenceComponent ToTypedContainerImageReferenceComponent() { - return new TypedComponent.DockerReferenceComponent(this) + return new TypedComponent.ContainerImageReferenceComponent(this) { Domain = this.Domain, Repository = this.Repository, @@ -162,7 +162,7 @@ public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceCo } // docker.io/library/ubuntu:latest -public class TaggedReference : DockerReference +public class TaggedReference : ContainerImageReference { public string Domain { get; set; } @@ -170,16 +170,16 @@ public class TaggedReference : DockerReference public string Tag { get; set; } - public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Tagged; + public override ContainerImageReferenceKind Kind { get; } = ContainerImageReferenceKind.Tagged; public override string ToString() { return $"{this.Domain}/{this.Repository}:${this.Tag}"; } - public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent() + public override TypedComponent.ContainerImageReferenceComponent ToTypedContainerImageReferenceComponent() { - return new TypedComponent.DockerReferenceComponent(this) + return new TypedComponent.ContainerImageReferenceComponent(this) { Domain = this.Domain, Tag = this.Tag, @@ -189,7 +189,7 @@ public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceCo } // docker.io/library/ubuntu:latest@sha256:abc123... -public class DualReference : DockerReference +public class DualReference : ContainerImageReference { public string Domain { get; set; } @@ -199,16 +199,16 @@ public class DualReference : DockerReference public string Digest { get; set; } - public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Dual; + public override ContainerImageReferenceKind Kind { get; } = ContainerImageReferenceKind.Dual; public override string ToString() { return $"{this.Domain}/{this.Repository}:${this.Tag}@${this.Digest}"; } - public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent() + public override TypedComponent.ContainerImageReferenceComponent ToTypedContainerImageReferenceComponent() { - return new TypedComponent.DockerReferenceComponent(this) + return new TypedComponent.ContainerImageReferenceComponent(this) { Domain = this.Domain, Digest = this.Digest, diff --git a/src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs b/src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs index becfa4f9f..dc15d8233 100644 --- a/src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs +++ b/src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs @@ -42,8 +42,8 @@ public enum DetectorClass /// Indicates a detector applies to Vcpkg packages. Vcpkg, - /// Indicates a detector applies to Docker references. - DockerReference, + /// Indicates a detector applies to container image references. + ContainerImageReference, /// Indicates a detector applies to Swift packages. Swift, diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ComponentType.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ComponentType.cs index 391bc82bc..1f87ffdae 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ComponentType.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ComponentType.cs @@ -1,5 +1,6 @@ namespace Microsoft.ComponentDetection.Contracts.TypedComponent; +using System; using System.Runtime.Serialization; using System.Text.Json.Serialization; @@ -54,7 +55,12 @@ public enum ComponentType : byte Vcpkg = 15, [EnumMember] - DockerReference = 16, + ContainerImageReference = 16, + + /// Deprecated. Backward compatibility alias for . This alias will be removed in a future major version. Use instead. + [EnumMember] + [Obsolete("Use ContainerImageReference instead.")] + DockerReference = ContainerImageReference, [EnumMember] Conan = 17, diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/DockerReferenceComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ContainerImageReferenceComponent.cs similarity index 56% rename from src/Microsoft.ComponentDetection.Contracts/TypedComponent/DockerReferenceComponent.cs rename to src/Microsoft.ComponentDetection.Contracts/TypedComponent/ContainerImageReferenceComponent.cs index 8c0c6330d..1041a3a08 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/DockerReferenceComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ContainerImageReferenceComponent.cs @@ -3,20 +3,20 @@ namespace Microsoft.ComponentDetection.Contracts.TypedComponent; using System.Text.Json.Serialization; -public class DockerReferenceComponent : TypedComponent +public class ContainerImageReferenceComponent : TypedComponent { - public DockerReferenceComponent(string hash, string repository = null, string tag = null) + public ContainerImageReferenceComponent(string hash, string repository = null, string tag = null) { - this.Digest = this.ValidateRequiredInput(hash, nameof(this.Digest), nameof(ComponentType.DockerReference)); + this.Digest = this.ValidateRequiredInput(hash, nameof(this.Digest), nameof(ComponentType.ContainerImageReference)); this.Repository = repository; this.Tag = tag; } - public DockerReferenceComponent(DockerReference reference) + public ContainerImageReferenceComponent(ContainerImageReference reference) { } - public DockerReferenceComponent() + public ContainerImageReferenceComponent() { /* Reserved for deserialization */ } @@ -34,13 +34,13 @@ public DockerReferenceComponent() public string Domain { get; set; } [JsonIgnore] - public override ComponentType Type => ComponentType.DockerReference; + public override ComponentType Type => ComponentType.ContainerImageReference; - public DockerReference FullReference + public ContainerImageReference FullReference { get { - return DockerReference.CreateDockerReference(this.Repository, this.Domain, this.Digest, this.Tag); + return ContainerImageReference.CreateContainerImageReference(this.Repository, this.Domain, this.Digest, this.Tag); } } diff --git a/src/Microsoft.ComponentDetection.Detectors/dockerfile/DockerfileComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/dockerfile/DockerfileComponentDetector.cs index d1d19f3a9..247898f8a 100644 --- a/src/Microsoft.ComponentDetection.Detectors/dockerfile/DockerfileComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/dockerfile/DockerfileComponentDetector.cs @@ -36,11 +36,11 @@ public DockerfileComponentDetector( public override string Id { get; } = "DockerReference"; - public override IEnumerable Categories => [Enum.GetName(typeof(DetectorClass), DetectorClass.DockerReference)]; + public override IEnumerable Categories => [Enum.GetName(typeof(DetectorClass), DetectorClass.ContainerImageReference)]; public override IList SearchPatterns { get; } = ["dockerfile", "dockerfile.*", "*.dockerfile"]; - public override IEnumerable SupportedComponentTypes { get; } = [ComponentType.DockerReference]; + public override IEnumerable SupportedComponentTypes { get; } = [ComponentType.ContainerImageReference]; public override int Version => 1; @@ -77,19 +77,19 @@ private Task ParseDockerFileAsync(string fileContents, string fileLocation, ISin var imageReference = this.ProcessDockerfileConstruct(instruction, dockerfileModel.EscapeChar, stageNameMap); if (imageReference != null) { - singleFileComponentRecorder.RegisterUsage(new DetectedComponent(imageReference.ToTypedDockerReferenceComponent())); + singleFileComponentRecorder.RegisterUsage(new DetectedComponent(imageReference.ToTypedContainerImageReferenceComponent())); } } return Task.CompletedTask; } - private DockerReference ProcessDockerfileConstruct(DockerfileConstruct construct, char escapeChar, Dictionary stageNameMap) + private ContainerImageReference ProcessDockerfileConstruct(DockerfileConstruct construct, char escapeChar, Dictionary stageNameMap) { try { var instructionKeyword = construct.Type; - DockerReference baseImage = null; + ContainerImageReference baseImage = null; if (instructionKeyword == ConstructType.Instruction) { var constructType = construct.GetType().Name; @@ -110,12 +110,12 @@ private DockerReference ProcessDockerfileConstruct(DockerfileConstruct construct } catch (Exception e) { - this.Logger.LogError(e, "Failed to detect a DockerReference component, the component will not be registered."); + this.Logger.LogError(e, "Failed to detect a ContainerImageReference component, the component will not be registered."); return null; } } - private DockerReference ParseFromInstruction(DockerfileConstruct construct, char escapeChar, Dictionary stageNameMap) + private ContainerImageReference ParseFromInstruction(DockerfileConstruct construct, char escapeChar, Dictionary stageNameMap) { var tokens = construct.Tokens.ToArray(); var resolvedFromStatement = construct.ResolveVariables(escapeChar).TrimEnd(); @@ -148,7 +148,7 @@ private DockerReference ParseFromInstruction(DockerfileConstruct construct, char return null; } - return DockerReferenceUtility.ParseFamiliarName(stageNameReference); + return ContainerImageReferenceUtility.ParseFamiliarName(stageNameReference); } if (this.HasUnresolvedVariables(reference)) @@ -156,10 +156,10 @@ private DockerReference ParseFromInstruction(DockerfileConstruct construct, char return null; } - return DockerReferenceUtility.ParseFamiliarName(reference); + return ContainerImageReferenceUtility.ParseFamiliarName(reference); } - private DockerReference ParseCopyInstruction(DockerfileConstruct construct, char escapeChar, Dictionary stageNameMap) + private ContainerImageReference ParseCopyInstruction(DockerfileConstruct construct, char escapeChar, Dictionary stageNameMap) { var resolvedCopyStatement = construct.ResolveVariables(escapeChar).TrimEnd(); var copyInstruction = (CopyInstruction)construct; @@ -178,7 +178,7 @@ private DockerReference ParseCopyInstruction(DockerfileConstruct construct, char } else { - return DockerReferenceUtility.ParseFamiliarName(stageNameReference); + return ContainerImageReferenceUtility.ParseFamiliarName(stageNameReference); } } @@ -187,7 +187,7 @@ private DockerReference ParseCopyInstruction(DockerfileConstruct construct, char return null; } - return DockerReferenceUtility.ParseFamiliarName(reference); + return ContainerImageReferenceUtility.ParseFamiliarName(reference); } private bool HasUnresolvedVariables(string reference) diff --git a/test/Microsoft.ComponentDetection.Common.Tests/DockerReferenceUtilityTests.cs b/test/Microsoft.ComponentDetection.Common.Tests/ContainerImageReferenceUtilityTests.cs similarity index 79% rename from test/Microsoft.ComponentDetection.Common.Tests/DockerReferenceUtilityTests.cs rename to test/Microsoft.ComponentDetection.Common.Tests/ContainerImageReferenceUtilityTests.cs index bc4f4f1d4..79f949d6b 100644 --- a/test/Microsoft.ComponentDetection.Common.Tests/DockerReferenceUtilityTests.cs +++ b/test/Microsoft.ComponentDetection.Common.Tests/ContainerImageReferenceUtilityTests.cs @@ -9,14 +9,14 @@ namespace Microsoft.ComponentDetection.Common.Tests; [TestClass] [TestCategory("Governance/All")] [TestCategory("Governance/ComponentDetection")] -public class DockerReferenceUtilityTests +public class ContainerImageReferenceUtilityTests { [TestMethod] public void ParseQualifiedName_ThrowsReferenceNameEmptyException() { var qualifiedName = string.Empty; - var func = () => DockerReferenceUtility.ParseQualifiedName(qualifiedName); + var func = () => ContainerImageReferenceUtility.ParseQualifiedName(qualifiedName); func.Should().Throw(); } @@ -26,7 +26,7 @@ public void ParseQualifiedName_ThrowsReferenceNameContainsUppercaseException() { var qualifiedName = "docker.io/library/Nginx"; - var func = () => DockerReferenceUtility.ParseQualifiedName(qualifiedName); + var func = () => ContainerImageReferenceUtility.ParseQualifiedName(qualifiedName); func.Should().Throw(); } @@ -36,7 +36,7 @@ public void ParseQualifiedName_ThrowsReferenceInvalidFormatException() { var qualifiedName = "docker.io/library/nginx:latest:latest"; - var func = () => DockerReferenceUtility.ParseQualifiedName(qualifiedName); + var func = () => ContainerImageReferenceUtility.ParseQualifiedName(qualifiedName); func.Should().Throw(); } @@ -46,7 +46,7 @@ public void ParseQualifiedName_ThrowsReferenceNameTooLongException() { var qualifiedName = $"docker.io/library/{"nginx".PadRight(256, 'a')}"; - var func = () => DockerReferenceUtility.ParseQualifiedName(qualifiedName); + var func = () => ContainerImageReferenceUtility.ParseQualifiedName(qualifiedName); func.Should().Throw(); } @@ -56,7 +56,7 @@ public void ParseQualifiedName_CreatesProperTaggedReference() { var qualifiedName = "docker.io/library/nginx:latest"; - var result = DockerReferenceUtility.ParseQualifiedName(qualifiedName); + var result = ContainerImageReferenceUtility.ParseQualifiedName(qualifiedName); result.Should().NotBeNull(); result.Should().BeAssignableTo(); @@ -73,7 +73,7 @@ public void ParseQualifiedName_CreatesProperRepositoryReference() { var qualifiedName = "docker.io/library/nginx"; - var result = DockerReferenceUtility.ParseQualifiedName(qualifiedName); + var result = ContainerImageReferenceUtility.ParseQualifiedName(qualifiedName); result.Should().NotBeNull(); result.Should().BeAssignableTo(); @@ -89,7 +89,7 @@ public void ParseQualifiedName_ThrowsInvalidOperationIfNoComponent() { var qualifiedName = "nginx"; - var func = () => DockerReferenceUtility.ParseQualifiedName(qualifiedName); + var func = () => ContainerImageReferenceUtility.ParseQualifiedName(qualifiedName); func.Should().Throw(); } @@ -100,7 +100,7 @@ public void ParseQualifiedName_CreatesProperCanonicalReference() var hashTag = $"sha256:{new string('a', 64)}"; var qualifiedName = $"docker.io/library/nginx@{hashTag}"; - var result = DockerReferenceUtility.ParseQualifiedName(qualifiedName); + var result = ContainerImageReferenceUtility.ParseQualifiedName(qualifiedName); result.Should().NotBeNull(); result.Should().BeAssignableTo(); @@ -118,7 +118,7 @@ public void ParseQualifiedName_CreatedProperDigestReference() var hashTag = $"sha256:{new string('a', 64)}"; var qualifiedName = $"nginx@{hashTag}"; - var result = DockerReferenceUtility.ParseQualifiedName(qualifiedName); + var result = ContainerImageReferenceUtility.ParseQualifiedName(qualifiedName); result.Should().NotBeNull(); result.Should().BeAssignableTo(); @@ -133,7 +133,7 @@ public void ParseQualifiedName_CreatedDualReference() var hashTag = $"sha256:{new string('a', 64)}"; var qualifiedName = $"docker.io/library/nginx:latest@{hashTag}"; - var result = DockerReferenceUtility.ParseQualifiedName(qualifiedName); + var result = ContainerImageReferenceUtility.ParseQualifiedName(qualifiedName); result.Should().NotBeNull(); result.Should().BeAssignableTo(); @@ -151,7 +151,7 @@ public void SplitDockerDomain_AddsDefaultDomain() { var name = "library/nginx"; - var result = DockerReferenceUtility.SplitDockerDomain(name); + var result = ContainerImageReferenceUtility.SplitDockerDomain(name); result.Should().NotBeNull(); result.Domain.Should().Be("docker.io"); @@ -163,7 +163,7 @@ public void SplitDockerDomain_ReplacesLegacyDefaultDomain() { var name = "index.docker.io/library/nginx"; - var result = DockerReferenceUtility.SplitDockerDomain(name); + var result = ContainerImageReferenceUtility.SplitDockerDomain(name); result.Should().NotBeNull(); result.Domain.Should().Be("docker.io"); @@ -175,7 +175,7 @@ public void SplitDockerDomain_UpdatesRemainderWithOfficialRepoName() { var name = "nginx"; - var result = DockerReferenceUtility.SplitDockerDomain(name); + var result = ContainerImageReferenceUtility.SplitDockerDomain(name); result.Should().NotBeNull(); result.Domain.Should().Be("docker.io"); @@ -187,7 +187,7 @@ public void SplitDockerDomain_Works() { var name = "docker.io/library/nginx"; - var result = DockerReferenceUtility.SplitDockerDomain(name); + var result = ContainerImageReferenceUtility.SplitDockerDomain(name); result.Should().NotBeNull(); result.Domain.Should().Be("docker.io"); @@ -199,7 +199,7 @@ public void ParseFamiliarName_ThrowsReferenceNameNotCanonicalException() { var name = new string('a', 64); - var func = () => DockerReferenceUtility.ParseFamiliarName(name); + var func = () => ContainerImageReferenceUtility.ParseFamiliarName(name); func.Should().Throw(); } @@ -209,7 +209,7 @@ public void ParseFamiliarName_HandlesMissingTagSeperator() { var name = "docker.io/library/nginx"; - var result = DockerReferenceUtility.ParseFamiliarName(name); + var result = ContainerImageReferenceUtility.ParseFamiliarName(name); result.Should().NotBeNull(); result.Should().BeAssignableTo(); @@ -220,7 +220,7 @@ public void ParseFamiliarName_HandlesTag() { var name = "docker.io/library/nginx:latest"; - var result = DockerReferenceUtility.ParseFamiliarName(name); + var result = ContainerImageReferenceUtility.ParseFamiliarName(name); result.Should().NotBeNull(); result.Should().BeAssignableTo(); @@ -231,7 +231,7 @@ public void ParseFamiliarName_ThrowsReferenceNameContainsUppercaseException() { var name = "docker.io/library/Nginx"; - var func = () => DockerReferenceUtility.ParseFamiliarName(name); + var func = () => ContainerImageReferenceUtility.ParseFamiliarName(name); func.Should().Throw(); } @@ -241,7 +241,7 @@ public void ParseAll_HandlesAnchoredIdentifiers() { var name = new string('a', 64); - var result = DockerReferenceUtility.ParseAll(name); + var result = ContainerImageReferenceUtility.ParseAll(name); result.Should().NotBeNull(); result.Should().BeAssignableTo(); @@ -252,7 +252,7 @@ public void ParseAll_HandlesDigests() { var name = $"sha256:{new string('a', 64)}"; - var result = DockerReferenceUtility.ParseAll(name); + var result = ContainerImageReferenceUtility.ParseAll(name); result.Should().NotBeNull(); result.Should().BeAssignableTo(); @@ -263,7 +263,7 @@ public void ParseAll_ParsesFamiliarNames() { var name = "docker.io/library/nginx"; - var result = DockerReferenceUtility.ParseAll(name); + var result = ContainerImageReferenceUtility.ParseAll(name); result.Should().NotBeNull(); result.Should().BeAssignableTo(); diff --git a/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs b/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs index 57342f7e7..08754975a 100644 --- a/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs +++ b/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs @@ -2,6 +2,7 @@ namespace Microsoft.ComponentDetection.Contracts.Tests; using System; +using System.Collections.Generic; using System.Linq; using System.Text.Json; using AwesomeAssertions; @@ -273,8 +274,12 @@ public void TypedComponentMapping_AllMappedTypes_AreTypedComponentSubclasses() [TestMethod] public void TypedComponentMapping_AllMappedTypes_AreUnique() { - // Ensure no two discriminators map to the same type - var mappedTypes = TypedComponentMapping.TypeDiscriminatorToType.Values.ToList(); + // Ensure no two discriminators map to the same type, except for known backward-compatibility aliases + var backwardCompatAliases = new HashSet(StringComparer.OrdinalIgnoreCase) { "DockerReference" }; + var mappedTypes = TypedComponentMapping.TypeDiscriminatorToType + .Where(kvp => !backwardCompatAliases.Contains(kvp.Key)) + .Select(kvp => kvp.Value) + .ToList(); var uniqueTypes = mappedTypes.Distinct().ToList(); mappedTypes.Should().HaveCount(uniqueTypes.Count, "Each component type should map to a unique concrete type");