sbomify-action 0.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. sbomify_action/__init__.py +81 -0
  2. sbomify_action/_augmentation/__init__.py +76 -0
  3. sbomify_action/_augmentation/metadata.py +227 -0
  4. sbomify_action/_augmentation/protocol.py +80 -0
  5. sbomify_action/_augmentation/providers/__init__.py +18 -0
  6. sbomify_action/_augmentation/providers/bitbucket.py +105 -0
  7. sbomify_action/_augmentation/providers/github.py +97 -0
  8. sbomify_action/_augmentation/providers/gitlab.py +96 -0
  9. sbomify_action/_augmentation/providers/json_config.py +157 -0
  10. sbomify_action/_augmentation/providers/sbomify_api.py +128 -0
  11. sbomify_action/_augmentation/registry.py +126 -0
  12. sbomify_action/_augmentation/utils.py +62 -0
  13. sbomify_action/_enrichment/__init__.py +16 -0
  14. sbomify_action/_enrichment/enricher.py +274 -0
  15. sbomify_action/_enrichment/license_db_generator.py +1630 -0
  16. sbomify_action/_enrichment/license_normalizer.py +534 -0
  17. sbomify_action/_enrichment/license_utils.py +311 -0
  18. sbomify_action/_enrichment/lifecycle_data.py +983 -0
  19. sbomify_action/_enrichment/metadata.py +132 -0
  20. sbomify_action/_enrichment/protocol.py +89 -0
  21. sbomify_action/_enrichment/registry.py +128 -0
  22. sbomify_action/_enrichment/sanitization.py +350 -0
  23. sbomify_action/_enrichment/sources/__init__.py +27 -0
  24. sbomify_action/_enrichment/sources/clearlydefined.py +200 -0
  25. sbomify_action/_enrichment/sources/cratesio.py +187 -0
  26. sbomify_action/_enrichment/sources/debian.py +257 -0
  27. sbomify_action/_enrichment/sources/depsdev.py +202 -0
  28. sbomify_action/_enrichment/sources/ecosystems.py +216 -0
  29. sbomify_action/_enrichment/sources/license_db.py +703 -0
  30. sbomify_action/_enrichment/sources/lifecycle.py +214 -0
  31. sbomify_action/_enrichment/sources/pubdev.py +195 -0
  32. sbomify_action/_enrichment/sources/purl.py +175 -0
  33. sbomify_action/_enrichment/sources/pypi.py +197 -0
  34. sbomify_action/_enrichment/sources/repology.py +245 -0
  35. sbomify_action/_enrichment/utils.py +98 -0
  36. sbomify_action/_generation/__init__.py +68 -0
  37. sbomify_action/_generation/generator.py +187 -0
  38. sbomify_action/_generation/generators/__init__.py +29 -0
  39. sbomify_action/_generation/generators/cdxgen.py +339 -0
  40. sbomify_action/_generation/generators/cyclonedx_cargo.py +161 -0
  41. sbomify_action/_generation/generators/cyclonedx_py.py +225 -0
  42. sbomify_action/_generation/generators/syft.py +320 -0
  43. sbomify_action/_generation/generators/trivy.py +326 -0
  44. sbomify_action/_generation/protocol.py +225 -0
  45. sbomify_action/_generation/registry.py +344 -0
  46. sbomify_action/_generation/result.py +86 -0
  47. sbomify_action/_generation/utils.py +502 -0
  48. sbomify_action/_processors/__init__.py +44 -0
  49. sbomify_action/_processors/orchestrator.py +152 -0
  50. sbomify_action/_processors/processors/__init__.py +5 -0
  51. sbomify_action/_processors/processors/releases.py +197 -0
  52. sbomify_action/_processors/protocol.py +100 -0
  53. sbomify_action/_processors/registry.py +152 -0
  54. sbomify_action/_processors/releases_api.py +314 -0
  55. sbomify_action/_processors/result.py +141 -0
  56. sbomify_action/_upload/__init__.py +62 -0
  57. sbomify_action/_upload/destinations/__init__.py +10 -0
  58. sbomify_action/_upload/destinations/dependency_track.py +252 -0
  59. sbomify_action/_upload/destinations/sbomify.py +219 -0
  60. sbomify_action/_upload/orchestrator.py +159 -0
  61. sbomify_action/_upload/protocol.py +145 -0
  62. sbomify_action/_upload/registry.py +152 -0
  63. sbomify_action/_upload/result.py +84 -0
  64. sbomify_action/additional_packages.py +433 -0
  65. sbomify_action/augmentation.py +1708 -0
  66. sbomify_action/cli/__init__.py +25 -0
  67. sbomify_action/cli/main.py +1923 -0
  68. sbomify_action/console.py +886 -0
  69. sbomify_action/enrichment.py +1253 -0
  70. sbomify_action/exceptions.py +50 -0
  71. sbomify_action/generation.py +193 -0
  72. sbomify_action/http_client.py +46 -0
  73. sbomify_action/logging_config.py +93 -0
  74. sbomify_action/schemas/cyclonedx/cdx-1.3.schema.json +1058 -0
  75. sbomify_action/schemas/cyclonedx/cdx-1.4.schema.json +1697 -0
  76. sbomify_action/schemas/cyclonedx/cdx-1.5.schema.json +3799 -0
  77. sbomify_action/schemas/cyclonedx/cdx-1.6.schema.json +5699 -0
  78. sbomify_action/schemas/cyclonedx/cdx-1.7.schema.json +6700 -0
  79. sbomify_action/schemas/spdx/spdx-2.2.schema.json +721 -0
  80. sbomify_action/schemas/spdx/spdx-2.3.schema.json +746 -0
  81. sbomify_action/serialization.py +1031 -0
  82. sbomify_action/tool_checks.py +338 -0
  83. sbomify_action/upload.py +180 -0
  84. sbomify_action/validation.py +352 -0
  85. sbomify_action-0.12.dist-info/METADATA +854 -0
  86. sbomify_action-0.12.dist-info/RECORD +89 -0
  87. sbomify_action-0.12.dist-info/WHEEL +4 -0
  88. sbomify_action-0.12.dist-info/entry_points.txt +3 -0
  89. sbomify_action-0.12.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,81 @@
1
+ """sbomify-action package for SBOM generation and enrichment."""
2
+
3
+
4
+ def format_display_name(fmt: str) -> str:
5
+ """Return properly capitalized display name for SBOM format.
6
+
7
+ This is the canonical function for displaying SBOM format names.
8
+ Use this everywhere format names are shown to users.
9
+
10
+ Args:
11
+ fmt: Format string ('cyclonedx' or 'spdx', case-insensitive)
12
+
13
+ Returns:
14
+ Display name: 'CycloneDX' or 'SPDX'
15
+
16
+ Examples:
17
+ >>> format_display_name("cyclonedx")
18
+ 'CycloneDX'
19
+ >>> format_display_name("SPDX")
20
+ 'SPDX'
21
+ """
22
+ return "CycloneDX" if fmt.lower() == "cyclonedx" else "SPDX"
23
+
24
+
25
+ def _get_version() -> str:
26
+ """Get package version with fallback mechanisms.
27
+
28
+ Tries the following methods in order until one succeeds:
29
+ - importlib.metadata (preferred for installed packages)
30
+ - tomllib/pyproject.toml (Python 3.11+, for development)
31
+ - toml library/pyproject.toml (older Python, for development)
32
+ - Returns "unknown" if all methods fail
33
+ """
34
+ # Try importlib.metadata (preferred for installed packages)
35
+ try:
36
+ from importlib.metadata import version
37
+
38
+ return version("sbomify-action")
39
+ except ImportError:
40
+ pass
41
+ except Exception:
42
+ pass
43
+
44
+ # Try reading from pyproject.toml using tomllib (Python 3.11+)
45
+ try:
46
+ from pathlib import Path
47
+
48
+ import tomllib
49
+
50
+ pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
51
+ if pyproject_path.exists():
52
+ with open(pyproject_path, "rb") as f:
53
+ pyproject_data = tomllib.load(f)
54
+ return pyproject_data.get("project", {}).get("version", "unknown")
55
+ except ImportError:
56
+ # Python < 3.11 doesn't have tomllib
57
+ pass
58
+ except Exception:
59
+ pass
60
+
61
+ # Try toml library as fallback for older Python
62
+ try:
63
+ from pathlib import Path
64
+
65
+ import toml
66
+
67
+ pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
68
+ if pyproject_path.exists():
69
+ with open(pyproject_path, "r") as f:
70
+ pyproject_data = toml.load(f)
71
+ return pyproject_data.get("project", {}).get("version", "unknown")
72
+ except ImportError:
73
+ pass
74
+ except Exception:
75
+ pass
76
+
77
+ # Final fallback
78
+ return "unknown"
79
+
80
+
81
+ __version__ = _get_version()
@@ -0,0 +1,76 @@
1
+ """Augmentation plugin architecture for SBOM metadata providers.
2
+
3
+ This module provides a plugin-based approach to fetching organizational
4
+ metadata for SBOM augmentation. Multiple providers can supply metadata
5
+ (supplier, authors, licenses, lifecycle_phase, VCS info), which is merged by priority.
6
+
7
+ Providers (in priority order):
8
+ - json-config: Reads from sbomify.json config file (priority 10)
9
+ - github-actions: Auto-detects VCS info from GitHub Actions env (priority 20)
10
+ - gitlab-ci: Auto-detects VCS info from GitLab CI env (priority 20)
11
+ - bitbucket-pipelines: Auto-detects VCS info from Bitbucket Pipelines env (priority 20)
12
+ - sbomify-api: Fetches from sbomify backend API (priority 50)
13
+
14
+ VCS Augmentation:
15
+ CI providers automatically detect repository URL, commit SHA, and branch/ref
16
+ from CI environment variables. This can be:
17
+ - Overridden via sbomify.json config (vcs_url, vcs_commit_sha, vcs_ref)
18
+ - Disabled entirely via DISABLE_VCS_AUGMENTATION=true environment variable
19
+
20
+ Usage:
21
+ from sbomify_action._augmentation import create_default_registry
22
+
23
+ registry = create_default_registry()
24
+ metadata = registry.fetch_metadata(
25
+ component_id="xxx",
26
+ api_base_url="https://app.sbomify.com",
27
+ token="your-token",
28
+ )
29
+ """
30
+
31
+ from .metadata import AugmentationMetadata
32
+ from .protocol import AugmentationProvider
33
+ from .registry import ProviderRegistry
34
+
35
+ __all__ = [
36
+ "AugmentationMetadata",
37
+ "AugmentationProvider",
38
+ "ProviderRegistry",
39
+ "create_default_registry",
40
+ ]
41
+
42
+
43
+ def create_default_registry() -> ProviderRegistry:
44
+ """
45
+ Create a registry with default augmentation providers.
46
+
47
+ Providers are registered in priority order (lower number = higher priority):
48
+ - Priority 10: JsonConfigProvider (local config, can override CI-detected VCS)
49
+ - Priority 20: CI providers (GitHub Actions, GitLab CI, Bitbucket Pipelines)
50
+ - Priority 50: SbomifyApiProvider (backend metadata)
51
+
52
+ Returns:
53
+ ProviderRegistry configured with standard providers
54
+ """
55
+ from .providers import (
56
+ BitbucketPipelinesProvider,
57
+ GitHubActionsProvider,
58
+ GitLabCIProvider,
59
+ JsonConfigProvider,
60
+ SbomifyApiProvider,
61
+ )
62
+
63
+ registry = ProviderRegistry()
64
+
65
+ # Priority 10: Local config (can override CI-detected VCS)
66
+ registry.register(JsonConfigProvider())
67
+
68
+ # Priority 20: CI providers (auto-detect VCS from environment)
69
+ registry.register(GitHubActionsProvider())
70
+ registry.register(GitLabCIProvider())
71
+ registry.register(BitbucketPipelinesProvider())
72
+
73
+ # Priority 50: API provider (backend metadata)
74
+ registry.register(SbomifyApiProvider())
75
+
76
+ return registry
@@ -0,0 +1,227 @@
1
+ """AugmentationMetadata dataclass for normalized augmentation data."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Dict, List, Optional
5
+
6
+
7
+ @dataclass
8
+ class AugmentationMetadata:
9
+ """
10
+ Normalized metadata for SBOM augmentation.
11
+
12
+ This dataclass represents the canonical format for augmentation data
13
+ from any provider. It includes fields for NTIA Minimum Elements and
14
+ CISA 2025 requirements.
15
+
16
+ Attributes:
17
+ supplier: Supplier information (name, urls, contacts)
18
+ manufacturer: Manufacturer information (name, urls, contacts, address)
19
+ authors: List of author information
20
+ licenses: List of license data (strings or dicts)
21
+ lifecycle_phase: CISA 2025 Generation Context (build, post-build, operations, etc.)
22
+ source: Name of the provider that supplied this metadata
23
+
24
+ VCS fields (added by CI providers or sbomify.json config):
25
+ vcs_url: Repository URL (e.g., https://github.com/owner/repo)
26
+ vcs_commit_sha: Full commit SHA
27
+ vcs_ref: Branch or tag name (e.g., main, refs/heads/main, v1.0.0)
28
+ vcs_commit_url: URL to the specific commit
29
+
30
+ Security and support fields (CRA compliance):
31
+ security_contact: URL/email for security vulnerability reporting
32
+ support_period_end: ISO-8601 date when security support ends (YYYY-MM-DD)
33
+
34
+ Lifecycle date fields (from sbomify API):
35
+ release_date: ISO-8601 date when the component was released (YYYY-MM-DD)
36
+ end_of_life: ISO-8601 date when all support ends (YYYY-MM-DD)
37
+ """
38
+
39
+ supplier: Optional[Dict[str, Any]] = None
40
+ manufacturer: Optional[Dict[str, Any]] = None
41
+ authors: Optional[List[Dict[str, Any]]] = None
42
+ licenses: Optional[List[Any]] = None
43
+ lifecycle_phase: Optional[str] = None
44
+ source: Optional[str] = None
45
+
46
+ # VCS fields for CI environment enrichment
47
+ vcs_url: Optional[str] = None
48
+ vcs_commit_sha: Optional[str] = None
49
+ vcs_ref: Optional[str] = None
50
+ vcs_commit_url: Optional[str] = None
51
+
52
+ # Security and support fields (CRA compliance)
53
+ security_contact: Optional[str] = None # URL/email for security vulnerability reporting
54
+ support_period_end: Optional[str] = None # ISO-8601 date when support ends (YYYY-MM-DD)
55
+
56
+ # Lifecycle date fields (from sbomify API)
57
+ release_date: Optional[str] = None # ISO-8601 date when released (YYYY-MM-DD)
58
+ end_of_life: Optional[str] = None # ISO-8601 date when all support ends (YYYY-MM-DD)
59
+
60
+ # Additional fields that may be added in the future
61
+ _extra: Dict[str, Any] = field(default_factory=dict)
62
+
63
+ def has_data(self) -> bool:
64
+ """Check if this metadata contains any meaningful data."""
65
+ return any(
66
+ [
67
+ self.supplier,
68
+ self.manufacturer,
69
+ self.authors,
70
+ self.licenses,
71
+ self.lifecycle_phase,
72
+ self.vcs_url,
73
+ self.vcs_commit_sha,
74
+ self.vcs_ref,
75
+ self.vcs_commit_url,
76
+ self.security_contact,
77
+ self.support_period_end,
78
+ self.release_date,
79
+ self.end_of_life,
80
+ ]
81
+ )
82
+
83
+ def merge(self, other: "AugmentationMetadata") -> "AugmentationMetadata":
84
+ """
85
+ Merge another metadata instance into this one.
86
+
87
+ The current instance's values take precedence (are not overwritten).
88
+ Only missing fields are filled from the other instance.
89
+
90
+ Args:
91
+ other: Another AugmentationMetadata to merge from
92
+
93
+ Returns:
94
+ New AugmentationMetadata with merged values
95
+ """
96
+ # Merge sources for attribution
97
+ sources = []
98
+ if self.source:
99
+ sources.append(self.source)
100
+ if other.source and other.source not in sources:
101
+ sources.append(other.source)
102
+ merged_source = ", ".join(sources) if sources else None
103
+
104
+ return AugmentationMetadata(
105
+ supplier=self.supplier if self.supplier else other.supplier,
106
+ manufacturer=self.manufacturer if self.manufacturer else other.manufacturer,
107
+ authors=self.authors if self.authors else other.authors,
108
+ licenses=self.licenses if self.licenses else other.licenses,
109
+ lifecycle_phase=self.lifecycle_phase if self.lifecycle_phase else other.lifecycle_phase,
110
+ source=merged_source,
111
+ # VCS fields
112
+ vcs_url=self.vcs_url if self.vcs_url else other.vcs_url,
113
+ vcs_commit_sha=self.vcs_commit_sha if self.vcs_commit_sha else other.vcs_commit_sha,
114
+ vcs_ref=self.vcs_ref if self.vcs_ref else other.vcs_ref,
115
+ vcs_commit_url=self.vcs_commit_url if self.vcs_commit_url else other.vcs_commit_url,
116
+ # Security and support fields
117
+ security_contact=self.security_contact if self.security_contact else other.security_contact,
118
+ support_period_end=self.support_period_end if self.support_period_end else other.support_period_end,
119
+ # Lifecycle date fields
120
+ release_date=self.release_date if self.release_date else other.release_date,
121
+ end_of_life=self.end_of_life if self.end_of_life else other.end_of_life,
122
+ _extra={**other._extra, **self._extra}, # self takes precedence
123
+ )
124
+
125
+ def to_dict(self) -> Dict[str, Any]:
126
+ """
127
+ Convert to dictionary format compatible with existing augmentation functions.
128
+
129
+ Returns:
130
+ Dictionary with augmentation data
131
+ """
132
+ result: Dict[str, Any] = {}
133
+
134
+ if self.supplier:
135
+ result["supplier"] = self.supplier
136
+ if self.manufacturer:
137
+ result["manufacturer"] = self.manufacturer
138
+ if self.authors:
139
+ result["authors"] = self.authors
140
+ if self.licenses:
141
+ result["licenses"] = self.licenses
142
+ if self.lifecycle_phase:
143
+ result["lifecycle_phase"] = self.lifecycle_phase
144
+
145
+ # VCS fields
146
+ if self.vcs_url:
147
+ result["vcs_url"] = self.vcs_url
148
+ if self.vcs_commit_sha:
149
+ result["vcs_commit_sha"] = self.vcs_commit_sha
150
+ if self.vcs_ref:
151
+ result["vcs_ref"] = self.vcs_ref
152
+ if self.vcs_commit_url:
153
+ result["vcs_commit_url"] = self.vcs_commit_url
154
+
155
+ # Security and support fields
156
+ if self.security_contact:
157
+ result["security_contact"] = self.security_contact
158
+ if self.support_period_end:
159
+ result["support_period_end"] = self.support_period_end
160
+
161
+ # Lifecycle date fields
162
+ if self.release_date:
163
+ result["release_date"] = self.release_date
164
+ if self.end_of_life:
165
+ result["end_of_life"] = self.end_of_life
166
+
167
+ # Include any extra fields
168
+ result.update(self._extra)
169
+
170
+ return result
171
+
172
+ @classmethod
173
+ def from_dict(cls, data: Dict[str, Any], source: Optional[str] = None) -> "AugmentationMetadata":
174
+ """
175
+ Create AugmentationMetadata from a dictionary.
176
+
177
+ Handles both config file field names and sbomify API field names:
178
+ - API uses 'end_of_support' -> maps to 'support_period_end'
179
+
180
+ Args:
181
+ data: Dictionary with augmentation data
182
+ source: Name of the source that provided this data
183
+
184
+ Returns:
185
+ New AugmentationMetadata instance
186
+ """
187
+ known_keys = {
188
+ "supplier",
189
+ "manufacturer",
190
+ "authors",
191
+ "licenses",
192
+ "lifecycle_phase",
193
+ "vcs_url",
194
+ "vcs_commit_sha",
195
+ "vcs_ref",
196
+ "vcs_commit_url",
197
+ "security_contact",
198
+ "support_period_end",
199
+ "release_date",
200
+ "end_of_life",
201
+ # API field names (mapped to internal names)
202
+ "end_of_support", # -> support_period_end
203
+ }
204
+ extra = {k: v for k, v in data.items() if k not in known_keys}
205
+
206
+ # Map API field names to internal names
207
+ # API uses 'end_of_support', we use 'support_period_end'
208
+ # Prefer explicit support_period_end, fall back to API's end_of_support
209
+ support_period_end = data.get("support_period_end") or data.get("end_of_support")
210
+
211
+ return cls(
212
+ supplier=data.get("supplier"),
213
+ manufacturer=data.get("manufacturer"),
214
+ authors=data.get("authors"),
215
+ licenses=data.get("licenses"),
216
+ lifecycle_phase=data.get("lifecycle_phase"),
217
+ source=source,
218
+ vcs_url=data.get("vcs_url"),
219
+ vcs_commit_sha=data.get("vcs_commit_sha"),
220
+ vcs_ref=data.get("vcs_ref"),
221
+ vcs_commit_url=data.get("vcs_commit_url"),
222
+ security_contact=data.get("security_contact"),
223
+ support_period_end=support_period_end,
224
+ release_date=data.get("release_date"),
225
+ end_of_life=data.get("end_of_life"),
226
+ _extra=extra,
227
+ )
@@ -0,0 +1,80 @@
1
+ """AugmentationProvider protocol for SBOM augmentation plugins."""
2
+
3
+ from typing import Optional, Protocol
4
+
5
+ from .metadata import AugmentationMetadata
6
+
7
+
8
+ class AugmentationProvider(Protocol):
9
+ """
10
+ Protocol defining the interface for augmentation provider plugins.
11
+
12
+ Each provider implements this protocol to supply organizational metadata
13
+ for SBOM augmentation (supplier, authors, licenses, lifecycle_phase).
14
+ Providers have priorities - lower numbers indicate higher priority (tried first).
15
+
16
+ Example:
17
+ class JsonConfigProvider:
18
+ name = "json-config"
19
+ priority = 10 # High priority - local config takes precedence
20
+
21
+ def fetch(self, component_id: str | None = None, **kwargs) -> Optional[AugmentationMetadata]:
22
+ # Read from sbomify.json and return metadata
23
+ ...
24
+ """
25
+
26
+ @property
27
+ def name(self) -> str:
28
+ """
29
+ Human-readable name of this provider.
30
+
31
+ Used for logging and tracking which provider supplied metadata.
32
+ Examples: "json-config", "sbomify-api", "env-vars"
33
+ """
34
+ ...
35
+
36
+ @property
37
+ def priority(self) -> int:
38
+ """
39
+ Priority of this provider (lower = higher priority).
40
+
41
+ When multiple providers are available, they are tried in priority order.
42
+ Local/static sources should have low priorities (e.g., 10), API sources
43
+ should have higher priorities (e.g., 50).
44
+
45
+ Recommended priority ranges:
46
+ - 1-20: Local config files (JSON, YAML)
47
+ - 21-40: Environment variables
48
+ - 41-60: API sources (sbomify API)
49
+ - 61-100: Fallback sources
50
+ """
51
+ ...
52
+
53
+ def fetch(
54
+ self,
55
+ component_id: Optional[str] = None,
56
+ api_base_url: Optional[str] = None,
57
+ token: Optional[str] = None,
58
+ config_path: Optional[str] = None,
59
+ **kwargs,
60
+ ) -> Optional[AugmentationMetadata]:
61
+ """
62
+ Fetch augmentation metadata from this provider.
63
+
64
+ Implementations should:
65
+ 1. Retrieve metadata from their source (file, API, env vars, etc.)
66
+ 2. Return normalized AugmentationMetadata
67
+ 3. Handle errors gracefully (return None on failure)
68
+ 4. Set the 'source' field on the returned metadata
69
+
70
+ Args:
71
+ component_id: Component ID (required for API providers)
72
+ api_base_url: API base URL (for API providers)
73
+ token: Authentication token (for API providers)
74
+ config_path: Path to config file (for file-based providers)
75
+ **kwargs: Additional provider-specific arguments
76
+
77
+ Returns:
78
+ AugmentationMetadata if successful, None if fetch fails or no data
79
+ """
80
+ ...
@@ -0,0 +1,18 @@
1
+ """Augmentation providers for fetching organizational metadata."""
2
+
3
+ # Re-export utility from parent module for backwards compatibility
4
+ from ..utils import is_vcs_augmentation_disabled
5
+ from .bitbucket import BitbucketPipelinesProvider
6
+ from .github import GitHubActionsProvider
7
+ from .gitlab import GitLabCIProvider
8
+ from .json_config import JsonConfigProvider
9
+ from .sbomify_api import SbomifyApiProvider
10
+
11
+ __all__ = [
12
+ "BitbucketPipelinesProvider",
13
+ "GitHubActionsProvider",
14
+ "GitLabCIProvider",
15
+ "JsonConfigProvider",
16
+ "SbomifyApiProvider",
17
+ "is_vcs_augmentation_disabled",
18
+ ]
@@ -0,0 +1,105 @@
1
+ """Bitbucket Pipelines provider for VCS augmentation metadata.
2
+
3
+ This provider detects when running in Bitbucket Pipelines and extracts
4
+ VCS information from environment variables. Supports both Bitbucket Cloud
5
+ and attempts best-effort support for Bitbucket Data Center.
6
+
7
+ Environment variables used (Cloud):
8
+ - BITBUCKET_PIPELINE_UUID: Detection (present when in Bitbucket Pipelines)
9
+ - BITBUCKET_GIT_HTTP_ORIGIN: Repository URL
10
+ - BITBUCKET_COMMIT: Full commit SHA
11
+ - BITBUCKET_BRANCH: Branch name (not set for tags)
12
+ - BITBUCKET_TAG: Tag name (not set for branches)
13
+ - BITBUCKET_WORKSPACE: Workspace name
14
+ - BITBUCKET_REPO_SLUG: Repository slug
15
+
16
+ For Bitbucket Data Center, environment variables may differ. Use sbomify.json
17
+ to configure VCS URL manually if auto-detection fails.
18
+
19
+ Set DISABLE_VCS_AUGMENTATION=true to disable VCS enrichment.
20
+ """
21
+
22
+ import os
23
+ from typing import Optional
24
+
25
+ from sbomify_action.logging_config import logger
26
+
27
+ from ..metadata import AugmentationMetadata
28
+ from ..utils import is_vcs_augmentation_disabled, truncate_sha
29
+
30
+
31
+ class BitbucketPipelinesProvider:
32
+ """
33
+ Provider that extracts VCS metadata from Bitbucket Pipelines environment.
34
+
35
+ This provider has priority 20, which is lower than sbomify.json (10),
36
+ allowing local config to override auto-detected values.
37
+ """
38
+
39
+ name: str = "bitbucket-pipelines"
40
+ priority: int = 20
41
+
42
+ def fetch(
43
+ self,
44
+ component_id: Optional[str] = None,
45
+ api_base_url: Optional[str] = None,
46
+ token: Optional[str] = None,
47
+ config_path: Optional[str] = None,
48
+ **kwargs,
49
+ ) -> Optional[AugmentationMetadata]:
50
+ """
51
+ Extract VCS metadata from Bitbucket Pipelines environment variables.
52
+
53
+ Args:
54
+ component_id: Ignored (not needed for CI provider)
55
+ api_base_url: Ignored (not needed for CI provider)
56
+ token: Ignored (not needed for CI provider)
57
+ config_path: Ignored (not needed for CI provider)
58
+ **kwargs: Additional arguments (ignored)
59
+
60
+ Returns:
61
+ AugmentationMetadata with VCS info if in Bitbucket Pipelines, None otherwise
62
+ """
63
+ # Check if VCS augmentation is disabled
64
+ if is_vcs_augmentation_disabled():
65
+ logger.debug("VCS augmentation disabled, skipping Bitbucket Pipelines provider")
66
+ return None
67
+
68
+ # Check if we're running in Bitbucket Pipelines
69
+ if not os.getenv("BITBUCKET_PIPELINE_UUID"):
70
+ return None
71
+
72
+ # Extract VCS information
73
+ # BITBUCKET_GIT_HTTP_ORIGIN contains the full repo URL for Cloud
74
+ vcs_url = os.getenv("BITBUCKET_GIT_HTTP_ORIGIN")
75
+ commit_sha = os.getenv("BITBUCKET_COMMIT")
76
+ # Branch or tag
77
+ ref = os.getenv("BITBUCKET_BRANCH") or os.getenv("BITBUCKET_TAG")
78
+
79
+ # Fallback: try to construct URL from workspace and repo slug
80
+ if not vcs_url:
81
+ workspace = os.getenv("BITBUCKET_WORKSPACE")
82
+ repo_slug = os.getenv("BITBUCKET_REPO_SLUG")
83
+ if workspace and repo_slug:
84
+ vcs_url = f"https://bitbucket.org/{workspace}/{repo_slug}"
85
+ logger.debug("Constructed Bitbucket URL from workspace/repo slug")
86
+
87
+ if not vcs_url:
88
+ logger.warning(
89
+ "Bitbucket Pipelines detected but could not determine repository URL. "
90
+ "For Bitbucket Data Center, configure vcs_url in sbomify.json."
91
+ )
92
+ return None
93
+
94
+ # Construct commit URL
95
+ vcs_commit_url = f"{vcs_url}/commits/{commit_sha}" if commit_sha else None
96
+
97
+ logger.info(f"Detected Bitbucket Pipelines: {vcs_url} @ {truncate_sha(commit_sha)}")
98
+
99
+ return AugmentationMetadata(
100
+ source=self.name,
101
+ vcs_url=vcs_url,
102
+ vcs_commit_sha=commit_sha,
103
+ vcs_ref=ref,
104
+ vcs_commit_url=vcs_commit_url,
105
+ )
@@ -0,0 +1,97 @@
1
+ """GitHub Actions provider for VCS augmentation metadata.
2
+
3
+ This provider detects when running in GitHub Actions and extracts
4
+ VCS information from environment variables. Supports both GitHub.com
5
+ and GitHub Enterprise Server via GITHUB_SERVER_URL.
6
+
7
+ Environment variables used:
8
+ - GITHUB_ACTIONS: Detection ("true" when in GitHub Actions)
9
+ - GITHUB_SERVER_URL: Server URL (e.g., https://github.com or https://github.mycompany.com)
10
+ - GITHUB_REPOSITORY: Repository in owner/repo format
11
+ - GITHUB_SHA: Full commit SHA
12
+ - GITHUB_REF: Git ref (e.g., refs/heads/main, refs/tags/v1.0.0)
13
+ - GITHUB_REF_NAME: Short ref name (e.g., main, v1.0.0)
14
+
15
+ Set DISABLE_VCS_AUGMENTATION=true to disable VCS enrichment.
16
+ """
17
+
18
+ import os
19
+ from typing import Optional
20
+
21
+ from sbomify_action.logging_config import logger
22
+
23
+ from ..metadata import AugmentationMetadata
24
+ from ..utils import is_vcs_augmentation_disabled, truncate_sha
25
+
26
+
27
+ class GitHubActionsProvider:
28
+ """
29
+ Provider that extracts VCS metadata from GitHub Actions environment.
30
+
31
+ This provider has priority 20, which is lower than sbomify.json (10),
32
+ allowing local config to override auto-detected values.
33
+ """
34
+
35
+ name: str = "github-actions"
36
+ priority: int = 20
37
+
38
+ def fetch(
39
+ self,
40
+ component_id: Optional[str] = None,
41
+ api_base_url: Optional[str] = None,
42
+ token: Optional[str] = None,
43
+ config_path: Optional[str] = None,
44
+ **kwargs,
45
+ ) -> Optional[AugmentationMetadata]:
46
+ """
47
+ Extract VCS metadata from GitHub Actions environment variables.
48
+
49
+ Args:
50
+ component_id: Ignored (not needed for CI provider)
51
+ api_base_url: Ignored (not needed for CI provider)
52
+ token: Ignored (not needed for CI provider)
53
+ config_path: Ignored (not needed for CI provider)
54
+ **kwargs: Additional arguments (ignored)
55
+
56
+ Returns:
57
+ AugmentationMetadata with VCS info if in GitHub Actions, None otherwise
58
+ """
59
+ # Check if VCS augmentation is disabled
60
+ if is_vcs_augmentation_disabled():
61
+ logger.debug("VCS augmentation disabled, skipping GitHub Actions provider")
62
+ return None
63
+
64
+ # Check if we're running in GitHub Actions
65
+ if os.getenv("GITHUB_ACTIONS") != "true":
66
+ return None
67
+
68
+ # Extract VCS information
69
+ server_url = os.getenv("GITHUB_SERVER_URL", "https://github.com")
70
+ repository = os.getenv("GITHUB_REPOSITORY")
71
+ commit_sha = os.getenv("GITHUB_SHA")
72
+ ref = os.getenv("GITHUB_REF_NAME") or os.getenv("GITHUB_REF")
73
+
74
+ if not repository:
75
+ logger.warning("GitHub Actions detected but GITHUB_REPOSITORY not set")
76
+ return None
77
+
78
+ # Construct URLs
79
+ vcs_url = f"{server_url}/{repository}"
80
+ vcs_commit_url = f"{server_url}/{repository}/commit/{commit_sha}" if commit_sha else None
81
+
82
+ # Clean up ref (remove refs/heads/ or refs/tags/ prefix if present)
83
+ if ref:
84
+ if ref.startswith("refs/heads/"):
85
+ ref = ref[len("refs/heads/") :]
86
+ elif ref.startswith("refs/tags/"):
87
+ ref = ref[len("refs/tags/") :]
88
+
89
+ logger.info(f"Detected GitHub Actions: {repository} @ {truncate_sha(commit_sha)}")
90
+
91
+ return AugmentationMetadata(
92
+ source=self.name,
93
+ vcs_url=vcs_url,
94
+ vcs_commit_sha=commit_sha,
95
+ vcs_ref=ref,
96
+ vcs_commit_url=vcs_commit_url,
97
+ )