Skip to content

Commit 131b87c

Browse files
committed
Rename _container-info to source
1 parent 1f820a6 commit 131b87c

File tree

10 files changed

+90
-43
lines changed

10 files changed

+90
-43
lines changed

SPECIFICATION.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ The tool outputs structured JSON with packages aggregated by scope (project/syst
6464

6565
```json
6666
{
67-
"_container-info": { "name": "nginx-container", "image": "nginx:latest", "hash": "sha256:..." },
67+
"source": { "type": "container", "name": "nginx-container", "image": "nginx:latest", "hash": "sha256:..." },
6868
"project": {
6969
"packages": [
7070
{ "name": "numpy", "version": "1.3.3", "type": "pip" }
@@ -87,7 +87,7 @@ For complete JSON schema documentation, field definitions, and examples, see [do
8787
- **System Packages**: All system-scoped packages aggregated in `system.packages[]` array
8888
- **Package Type**: Each package includes `type` field (`pip`, `npm`, `dpkg`, `apk`, `maven`)
8989
- **Metadata**: Package manager metadata stored in `project.{manager}` sections (location, hash)
90-
- **Container Info**: Docker metadata included as `_container-info` when applicable
90+
- **Source Info**: Environment metadata included as `source` when applicable (includes `type` field for container/host)
9191

9292
## Implementation Constraints
9393

TODOS.md

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

33
- Publish package to PyPI
4+
- Add host_info_detector (similar to docker_info_detector)
45
- Add CLI flag to select a subset of detectors (see how Syft does it: <https://github.com/anchore/syft/wiki/package-cataloger-selection>)
56
- Extend the set of supported package managers with the common ones for Go, PHP and Java
67
- Add Dockerfile

dependency_resolver/core/orchestrator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ def resolve_dependencies(
7171
if detector_name == "docker-info":
7272
packages, metadata = detector.get_dependencies(executor, working_dir)
7373
if metadata: # Only include if we got container info
74-
result["_container-info"] = metadata
74+
metadata["type"] = "container"
75+
result["source"] = metadata
7576
if self.debug:
7677
print(f"Found container info for {detector_name}")
7778
else:

dependency_resolver/detectors/docker_info_detector.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class DockerInfoDetector(PackageManagerDetector):
1010

1111
def is_usable(self, executor: EnvironmentExecutor, working_dir: Optional[str] = None) -> bool:
1212
"""Check if this is a Docker environment."""
13+
_ = working_dir # Unused parameter, required by interface
1314
return isinstance(executor, DockerExecutor)
1415

1516
def get_dependencies(
@@ -20,13 +21,14 @@ def get_dependencies(
2021
Note: This detector returns metadata rather than packages, but conforms to the interface.
2122
The orchestrator handles this specially.
2223
"""
24+
_ = working_dir # Unused parameter, required by interface
2325
if not isinstance(executor, DockerExecutor):
2426
return [], {}
2527

2628
container_info = executor.get_container_info()
2729

2830
# Return simplified container info structure as metadata
29-
# The orchestrator will handle this specially for _container-info section
31+
# The orchestrator will handle this specially for source section
3032
result = {
3133
"name": container_info["name"],
3234
"image": container_info["image"],
@@ -42,4 +44,6 @@ def get_dependencies(
4244

4345
def has_system_scope(self, executor: EnvironmentExecutor, working_dir: Optional[str] = None) -> bool:
4446
"""Docker container info has container scope, not system scope."""
47+
_ = executor # Unused parameter, required by interface
48+
_ = working_dir # Unused parameter, required by interface
4549
return False

docs/technical/detectors/docker_info_detector.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@ Unlike other detectors, Docker Info uses a flattened output structure:
2020

2121
```json
2222
{
23-
"_container-info": {
23+
"source": {
24+
"type": "container",
2425
"name": "container-name",
2526
"image": "nginx:latest",
2627
"hash": "sha256:abc123def456..."
2728
}
2829
}
2930
```
3031

31-
This simplified format reflects that container metadata is fundamentally different from package dependencies.
32+
This simplified format reflects that source metadata is fundamentally different from package dependencies. The `type` field distinguishes between container and host sources.
3233

3334
## Implementation Details
3435

@@ -86,7 +87,7 @@ Container images don't use location-based hashing since:
8687

8788
### Container Scope
8889

89-
Docker container info has `container` scope in the orchestrator logic, but outputs directly as `_container-info` to distinguish it from package managers.
90+
Docker container info has `container` scope in the orchestrator logic, but outputs directly as `source` with `type: "container"` to distinguish it from package managers.
9091

9192
### Executor Context
9293

@@ -122,7 +123,8 @@ The detector returns a tuple: `(packages, metadata)` but is handled specially by
122123

123124
```json
124125
{
125-
"_container-info": {
126+
"source": {
127+
"type": "container",
126128
"name": "my-nginx-container",
127129
"image": "nginx:latest",
128130
"hash": "sha256:2cd1d97f893f70cee86a38b7160c30e5750f3ed6ad86c598884ca9c6a563a501"
@@ -136,6 +138,7 @@ The detector returns a tuple: `(packages, metadata)` but is handled specially by
136138

137139
```json
138140
{
141+
"type": "container",
139142
"name": "my-container",
140143
"image": "unknown",
141144
"hash": "unknown",

docs/usage/output-format.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The dependency-resolver outputs a unified JSON structure that aggregates all pac
88

99
```json
1010
{
11-
"_container-info": { ... },
11+
"source": { ... },
1212
"project": {
1313
"packages": [...],
1414
"pip": { ... },
@@ -20,13 +20,14 @@ The dependency-resolver outputs a unified JSON structure that aggregates all pac
2020
}
2121
```
2222

23-
## Container Information
23+
## Source Information
2424

25-
When analyzing Docker containers, the `_container-info` section provides metadata:
25+
When analyzing environments, the `source` section provides metadata about where the scan was performed:
2626

2727
```json
2828
{
29-
"_container-info": {
29+
"source": {
30+
"type": "container",
3031
"name": "nginx-container",
3132
"image": "nginx:latest",
3233
"hash": "sha256:2cd1d97f893f..."
@@ -36,9 +37,10 @@ When analyzing Docker containers, the `_container-info` section provides metadat
3637

3738
### Fields
3839

39-
- `name` - Container name
40-
- `image` - Docker image used
41-
- `hash` - Container image hash
40+
- `type` - Source type (`"container"` for Docker containers, `"host"` for host scans)
41+
- `name` - Container name (for container sources)
42+
- `image` - Docker image used (for container sources)
43+
- `hash` - Container image hash (for container sources)
4244

4345
## Package Sections
4446

@@ -166,7 +168,8 @@ Here's a complete example showing the new unified structure:
166168

167169
```json
168170
{
169-
"_container-info": {
171+
"source": {
172+
"type": "container",
170173
"name": "web-app",
171174
"image": "python:3.12-slim",
172175
"hash": "sha256:a1b2c3d4e5f6..."

tests/common/docker_test_base.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,31 @@ def print_verbose_results(self, title: str, result: Dict[str, Any]) -> None:
109109
print("=" * 60)
110110

111111
def validate_basic_structure(self, result: Dict[str, Any], detector_type: str) -> None:
112-
"""Validate basic structure of new scope-based results."""
112+
"""Validate basic structure of dependency resolver results.
113+
114+
Validates both package scopes (project/system) and metadata sections (source).
115+
Handles both full analysis results and metadata-only results.
116+
"""
113117
assert isinstance(result, dict), "Result should be a dictionary"
114118

115-
# Should have either project or system scope (or both)
116-
valid_scopes = ["project", "system", "_container-info"]
117-
found_scopes = [scope for scope in result.keys() if scope in valid_scopes]
118-
assert len(found_scopes) > 0, f"Expected at least one scope in result keys: {list(result.keys())}"
119+
# Should have either package scopes (project/system) or source metadata
120+
package_scopes = ["project", "system"]
121+
metadata_sections = ["source"]
122+
all_valid_sections = package_scopes + metadata_sections
123+
124+
found_package_scopes = [scope for scope in result.keys() if scope in package_scopes]
125+
found_metadata_sections = [section for section in result.keys() if section in metadata_sections]
126+
found_valid_sections = [section for section in result.keys() if section in all_valid_sections]
127+
128+
assert (
129+
len(found_valid_sections) > 0
130+
), f"Expected at least one valid section in result keys: {list(result.keys())}"
119131

120-
# Validate packages were found for the expected detector type
121-
packages_found = False
122-
for scope_name in ["project", "system"]:
123-
if scope_name in result:
132+
# Validate packages were found for the expected detector type (unless only metadata)
133+
if found_package_scopes:
134+
# If we have package scopes, validate that the expected detector type is present
135+
packages_found = False
136+
for scope_name in found_package_scopes:
124137
scope_result = result[scope_name]
125138
assert isinstance(scope_result, dict), f"{scope_name} should be a dictionary"
126139
assert "packages" in scope_result, f"{scope_name} should contain 'packages'"
@@ -134,7 +147,16 @@ def validate_basic_structure(self, result: Dict[str, Any], detector_type: str) -
134147
packages_found = True
135148
break
136149

137-
assert packages_found, f"Should have found packages of type '{detector_type}' in result"
150+
assert packages_found, f"Should have found packages of type '{detector_type}' in result"
151+
elif found_metadata_sections:
152+
# If only metadata sections, validate source structure (e.g., container-info-only mode)
153+
source_result = result["source"]
154+
assert isinstance(source_result, dict), "source should be a dictionary"
155+
assert "type" in source_result, "source should contain 'type'"
156+
assert source_result["type"] in [
157+
"container",
158+
"host",
159+
], f"source type should be 'container' or 'host': {source_result['type']}"
138160

139161
def validate_dependency_structure(self, packages: list, sample_count: int = 5) -> None:
140162
"""Validate structure of package entries in the new format."""

tests/detectors/docker_info/README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This directory contains tests for the Docker Info detector, which extracts metad
2222

2323
- ✅ Container metadata extraction (name, image, hash)
2424
- ✅ Both full analysis and container-info-only modes
25-
- ✅ Simplified JSON output format (`_container-info`)
25+
- ✅ Simplified JSON output format (`source` with `type: "container"`)
2626
- ✅ Error handling and graceful degradation
2727

2828
### Integration Tests
@@ -62,7 +62,8 @@ pytest tests/detectors/docker_info/test_docker_info_detection.py::TestDockerInfo
6262

6363
```json
6464
{
65-
"_container-info": {
65+
"source": {
66+
"type": "container",
6667
"name": "container-id",
6768
"image": "nginx:alpine",
6869
"hash": "sha256:abc123..."
@@ -74,13 +75,18 @@ pytest tests/detectors/docker_info/test_docker_info_detection.py::TestDockerInfo
7475

7576
```json
7677
{
77-
"_container-info": {
78+
"source": {
79+
"type": "container",
7880
"name": "container-id",
7981
"image": "nginx:alpine",
8082
"hash": "sha256:abc123..."
8183
},
82-
"dpkg": { ... },
83-
"apk": { ... }
84+
"system": {
85+
"packages": [...]
86+
},
87+
"project": {
88+
"packages": [...]
89+
}
8490
}
8591
```
8692

tests/detectors/docker_info/test_docker_info_detection.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,14 @@ def _validate_container_info_in_full_result(self, result: Dict[str, Any]) -> Non
9696
"""Validate that container info is present in full analysis result."""
9797
assert isinstance(result, dict), "Result should be a dictionary"
9898

99-
# Check for _container-info
100-
assert "_container-info" in result, f"Expected '_container-info' in result keys: {list(result.keys())}"
99+
# Check for source
100+
assert "source" in result, f"Expected 'source' in result keys: {list(result.keys())}"
101101

102-
container_info = result["_container-info"]
102+
container_info = result["source"]
103103
self._validate_container_info_structure(container_info)
104104

105105
# Should also have other detectors
106-
other_detectors = [key for key in result.keys() if key != "_container-info"]
106+
other_detectors = [key for key in result.keys() if key != "source"]
107107
assert len(other_detectors) > 0, "Should have other detectors in full analysis"
108108

109109
print(f"✓ Container info included in full analysis with {len(other_detectors)} other detectors")
@@ -112,11 +112,11 @@ def _validate_container_info_only_result(self, result: Dict[str, Any]) -> None:
112112
"""Validate container-info-only result structure."""
113113
assert isinstance(result, dict), "Result should be a dictionary"
114114

115-
# Should only have _container-info
116-
assert "_container-info" in result, f"Expected '_container-info' in result keys: {list(result.keys())}"
115+
# Should only have source
116+
assert "source" in result, f"Expected 'source' in result keys: {list(result.keys())}"
117117
assert len(result) == 1, f"Container-info-only should have exactly 1 key, got: {list(result.keys())}"
118118

119-
container_info = result["_container-info"]
119+
container_info = result["source"]
120120
self._validate_container_info_structure(container_info)
121121

122122
print("✓ Container-info-only mode working correctly")
@@ -126,10 +126,13 @@ def _validate_container_info_structure(self, container_info: Dict[str, Any]) ->
126126
assert isinstance(container_info, dict), "Container info should be a dictionary"
127127

128128
# Required fields
129-
required_fields = ["name", "image", "hash"]
129+
required_fields = ["type", "name", "image", "hash"]
130130
for field in required_fields:
131131
assert field in container_info, f"Container info should have '{field}': {container_info}"
132132

133+
# Validate type field
134+
assert container_info["type"] == "container", f"Type should be 'container': {container_info['type']}"
135+
133136
name = container_info["name"]
134137
image = container_info["image"]
135138
hash_value = container_info["hash"]
@@ -171,15 +174,17 @@ def test_orchestrator_only_container_info_mode(self) -> None:
171174
# Test only_container_info mode
172175
result = orchestrator.resolve_dependencies(executor, only_container_info=True)
173176

174-
# Should only contain _container-info
177+
# Should only contain source
175178
assert isinstance(result, dict)
176-
assert "_container-info" in result
179+
assert "source" in result
177180
assert len(result) == 1
178181

179-
container_info = result["_container-info"]
182+
container_info = result["source"]
183+
assert "type" in container_info
180184
assert "name" in container_info
181185
assert "image" in container_info
182186
assert "hash" in container_info
187+
assert container_info["type"] == "container"
183188

184189
finally:
185190
if container_id:

tests/integration/test_programmatic_interface.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,9 @@ def test_resolve_docker_dependencies_as_dict_with_all_args(
260260

261261
mock_orchestrator_instance = MagicMock()
262262
mock_orchestrator.return_value = mock_orchestrator_instance
263-
mock_dependencies = {"_container-info": {"name": "my-container", "image": "ubuntu:20.04", "hash": "abc123"}}
263+
mock_dependencies = {
264+
"source": {"type": "container", "name": "my-container", "image": "ubuntu:20.04", "hash": "abc123"}
265+
}
264266
mock_orchestrator_instance.resolve_dependencies.return_value = mock_dependencies
265267

266268
# Call function with all arguments

0 commit comments

Comments
 (0)