diff --git a/docs/detectors/linux.md b/docs/detectors/linux.md index b4e1c613f..839363300 100644 --- a/docs/detectors/linux.md +++ b/docs/detectors/linux.md @@ -32,10 +32,6 @@ Images present on the filesystem as either an [OCI layout directory](https://spe - For OCI image layout directories, use the prefix `oci-dir:` followed by the path to the directory, e.g. `oci-dir:/path/to/image` - For OCI image archives (tarballs), use the prefix `oci-archive:` followed by the path to the archive file, e.g. `oci-archive:/path/to/image.tar` -#### Docker Archives - -Images saved to disk via `docker save` can be referenced using the `docker-archive:` prefix followed by the path to the tarball, e.g. `docker-archive:/path/to/image.tar`. - ### Scanner Scope By default, this detector invokes Syft with the `all-layers` scanning scope (i.e. the Syft argument `--scope all-layers`). diff --git a/src/Microsoft.ComponentDetection.Detectors/linux/ImageReference.cs b/src/Microsoft.ComponentDetection.Detectors/linux/ImageReference.cs index fcb8c1c34..5ba97f9bd 100644 --- a/src/Microsoft.ComponentDetection.Detectors/linux/ImageReference.cs +++ b/src/Microsoft.ComponentDetection.Detectors/linux/ImageReference.cs @@ -21,11 +21,6 @@ internal enum ImageReferenceKind /// An OCI archive (tarball) file on disk (e.g., "oci-archive:/path/to/image.tar"). /// OciArchive, - - /// - /// A Docker archive (tarball) file on disk created by "docker save" (e.g., "docker-archive:/path/to/image.tar"). - /// - DockerArchive, } /// @@ -35,7 +30,6 @@ internal class ImageReference { private const string OciDirPrefix = "oci-dir:"; private const string OciArchivePrefix = "oci-archive:"; - private const string DockerArchivePrefix = "docker-archive:"; /// /// Gets the original input string as provided by the user. @@ -44,7 +38,7 @@ internal class ImageReference /// /// Gets the cleaned reference string with any scheme prefix removed. - /// For Docker images, this is lowercased. For file paths, case is preserved. + /// For Docker images, this is lowercased. For OCI paths, case is preserved. /// public required string Reference { get; init; } @@ -92,22 +86,6 @@ public static ImageReference Parse(string input) }; } - if (input.StartsWith(DockerArchivePrefix, StringComparison.OrdinalIgnoreCase)) - { - var path = input[DockerArchivePrefix.Length..]; - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentException($"Input with '{DockerArchivePrefix}' prefix must include a path.", nameof(input)); - } - - return new ImageReference - { - OriginalInput = input, - Reference = path, - Kind = ImageReferenceKind.DockerArchive, - }; - } - #pragma warning disable CA1308 return new ImageReference { diff --git a/src/Microsoft.ComponentDetection.Detectors/linux/LinuxContainerDetector.cs b/src/Microsoft.ComponentDetection.Detectors/linux/LinuxContainerDetector.cs index 41d0747d0..0c762d71c 100644 --- a/src/Microsoft.ComponentDetection.Detectors/linux/LinuxContainerDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/linux/LinuxContainerDetector.cs @@ -264,7 +264,6 @@ private async Task ResolveImageAsync( break; case ImageReferenceKind.OciLayout: case ImageReferenceKind.OciArchive: - case ImageReferenceKind.DockerArchive: var fullPath = this.ValidateLocalImagePath(imageRef); localImages.TryAdd(fullPath, imageRef.Kind); break; @@ -310,21 +309,16 @@ await this.dockerService.InspectImageAsync(image, cancellationToken) } /// - /// Validates that a local image path exists on disk. Throws a if it does not. - /// For OCI layouts, checks for a directory. For OCI archives and Docker archives, checks for a file. + /// Validates that a local image path exists on disk. Throws a FileNotFoundException if it does not. + /// For OCI layouts, checks for a directory. For OCI archives, checks for a file. /// Returns the full path to the local image if validation succeeds. /// private string ValidateLocalImagePath(ImageReference imageRef) { var path = Path.GetFullPath(imageRef.Reference); - var exists = imageRef.Kind switch - { - ImageReferenceKind.OciLayout => Directory.Exists(path), - ImageReferenceKind.OciArchive => System.IO.File.Exists(path), - ImageReferenceKind.DockerArchive => System.IO.File.Exists(path), - ImageReferenceKind.DockerImage or _ => throw new InvalidOperationException( - $"ValidateLocalImagePath does not support image kind '{imageRef.Kind}'."), - }; + var exists = imageRef.Kind == ImageReferenceKind.OciLayout + ? Directory.Exists(path) + : System.IO.File.Exists(path); if (!exists) { @@ -415,11 +409,6 @@ private async Task ScanLocalImageAsync( ?? throw new InvalidOperationException($"Could not determine parent directory for OCI archive path '{localImagePath}'."); syftContainerPath = $"oci-archive:{LocalImageMountPoint}/{Path.GetFileName(localImagePath)}"; break; - case ImageReferenceKind.DockerArchive: - hostPathToBind = Path.GetDirectoryName(localImagePath) - ?? throw new InvalidOperationException($"Could not determine parent directory for Docker archive path '{localImagePath}'."); - syftContainerPath = $"docker-archive:{LocalImageMountPoint}/{Path.GetFileName(localImagePath)}"; - break; case ImageReferenceKind.DockerImage: default: throw new InvalidUserInputException( diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/LinuxContainerDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/LinuxContainerDetectorTests.cs index 823dbe395..8e68e88bf 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/LinuxContainerDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/LinuxContainerDetectorTests.cs @@ -1127,121 +1127,4 @@ public async Task TestLinuxContainerDetector_OciArchiveImage_DetectsComponentsAs System.IO.File.Delete(ociArchive); } } - - [TestMethod] - public async Task TestLinuxContainerDetector_DockerArchiveImage_DetectsComponentsAsync() - { - var componentRecorder = new ComponentRecorder(); - - // Create a temp file to act as the Docker archive - var dockerArchiveDir = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar); - var dockerArchiveName = "test-docker-archive-" + Guid.NewGuid().ToString("N") + ".tar"; - var dockerArchive = Path.Combine(dockerArchiveDir, dockerArchiveName); - await System.IO.File.WriteAllBytesAsync(dockerArchive, []); - - try - { - var scanRequest = new ScanRequest( - new DirectoryInfo(Path.GetTempPath()), - (_, __) => false, - this.mockLogger.Object, - null, - [$"docker-archive:{dockerArchive}"], - componentRecorder - ); - - var syftOutputJson = """ - { - "distro": { "id": "ubuntu", "versionID": "22.04" }, - "artifacts": [], - "source": { - "id": "sha256:abc", - "name": "/local-image", - "type": "image", - "version": "sha256:abc", - "metadata": { - "userInput": "/local-image", - "imageID": "sha256:dockerarchiveimg", - "tags": ["myapp:v2"], - "repoDigests": [], - "layers": [ - { "digest": "sha256:dockerlayer1", "size": 50000 }, - { "digest": "sha256:dockerlayer2", "size": 60000 } - ], - "labels": {} - } - } - } - """; - var syftOutput = SyftOutput.FromJson(syftOutputJson); - - this.mockSyftLinuxScanner.Setup(scanner => - scanner.GetSyftOutputAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny() - ) - ) - .ReturnsAsync(syftOutput); - - var layerMappedComponents = new[] - { - new LayerMappedLinuxComponents - { - DockerLayer = new DockerLayer { DiffId = "sha256:dockerlayer1", LayerIndex = 0 }, - Components = [new LinuxComponent("ubuntu", "22.04", "libc6", "2.35-0ubuntu3")], - }, - }; - - this.mockSyftLinuxScanner.Setup(scanner => - scanner.ProcessSyftOutput( - It.IsAny(), - It.IsAny>(), - It.IsAny>() - ) - ) - .Returns(layerMappedComponents); - - var linuxContainerDetector = new LinuxContainerDetector( - this.mockSyftLinuxScanner.Object, - this.mockDockerService.Object, - this.mockLinuxContainerDetectorLogger.Object - ); - - var scanResult = await linuxContainerDetector.ExecuteDetectorAsync(scanRequest); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - scanResult.ContainerDetails.Should().ContainSingle(); - - var containerDetails = scanResult.ContainerDetails.First(); - containerDetails.ImageId.Should().Be("sha256:dockerarchiveimg"); - containerDetails.Tags.Should().ContainSingle().Which.Should().Be("myapp:v2"); - - var detectedComponents = componentRecorder.GetDetectedComponents().ToList(); - detectedComponents.Should().ContainSingle(); - var detectedComponent = detectedComponents.First(); - detectedComponent.Component.Id.Should().Contain("libc6"); - detectedComponent.ContainerLayerIds.Keys.Should().ContainSingle(); - var containerId = detectedComponent.ContainerLayerIds.Keys.First(); - detectedComponent.ContainerLayerIds[containerId].Should().BeEquivalentTo([0]); - - // Verify GetSyftOutputAsync was called with docker-archive: prefix - this.mockSyftLinuxScanner.Verify( - scanner => - scanner.GetSyftOutputAsync( - It.Is(s => s.StartsWith("docker-archive:") && s.Contains(dockerArchiveName)), - It.Is>(binds => - binds.Count == 1 && binds[0].Contains(dockerArchiveDir)), - It.IsAny(), - It.IsAny() - ), - Times.Once - ); - } - finally - { - System.IO.File.Delete(dockerArchive); - } - } }