sbomify-action 0.10__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 (87) hide show
  1. sbomify_action/__init__.py +60 -0
  2. sbomify_action/_augmentation/__init__.py +76 -0
  3. sbomify_action/_augmentation/metadata.py +171 -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 +146 -0
  10. sbomify_action/_augmentation/providers/sbomify_api.py +125 -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 +268 -0
  15. sbomify_action/_enrichment/license_db_generator.py +1518 -0
  16. sbomify_action/_enrichment/license_normalizer.py +533 -0
  17. sbomify_action/_enrichment/license_utils.py +292 -0
  18. sbomify_action/_enrichment/metadata.py +132 -0
  19. sbomify_action/_enrichment/protocol.py +89 -0
  20. sbomify_action/_enrichment/registry.py +128 -0
  21. sbomify_action/_enrichment/sanitization.py +350 -0
  22. sbomify_action/_enrichment/sources/__init__.py +25 -0
  23. sbomify_action/_enrichment/sources/clearlydefined.py +200 -0
  24. sbomify_action/_enrichment/sources/cratesio.py +187 -0
  25. sbomify_action/_enrichment/sources/debian.py +257 -0
  26. sbomify_action/_enrichment/sources/depsdev.py +202 -0
  27. sbomify_action/_enrichment/sources/ecosystems.py +213 -0
  28. sbomify_action/_enrichment/sources/license_db.py +576 -0
  29. sbomify_action/_enrichment/sources/pubdev.py +195 -0
  30. sbomify_action/_enrichment/sources/purl.py +173 -0
  31. sbomify_action/_enrichment/sources/pypi.py +197 -0
  32. sbomify_action/_enrichment/sources/repology.py +245 -0
  33. sbomify_action/_enrichment/utils.py +59 -0
  34. sbomify_action/_generation/__init__.py +68 -0
  35. sbomify_action/_generation/generator.py +187 -0
  36. sbomify_action/_generation/generators/__init__.py +29 -0
  37. sbomify_action/_generation/generators/cdxgen.py +331 -0
  38. sbomify_action/_generation/generators/cyclonedx_cargo.py +161 -0
  39. sbomify_action/_generation/generators/cyclonedx_py.py +225 -0
  40. sbomify_action/_generation/generators/syft.py +307 -0
  41. sbomify_action/_generation/generators/trivy.py +317 -0
  42. sbomify_action/_generation/protocol.py +225 -0
  43. sbomify_action/_generation/registry.py +277 -0
  44. sbomify_action/_generation/result.py +86 -0
  45. sbomify_action/_generation/utils.py +431 -0
  46. sbomify_action/_processors/__init__.py +44 -0
  47. sbomify_action/_processors/orchestrator.py +152 -0
  48. sbomify_action/_processors/processors/__init__.py +5 -0
  49. sbomify_action/_processors/processors/releases.py +197 -0
  50. sbomify_action/_processors/protocol.py +100 -0
  51. sbomify_action/_processors/registry.py +152 -0
  52. sbomify_action/_processors/releases_api.py +314 -0
  53. sbomify_action/_processors/result.py +141 -0
  54. sbomify_action/_upload/__init__.py +62 -0
  55. sbomify_action/_upload/destinations/__init__.py +10 -0
  56. sbomify_action/_upload/destinations/dependency_track.py +252 -0
  57. sbomify_action/_upload/destinations/sbomify.py +206 -0
  58. sbomify_action/_upload/orchestrator.py +156 -0
  59. sbomify_action/_upload/protocol.py +145 -0
  60. sbomify_action/_upload/registry.py +152 -0
  61. sbomify_action/_upload/result.py +80 -0
  62. sbomify_action/additional_packages.py +433 -0
  63. sbomify_action/augmentation.py +1507 -0
  64. sbomify_action/cli/__init__.py +0 -0
  65. sbomify_action/cli/main.py +1486 -0
  66. sbomify_action/console.py +531 -0
  67. sbomify_action/enrichment.py +1079 -0
  68. sbomify_action/exceptions.py +29 -0
  69. sbomify_action/generation.py +189 -0
  70. sbomify_action/http_client.py +46 -0
  71. sbomify_action/logging_config.py +93 -0
  72. sbomify_action/schemas/cyclonedx/cdx-1.3.schema.json +1058 -0
  73. sbomify_action/schemas/cyclonedx/cdx-1.4.schema.json +1697 -0
  74. sbomify_action/schemas/cyclonedx/cdx-1.5.schema.json +3799 -0
  75. sbomify_action/schemas/cyclonedx/cdx-1.6.schema.json +5699 -0
  76. sbomify_action/schemas/cyclonedx/cdx-1.7.schema.json +6700 -0
  77. sbomify_action/schemas/spdx/spdx-2.2.schema.json +721 -0
  78. sbomify_action/schemas/spdx/spdx-2.3.schema.json +746 -0
  79. sbomify_action/serialization.py +762 -0
  80. sbomify_action/tool_checks.py +338 -0
  81. sbomify_action/upload.py +180 -0
  82. sbomify_action/validation.py +302 -0
  83. sbomify_action-0.10.dist-info/METADATA +712 -0
  84. sbomify_action-0.10.dist-info/RECORD +87 -0
  85. sbomify_action-0.10.dist-info/WHEEL +4 -0
  86. sbomify_action-0.10.dist-info/entry_points.txt +3 -0
  87. sbomify_action-0.10.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,60 @@
1
+ """sbomify-action package for SBOM generation and enrichment."""
2
+
3
+
4
+ def _get_version() -> str:
5
+ """Get package version with fallback mechanisms.
6
+
7
+ Tries the following methods in order until one succeeds:
8
+ - importlib.metadata (preferred for installed packages)
9
+ - tomllib/pyproject.toml (Python 3.11+, for development)
10
+ - toml library/pyproject.toml (older Python, for development)
11
+ - Returns "unknown" if all methods fail
12
+ """
13
+ # Try importlib.metadata (preferred for installed packages)
14
+ try:
15
+ from importlib.metadata import version
16
+
17
+ return version("sbomify-action")
18
+ except ImportError:
19
+ pass
20
+ except Exception:
21
+ pass
22
+
23
+ # Try reading from pyproject.toml using tomllib (Python 3.11+)
24
+ try:
25
+ from pathlib import Path
26
+
27
+ import tomllib
28
+
29
+ pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
30
+ if pyproject_path.exists():
31
+ with open(pyproject_path, "rb") as f:
32
+ pyproject_data = tomllib.load(f)
33
+ return pyproject_data.get("project", {}).get("version", "unknown")
34
+ except ImportError:
35
+ # Python < 3.11 doesn't have tomllib
36
+ pass
37
+ except Exception:
38
+ pass
39
+
40
+ # Try toml library as fallback for older Python
41
+ try:
42
+ from pathlib import Path
43
+
44
+ import toml
45
+
46
+ pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
47
+ if pyproject_path.exists():
48
+ with open(pyproject_path, "r") as f:
49
+ pyproject_data = toml.load(f)
50
+ return pyproject_data.get("project", {}).get("version", "unknown")
51
+ except ImportError:
52
+ pass
53
+ except Exception:
54
+ pass
55
+
56
+ # Final fallback
57
+ return "unknown"
58
+
59
+
60
+ __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,171 @@
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
+
31
+ supplier: Optional[Dict[str, Any]] = None
32
+ manufacturer: Optional[Dict[str, Any]] = None
33
+ authors: Optional[List[Dict[str, Any]]] = None
34
+ licenses: Optional[List[Any]] = None
35
+ lifecycle_phase: Optional[str] = None
36
+ source: Optional[str] = None
37
+
38
+ # VCS fields for CI environment enrichment
39
+ vcs_url: Optional[str] = None
40
+ vcs_commit_sha: Optional[str] = None
41
+ vcs_ref: Optional[str] = None
42
+ vcs_commit_url: Optional[str] = None
43
+
44
+ # Additional fields that may be added in the future
45
+ _extra: Dict[str, Any] = field(default_factory=dict)
46
+
47
+ def has_data(self) -> bool:
48
+ """Check if this metadata contains any meaningful data."""
49
+ return any(
50
+ [
51
+ self.supplier,
52
+ self.manufacturer,
53
+ self.authors,
54
+ self.licenses,
55
+ self.lifecycle_phase,
56
+ self.vcs_url,
57
+ self.vcs_commit_sha,
58
+ self.vcs_ref,
59
+ self.vcs_commit_url,
60
+ ]
61
+ )
62
+
63
+ def merge(self, other: "AugmentationMetadata") -> "AugmentationMetadata":
64
+ """
65
+ Merge another metadata instance into this one.
66
+
67
+ The current instance's values take precedence (are not overwritten).
68
+ Only missing fields are filled from the other instance.
69
+
70
+ Args:
71
+ other: Another AugmentationMetadata to merge from
72
+
73
+ Returns:
74
+ New AugmentationMetadata with merged values
75
+ """
76
+ # Merge sources for attribution
77
+ sources = []
78
+ if self.source:
79
+ sources.append(self.source)
80
+ if other.source and other.source not in sources:
81
+ sources.append(other.source)
82
+ merged_source = ", ".join(sources) if sources else None
83
+
84
+ return AugmentationMetadata(
85
+ supplier=self.supplier if self.supplier else other.supplier,
86
+ manufacturer=self.manufacturer if self.manufacturer else other.manufacturer,
87
+ authors=self.authors if self.authors else other.authors,
88
+ licenses=self.licenses if self.licenses else other.licenses,
89
+ lifecycle_phase=self.lifecycle_phase if self.lifecycle_phase else other.lifecycle_phase,
90
+ source=merged_source,
91
+ # VCS fields
92
+ vcs_url=self.vcs_url if self.vcs_url else other.vcs_url,
93
+ vcs_commit_sha=self.vcs_commit_sha if self.vcs_commit_sha else other.vcs_commit_sha,
94
+ vcs_ref=self.vcs_ref if self.vcs_ref else other.vcs_ref,
95
+ vcs_commit_url=self.vcs_commit_url if self.vcs_commit_url else other.vcs_commit_url,
96
+ _extra={**other._extra, **self._extra}, # self takes precedence
97
+ )
98
+
99
+ def to_dict(self) -> Dict[str, Any]:
100
+ """
101
+ Convert to dictionary format compatible with existing augmentation functions.
102
+
103
+ Returns:
104
+ Dictionary with augmentation data
105
+ """
106
+ result: Dict[str, Any] = {}
107
+
108
+ if self.supplier:
109
+ result["supplier"] = self.supplier
110
+ if self.manufacturer:
111
+ result["manufacturer"] = self.manufacturer
112
+ if self.authors:
113
+ result["authors"] = self.authors
114
+ if self.licenses:
115
+ result["licenses"] = self.licenses
116
+ if self.lifecycle_phase:
117
+ result["lifecycle_phase"] = self.lifecycle_phase
118
+
119
+ # VCS fields
120
+ if self.vcs_url:
121
+ result["vcs_url"] = self.vcs_url
122
+ if self.vcs_commit_sha:
123
+ result["vcs_commit_sha"] = self.vcs_commit_sha
124
+ if self.vcs_ref:
125
+ result["vcs_ref"] = self.vcs_ref
126
+ if self.vcs_commit_url:
127
+ result["vcs_commit_url"] = self.vcs_commit_url
128
+
129
+ # Include any extra fields
130
+ result.update(self._extra)
131
+
132
+ return result
133
+
134
+ @classmethod
135
+ def from_dict(cls, data: Dict[str, Any], source: Optional[str] = None) -> "AugmentationMetadata":
136
+ """
137
+ Create AugmentationMetadata from a dictionary.
138
+
139
+ Args:
140
+ data: Dictionary with augmentation data
141
+ source: Name of the source that provided this data
142
+
143
+ Returns:
144
+ New AugmentationMetadata instance
145
+ """
146
+ known_keys = {
147
+ "supplier",
148
+ "manufacturer",
149
+ "authors",
150
+ "licenses",
151
+ "lifecycle_phase",
152
+ "vcs_url",
153
+ "vcs_commit_sha",
154
+ "vcs_ref",
155
+ "vcs_commit_url",
156
+ }
157
+ extra = {k: v for k, v in data.items() if k not in known_keys}
158
+
159
+ return cls(
160
+ supplier=data.get("supplier"),
161
+ manufacturer=data.get("manufacturer"),
162
+ authors=data.get("authors"),
163
+ licenses=data.get("licenses"),
164
+ lifecycle_phase=data.get("lifecycle_phase"),
165
+ source=source,
166
+ vcs_url=data.get("vcs_url"),
167
+ vcs_commit_sha=data.get("vcs_commit_sha"),
168
+ vcs_ref=data.get("vcs_ref"),
169
+ vcs_commit_url=data.get("vcs_commit_url"),
170
+ _extra=extra,
171
+ )
@@ -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
+ )