Skip to content

Commit b99010a

Browse files
committed
Fix using full image name including repository
1 parent 4dd9f3f commit b99010a

File tree

4 files changed

+44
-130
lines changed

4 files changed

+44
-130
lines changed

TODOS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# TODOS
22

3+
- Rename CLI flag '--only-container-info' to '--only-source-info'
34
- Publish package to PyPI
45
- Add host_info_detector (similar to docker_info_detector)
56
- Add CLI flag to select a subset of detectors (see how Syft does it: <https://github.com/anchore/syft/wiki/package-cataloger-selection>)

dependency_resolver/executors/docker_executor.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -114,25 +114,9 @@ def get_container_info(self) -> dict:
114114
image = self.container.image
115115
if image is None:
116116
return {"name": self.container.name, "image": "unknown", "image_hash": "unknown"}
117-
image_name = self._extract_image_name(image.tags)
117+
image_name = image.tags[0] if image.tags else "unknown"
118118
image_id = image.id
119119

120120
return {"name": self.container.name, "image": image_name, "image_hash": image_id}
121121
except (AttributeError, KeyError, ValueError) as e:
122122
return {"name": self.container.name, "image": "unknown", "image_hash": "unknown", "error": str(e)}
123-
124-
def _extract_image_name(self, tags: list) -> str:
125-
"""Extract a readable image name from image tags."""
126-
if not tags:
127-
return "unknown"
128-
129-
# Use the first tag, or if empty, return 'unknown'
130-
first_tag = tags[0] if tags else "unknown"
131-
132-
# Clean up the tag (remove registry prefixes if any)
133-
if "/" in first_tag:
134-
# Keep only the last part after the last slash for readability
135-
parts = first_tag.split("/")
136-
return parts[-1]
137-
138-
return first_tag

docs/technical/detectors/docker_info_detector.md

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def get_dependencies(self, executor: EnvironmentExecutor, working_dir: str = Non
6161
### Image Name Processing
6262

6363
- **Tag selection**: Uses first available image tag
64-
- **Registry cleanup**: Removes registry prefixes for readability
64+
- **Full name preservation**: Returns complete image name including repository
6565
- **Fallback handling**: Defaults to "unknown" for missing tags
6666

6767
## Hash Generation
@@ -178,24 +178,7 @@ Container info is included alongside package dependencies (dpkg, pip, npm, etc.)
178178

179179
### Integration with DockerExecutor
180180

181-
Leverages `DockerExecutor.get_container_info()` method:
182-
183-
```python
184-
def get_container_info(self) -> dict:
185-
# Reload container to get latest info
186-
self.container.reload()
187-
188-
# Get image information
189-
image = self.container.image
190-
image_name = self._extract_image_name(image.tags)
191-
image_id = image.id
192-
193-
return {
194-
"name": self.container.name,
195-
"image": image_name,
196-
"image_hash": image_id
197-
}
198-
```
181+
Leverages `DockerExecutor.get_container_info()` method to extract container metadata.
199182

200183
## Error Handling
201184

tests/detectors/docker_info/test_docker_info_detection.py

Lines changed: 40 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import os
44
import sys
5-
from typing import Dict, Any
65
from unittest.mock import Mock
76

87
# Add parent directory to path for imports
@@ -31,56 +30,65 @@ def test_docker_info_detection_real_container(self, request: pytest.FixtureReque
3130
container_id = None
3231

3332
try:
34-
# Start a test container
35-
container_id = self.start_container("nginx:alpine", sleep_duration="60")
33+
# Start a test container (use non-Docker Hub image to test full repository name preservation)
34+
container_id = self.start_container("quay.io/prometheus/busybox:latest", sleep_duration="60")
3635

3736
# Wait for container to be ready
3837
self.wait_for_container_ready(container_id, "echo ready", max_wait=10)
3938

40-
# Test Docker Info detection
39+
# Test Docker Info detection (container info only)
4140
executor = DockerExecutor(container_id)
4241
orchestrator = Orchestrator(debug=False)
4342

44-
# Test full analysis (includes container info)
45-
result = orchestrator.resolve_dependencies(executor)
43+
result = orchestrator.resolve_dependencies(executor, only_container_info=True)
4644

4745
if verbose_output:
48-
self.print_verbose_results("DOCKER INFO DETECTION OUTPUT (FULL):", result)
46+
self.print_verbose_results("DOCKER INFO DETECTION OUTPUT:", result)
4947

50-
# Validate container info is included
51-
self._validate_container_info_in_full_result(result)
48+
assert isinstance(result, dict), "Result should be a dictionary"
5249

53-
# Test container-info-only mode
54-
result_info_only = orchestrator.resolve_dependencies(executor, only_container_info=True)
50+
# Should only have source
51+
assert "source" in result, f"Expected 'source' in result keys: {list(result.keys())}"
52+
assert len(result) == 1, f"Container-info-only should have exactly 1 key, got: {list(result.keys())}"
5553

56-
if verbose_output:
57-
self.print_verbose_results("DOCKER INFO DETECTION OUTPUT (INFO ONLY):", result_info_only)
54+
container_info = result["source"]
5855

59-
# Validate container-info-only result
60-
self._validate_container_info_only_result(result_info_only)
56+
# Required fields
57+
required_fields = ["type", "name", "image", "hash"]
58+
for field in required_fields:
59+
assert field in container_info, f"Container info should have '{field}': {container_info}"
6160

62-
finally:
63-
if container_id:
64-
self.cleanup_container(container_id)
61+
# Validate type field
62+
assert container_info["type"] == "container", f"Type should be 'container': {container_info['type']}"
6563

66-
def test_docker_info_detector_unit_tests(self) -> None:
67-
"""Unit tests for Docker Info detector without real containers."""
68-
detector = DockerInfoDetector()
64+
name = container_info["name"]
65+
image = container_info["image"]
66+
hash_value = container_info["hash"]
6967

70-
# Test detector name
71-
assert detector.NAME == "docker-info"
68+
# Validate field types and content
69+
assert isinstance(name, str) and len(name) > 0, f"Name should be non-empty string: {name}"
70+
assert isinstance(image, str) and len(image) > 0, f"Image should be non-empty string: {image}"
71+
assert isinstance(hash_value, str) and len(hash_value) > 0, f"Hash should be non-empty string: {hash_value}"
7272

73-
# Test system scope
74-
mock_executor = Mock()
75-
assert not detector.has_system_scope(mock_executor)
73+
# Validate that full image name with repository is preserved
74+
assert "quay.io/prometheus/busybox:latest" == image, f"Expected full image name with repository: {image}"
7675

77-
# Test is_usable with DockerExecutor
78-
docker_executor = Mock(spec=DockerExecutor)
79-
assert detector.is_usable(docker_executor)
76+
# Validate hash format (should start with sha256:)
77+
if hash_value != "unknown":
78+
assert hash_value.startswith("sha256:"), f"Hash should start with 'sha256:': {hash_value}"
79+
assert len(hash_value) == 71, f"Hash should be 71 chars (sha256: + 64 hex): {hash_value}"
8080

81-
# Test is_usable with non-DockerExecutor
82-
other_executor = Mock()
83-
assert not detector.is_usable(other_executor)
81+
# Optional error field
82+
if "error" in container_info:
83+
error = container_info["error"]
84+
assert isinstance(error, str), f"Error should be string: {error}"
85+
86+
print(f"✓ Container info structure valid: {name} -> {image} ({hash_value[:19]}...)")
87+
print("✓ Container-info-only mode working correctly")
88+
89+
finally:
90+
if container_id:
91+
self.cleanup_container(container_id)
8492

8593
def test_docker_info_detector_non_docker_executor(self) -> None:
8694
"""Test behavior with non-DockerExecutor."""
@@ -92,68 +100,6 @@ def test_docker_info_detector_non_docker_executor(self) -> None:
92100
assert not packages
93101
assert not metadata
94102

95-
def _validate_container_info_in_full_result(self, result: Dict[str, Any]) -> None:
96-
"""Validate that container info is present in full analysis result."""
97-
assert isinstance(result, dict), "Result should be a dictionary"
98-
99-
# Check for source
100-
assert "source" in result, f"Expected 'source' in result keys: {list(result.keys())}"
101-
102-
container_info = result["source"]
103-
self._validate_container_info_structure(container_info)
104-
105-
# Should also have other detectors
106-
other_detectors = [key for key in result.keys() if key != "source"]
107-
assert len(other_detectors) > 0, "Should have other detectors in full analysis"
108-
109-
print(f"✓ Container info included in full analysis with {len(other_detectors)} other detectors")
110-
111-
def _validate_container_info_only_result(self, result: Dict[str, Any]) -> None:
112-
"""Validate container-info-only result structure."""
113-
assert isinstance(result, dict), "Result should be a dictionary"
114-
115-
# Should only have source
116-
assert "source" in result, f"Expected 'source' in result keys: {list(result.keys())}"
117-
assert len(result) == 1, f"Container-info-only should have exactly 1 key, got: {list(result.keys())}"
118-
119-
container_info = result["source"]
120-
self._validate_container_info_structure(container_info)
121-
122-
print("✓ Container-info-only mode working correctly")
123-
124-
def _validate_container_info_structure(self, container_info: Dict[str, Any]) -> None:
125-
"""Validate the structure of container info."""
126-
assert isinstance(container_info, dict), "Container info should be a dictionary"
127-
128-
# Required fields
129-
required_fields = ["type", "name", "image", "hash"]
130-
for field in required_fields:
131-
assert field in container_info, f"Container info should have '{field}': {container_info}"
132-
133-
# Validate type field
134-
assert container_info["type"] == "container", f"Type should be 'container': {container_info['type']}"
135-
136-
name = container_info["name"]
137-
image = container_info["image"]
138-
hash_value = container_info["hash"]
139-
140-
# Validate field types and content
141-
assert isinstance(name, str) and len(name) > 0, f"Name should be non-empty string: {name}"
142-
assert isinstance(image, str) and len(image) > 0, f"Image should be non-empty string: {image}"
143-
assert isinstance(hash_value, str) and len(hash_value) > 0, f"Hash should be non-empty string: {hash_value}"
144-
145-
# Validate hash format (should start with sha256:)
146-
if hash_value != "unknown":
147-
assert hash_value.startswith("sha256:"), f"Hash should start with 'sha256:': {hash_value}"
148-
assert len(hash_value) == 71, f"Hash should be 71 chars (sha256: + 64 hex): {hash_value}"
149-
150-
# Optional error field
151-
if "error" in container_info:
152-
error = container_info["error"]
153-
assert isinstance(error, str), f"Error should be string: {error}"
154-
155-
print(f"✓ Container info structure valid: {name} -> {image} ({hash_value[:19]}...)")
156-
157103

158104
class TestDockerInfoDetectorIntegration:
159105
"""Integration tests for Docker Info detector with orchestrator."""

0 commit comments

Comments
 (0)