Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .github/workflows/fetch_upload_sboms.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Fetch and Upload Temurin SBOMs to DependencyTrack

on:
workflow_dispatch:

env:
API_URL_BASE: https://api.adoptium.net/v3/assets/feature_releases/21/ga
IMAGE_TYPE: sbom
VENDOR: eclipse
HEAP_SIZE: normal
PAGE_SIZE: 20
DEPENDENCY_TRACK_URL: https://sbom.eclipse.org
PROJECT_ROOT: Temurin
JAVA_VERSION: JDK 21

jobs:
fetch-sboms:
runs-on: ubuntu-latest

outputs: # Expose matrix output for next job
sbom-metadata: ${{ steps.export.outputs.matrix }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11

- name: Install Python dependencies
run: |
pip install requests
pip install os
pip install json
pip install time
pip install pathlib
pip install datetime

- name: Run SBOM fetcher script
run: python scripts/fetch_sboms.py

- name: Upload SBOMs and metadata
uses: actions/upload-artifact@v4
with:
name: sboms
path: |
sboms/ # Folder with downloaded SBOMs
metadata.json # JSON metadata for the next matrix job

- id: export # Step to read metadata.json and output a JSON matrix
run: |
matrix=$(jq -c '.' metadata.json) # Read the JSON file as a single-line JSON array
echo "matrix=$matrix" >> $GITHUB_OUTPUT

store-each-sbom:
needs: fetch-sboms
runs-on: ubuntu-latest

strategy:
matrix:
include: ${{ fromJson(needs.fetch-sboms.outputs.sbom-metadata) }}
# This creates one matrix job per object in the metadata.json list

steps:
- name: Download SBOMs
uses: actions/download-artifact@v4
with:
name: sboms
path: workspace

- name: Store SBOM in DependencyTrack # Reusable workflow that uploads to DependencyTrack
uses: eclipse-csi/workflows/.github/workflows/store-sbom-data.yml@main
with:
projectName: ${{ matrix.projectName }}
projectVersion: ${{ matrix.projectVersion }}
bomArtifact: sboms
bomFilename: workspace/${{ matrix.path }}
parentProject: '<parentProject_ID>' # must be replaced with real UUID
110 changes: 110 additions & 0 deletions scripts/fetch_sboms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import os
import requests
import json
import time
from pathlib import Path
from datetime import datetime


API_URL_BASE = os.environ.get("API_URL_BASE", "https://api.adoptium.net/v3/assets/feature_releases/21/ga")
IMAGE_TYPE = os.environ.get("IMAGE_TYPE", "sbom")
VENDOR = os.environ.get("VENDOR", "eclipse")
HEAP_SIZE = os.environ.get("HEAP_SIZE", "normal")
PAGE_SIZE = int(os.environ.get("PAGE_SIZE", "20"))
PROJECT_ROOT = os.environ.get("PROJECT_ROOT", "Temurin")
JAVA_VERSION = os.environ.get("JAVA_VERSION", "JDK 21")
cutoff_date = datetime(2023, 1, 1).date()

def fetch_with_retry(url, retries=3, delay=2):
for attempt in range(retries):
try:
response = requests.get(url)
response.raise_for_status()
return response
except requests.RequestException as e:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt < retries - 1:
time.sleep(delay)
raise Exception(f"Failed to fetch {url} after {retries} attempts.")

def fetch_sboms():
sbom_dir = Path("sboms")
sbom_dir.mkdir(exist_ok=True)
metadata = []

page = 1
before = None

while True:
print(f"Fetching page {page}...")
params = {
"image_type": IMAGE_TYPE,
"vendor": VENDOR,
"heap_size": HEAP_SIZE,
"page_size": PAGE_SIZE
}
if before:
params["before"] = before

response = requests.get(API_URL_BASE, params=params)
response.raise_for_status()
data = response.json()

if not data:
print("No more results.")
break

stop = False

for asset in data:
# we stop if the last asset is before the cutoff date
release_date_str = asset["timestamp"]
release_date = datetime.fromisoformat(release_date_str.replace("Z", "")).date()

if release_date < cutoff_date:
stop = True
break

version = asset["version_data"]["semver"]
for binary in asset.get("binaries", []):
os_name = binary["os"]
arch = binary["architecture"]
sbom_url = binary.get("package", {}).get("link")

if not sbom_url:
print(f"Skipping {version} ({os_name} {arch}) - no SBOM")
continue

os_arch = f"{os_name} {arch}"
# save path
folder = sbom_dir / os_arch / f"jdk-{version}"
folder.mkdir(parents=True, exist_ok=True)
path = folder / "sbom.json"

path.parent.mkdir(parents=True, exist_ok=True)
print(f"Downloading SBOM for {os_name} {arch} {version}")
sbom_resp = fetch_with_retry(sbom_url)
sbom_resp.raise_for_status()
path.write_text(sbom_resp.text)

project_name = f"{PROJECT_ROOT} / {JAVA_VERSION} / {os_name} {arch} / jdk-{version}"
metadata.append({
"path": str(path),
"projectName": project_name,
"projectVersion": version
})

if stop:
print(f"Stopping fetch as the last asset is before the cutoff date: {cutoff_date}")
break

before = data[-1]["timestamp"].split("T")[0]
page += 1
time.sleep(1)

with open("metadata.json", "w") as f:
json.dump(metadata, f, indent=2)
print("Done. Wrote SBOMs and metadata.json.")

if __name__ == "__main__":
fetch_sboms()
Loading