1+ #! /usr/bin/env bash
2+
3+ # Script to add license information to SBOM components
4+ # Usage:
5+ # ./add-license-to-sbom.sh <sbom_file> <component_name> <license_identifier> [version=<version>] [type=<type>] [--custom_license] [--strict] [--help]
6+ # Example: ./add-license-to-sbom.sh sbom.json "backend" "Apache-2.0" version="v1.0.0"
7+ # Example: ./add-license-to-sbom.sh sbom.json "frontend" "Chainloop Proprietary License" type="library" --custom_license --strict
8+
9+ set -euo pipefail
10+
11+ # Check for help flag first
12+ if [[ " ${1:- } " == " --help" ]] || [[ " ${1:- } " == " -h" ]]; then
13+ cat << EOF
14+ SBOM License Addition Script
15+
16+ This script adds license information to SBOM components if they exist and don't already have licenses.
17+
18+ Usage:
19+ ./add-license-to-sbom.sh <sbom_file> <component_name> <license_identifier> [options]
20+
21+ Arguments:
22+ sbom_file Path to the SBOM file (CycloneDX JSON format)
23+ component_name Name of the component to add license to
24+ license_identifier SPDX license identifier (e.g., "Apache-2.0", "MIT") or custom license name
25+
26+ Options:
27+ version=<ver> Match component by name and version
28+ type=<type> Match component by name and type
29+ --custom_license Treat license_identifier as custom license name (default: SPDX identifier)
30+ --strict Fail if component doesn't exist or already has licenses
31+ --help, -h Show this help message
32+
33+ Examples:
34+ # Add SPDX license identifier to component by name and version
35+ ./add-license-to-sbom.sh sbom.json "backend" "Apache-2.0" version="v1.0.0"
36+
37+ # Add SPDX license identifier to component by name and type
38+ ./add-license-to-sbom.sh sbom.json "frontend" "MIT" type="library"
39+
40+ # Add custom license name to component
41+ ./add-license-to-sbom.sh sbom.json "backend" "Chainloop Proprietary License" --custom_license
42+
43+ # Use strict mode (fail if component missing or has licenses)
44+ ./add-license-to-sbom.sh sbom.json "cli" "BSD-3-Clause" --strict
45+
46+ # Match by name only with SPDX identifier
47+ ./add-license-to-sbom.sh sbom.json "backend" "GPL-3.0-or-later"
48+
49+ Behavior:
50+ - Default: Skips with message if component not found or already has licenses
51+ - Strict: Exits with error code 1 if component not found or already has licenses
52+ EOF
53+ exit 0
54+ fi
55+
56+ # Validate required arguments
57+ if [[ $# -lt 3 ]]; then
58+ echo " Error: Missing required arguments"
59+ echo " Use --help for usage information"
60+ exit 1
61+ fi
62+
63+ SBOM_FILE=" $1 "
64+ COMPONENT_NAME=" $2 "
65+ LICENSE_NAME=" $3 "
66+
67+ # Validate SBOM file exists
68+ if [[ ! -f " $SBOM_FILE " ]]; then
69+ echo " Error: SBOM file '$SBOM_FILE ' does not exist"
70+ exit 1
71+ fi
72+
73+ # Initialize optional parameters
74+ VERSION=" "
75+ TYPE=" "
76+ CUSTOM_LICENSE=false # Default to SPDX identifier format
77+ STRICT_MODE=false
78+
79+ # Parse optional parameters from command line arguments (starting from 4th argument)
80+ for arg in " ${@: 4} " ; do
81+ case $arg in
82+ version=* )
83+ # Extract version value after the '=' sign
84+ VERSION=" ${arg#* =} "
85+ ;;
86+ type=* )
87+ # Extract type value after the '=' sign
88+ TYPE=" ${arg#* =} "
89+ ;;
90+ --custom_license)
91+ # Treat license_identifier as custom license name instead of SPDX identifier
92+ CUSTOM_LICENSE=true
93+ ;;
94+ --strict)
95+ # Enable strict mode - will fail if component missing or has licenses
96+ STRICT_MODE=true
97+ ;;
98+ esac
99+ done
100+
101+ # Build jq selector based on available criteria to identify the component
102+ # This creates a flexible matching system depending on what parameters were provided
103+ if [[ -n " $VERSION " && -n " $TYPE " ]]; then
104+ # Match by name, version AND type (most specific)
105+ SELECTOR=" .name == \" $COMPONENT_NAME \" and .version == \" $VERSION \" and .type == \" $TYPE \" "
106+ IDENTIFIER=" name='$COMPONENT_NAME ', version='$VERSION ', type='$TYPE '"
107+ elif [[ -n " $VERSION " ]]; then
108+ # Match by name and version only
109+ SELECTOR=" .name == \" $COMPONENT_NAME \" and .version == \" $VERSION \" "
110+ IDENTIFIER=" name='$COMPONENT_NAME ', version='$VERSION '"
111+ elif [[ -n " $TYPE " ]]; then
112+ # Match by name and type only
113+ SELECTOR=" .name == \" $COMPONENT_NAME \" and .type == \" $TYPE \" "
114+ IDENTIFIER=" name='$COMPONENT_NAME ', type='$TYPE '"
115+ else
116+ # Match by name only (least specific)
117+ SELECTOR=" .name == \" $COMPONENT_NAME \" "
118+ IDENTIFIER=" name='$COMPONENT_NAME '"
119+ fi
120+
121+ # Check if component exists in SBOM using our constructed selector
122+ # Uses jq to search through components array and returns the name if found
123+ HAS_COMPONENT=$( jq -r " .components[] | select($SELECTOR ) | .name" " $SBOM_FILE " | head -1)
124+
125+ if [[ -n " $HAS_COMPONENT " ]]; then
126+ # Component was found - now check if it already has license information
127+ # Count existing licenses (using // [] to handle missing licenses field)
128+ HAS_LICENSES=$( jq -r " .components[] | select($SELECTOR ) | (.licenses // []) | length" " $SBOM_FILE " | head -1)
129+
130+ if [[ " $HAS_LICENSES " == " 0" ]]; then
131+ # Component exists but has no licenses - proceed with adding license
132+ echo " Adding license '$LICENSE_NAME ' to component with $IDENTIFIER "
133+
134+ # Use jq to add license information to the matching component
135+ # Creates temporary file and atomically moves it to avoid corruption
136+ if [[ " $CUSTOM_LICENSE " == " true" ]]; then
137+ # Use "name" field for custom license names
138+ jq " (.components[] | select($SELECTOR ) | select((.licenses // []) | length == 0) | .licenses) = [{\" license\" : {\" name\" : \" $LICENSE_NAME \" }}]" " $SBOM_FILE " > " ${SBOM_FILE} .tmp" && mv " ${SBOM_FILE} .tmp" " $SBOM_FILE "
139+ else
140+ # Use "id" field for SPDX license identifiers
141+ jq " (.components[] | select($SELECTOR ) | select((.licenses // []) | length == 0) | .licenses) = [{\" license\" : {\" id\" : \" $LICENSE_NAME \" }}]" " $SBOM_FILE " > " ${SBOM_FILE} .tmp" && mv " ${SBOM_FILE} .tmp" " $SBOM_FILE "
142+ fi
143+
144+ echo " License added successfully"
145+ else
146+ # Component already has license information
147+ echo " Component with $IDENTIFIER already has licenses"
148+ if [[ " $STRICT_MODE " == " true" ]]; then
149+ # In strict mode, this is an error condition
150+ echo " ERROR: --strict mode enabled and component already has licenses"
151+ exit 1
152+ else
153+ # In lenient mode, just skip with a message
154+ echo " Skipping license addition"
155+ fi
156+ fi
157+ else
158+ # Component was not found in SBOM
159+ echo " Component with $IDENTIFIER not found in SBOM"
160+ if [[ " $STRICT_MODE " == " true" ]]; then
161+ # In strict mode, missing component is an error
162+ echo " ERROR: --strict mode enabled and component not found"
163+ exit 1
164+ else
165+ # In lenient mode, just skip with a message
166+ echo " Skipping license addition"
167+ fi
168+ fi
0 commit comments