Skip to content

Commit b73244f

Browse files
committed
chore: add new field custom_license_refs into license entity
1 parent c8f7067 commit b73244f

File tree

8 files changed

+177
-9
lines changed

8 files changed

+177
-9
lines changed

entity/src/license.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use sea_orm::entity::prelude::*;
22

3-
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
3+
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, serde::Serialize)]
44
#[sea_orm(table_name = "license")]
55
pub struct Model {
66
#[sea_orm(primary_key)]
77
pub id: Uuid,
88
pub text: String,
99
pub spdx_licenses: Option<Vec<String>>,
1010
pub spdx_license_exceptions: Option<Vec<String>>,
11+
pub custom_license_refs: Option<Vec<String>>,
1112
}
1213

1314
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

migration/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod m0001100_remove_get_purl;
2525
mod m0001110_sbom_node_checksum_indexes;
2626
mod m0001120_sbom_external_node_indexes;
2727
mod m0001130_gover_cmp;
28+
mod m0001150_license_add_custom_license_refs;
2829

2930
pub struct Migrator;
3031

@@ -57,6 +58,7 @@ impl MigratorTrait for Migrator {
5758
Box::new(m0001110_sbom_node_checksum_indexes::Migration),
5859
Box::new(m0001120_sbom_external_node_indexes::Migration),
5960
Box::new(m0001130_gover_cmp::Migration),
61+
Box::new(m0001150_license_add_custom_license_refs::Migration),
6062
]
6163
}
6264
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use sea_orm_migration::prelude::*;
2+
3+
#[derive(DeriveMigrationName)]
4+
pub struct Migration;
5+
6+
#[async_trait::async_trait]
7+
impl MigrationTrait for Migration {
8+
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
9+
manager
10+
.alter_table(
11+
Table::alter()
12+
.table(License::Table)
13+
.add_column(ColumnDef::new(License::CustomLicenseRefs).array(ColumnType::Text))
14+
.to_owned(),
15+
)
16+
.await?;
17+
18+
Ok(())
19+
}
20+
21+
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
22+
manager
23+
.alter_table(
24+
Table::alter()
25+
.table(License::Table)
26+
.drop_column(License::CustomLicenseRefs)
27+
.to_owned(),
28+
)
29+
.await?;
30+
Ok(())
31+
}
32+
}
33+
34+
#[derive(DeriveIden)]
35+
enum License {
36+
Table,
37+
CustomLicenseRefs,
38+
}

modules/fundamental/tests/sbom/license.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
use flate2::read::GzDecoder;
2-
use sea_orm::EntityTrait;
2+
use sea_orm::{ColumnTrait, QuerySelect};
3+
use sea_orm::{EntityTrait, QueryFilter};
4+
use sea_query::Cond;
5+
use serde_json::{Value, json};
36
use std::io::Read;
47
use tar::Archive;
58
use test_context::test_context;
69
use test_log::test;
710
use trustify_entity::sbom_package_license::LicenseCategory;
8-
use trustify_entity::{sbom_package, sbom_package_license};
11+
use trustify_entity::{license, sbom_package, sbom_package_license};
912
use trustify_module_fundamental::license::{
1013
model::sbom_license::SbomNameId,
1114
service::{LicenseService, license_export::LicenseExporter},
1215
};
1316
use trustify_test_context::TrustifyContext;
17+
use trustify_test_context::subset::ContainsSubset;
1418

1519
#[test_context(TrustifyContext)]
1620
#[test(tokio::test)]
@@ -39,6 +43,59 @@ async fn test_cyclonedx(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
3943
Ok(())
4044
}
4145

46+
#[test_context(TrustifyContext)]
47+
#[test(tokio::test)]
48+
async fn test_custom_license_refs_spdx(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
49+
let result = ctx
50+
.ingest_document("spdx/SATELLITE-6.15-RHEL-8.json")
51+
.await?;
52+
53+
assert_eq!(
54+
Some("https://access.redhat.com/security/data/sbom/spdx/SATELLITE-6.15-RHEL-8"),
55+
result.clone().document_id.as_deref()
56+
);
57+
58+
let license_result = license::Entity::find()
59+
.filter(
60+
Cond::any()
61+
.add(license::Column::Text.eq("LicenseRef-2 OR Ruby"))
62+
.add(license::Column::Text.eq("LicenseRef-GPLv3 AND LicenseRef-21")),
63+
)
64+
.select_only()
65+
.column(license::Column::Id)
66+
.column(license::Column::Text)
67+
.column(license::Column::SpdxLicenses)
68+
.column(license::Column::SpdxLicenseExceptions)
69+
.column(license::Column::CustomLicenseRefs)
70+
.all(&ctx.db)
71+
.await?;
72+
73+
assert!(
74+
!license_result.is_empty(),
75+
"No license found with the specified text."
76+
);
77+
let expected_result = json!([
78+
{
79+
"id": "107c5a51-d315-56fb-9d4c-dd337f242d2e",
80+
"text": "LicenseRef-2 OR Ruby",
81+
"spdx_licenses": ["Ruby"],
82+
"spdx_license_exceptions": null,
83+
"custom_license_refs": ["LicenseRef-2:GPLv2+"]
84+
},
85+
{
86+
"id": "5bec012e-9891-5715-a550-09287ced2d54",
87+
"text": "LicenseRef-GPLv3 AND LicenseRef-21",
88+
"spdx_licenses": null,
89+
"spdx_license_exceptions": null,
90+
"custom_license_refs": ["LicenseRef-GPLv3:GPLv3", "LicenseRef-21:Public domain"]
91+
}
92+
]);
93+
let license_result_value: Value =
94+
serde_json::to_value(&license_result).expect("Failed to serialize license_result to JSON");
95+
assert!(expected_result.contains_subset(license_result_value));
96+
Ok(())
97+
}
98+
4299
#[test_context(TrustifyContext)]
43100
#[test(tokio::test)]
44101
async fn test_spdx(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {

modules/ingestor/src/graph/sbom/common/license.rs

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use spdx_expression::SpdxExpression;
44
use std::collections::BTreeMap;
55
use tracing::instrument;
66
use trustify_common::db::chunk::EntityChunkedIter;
7-
use trustify_entity::license;
7+
use trustify_entity::{license, licensing_infos};
88
use uuid::Uuid;
99

1010
const NAMESPACE: Uuid = Uuid::from_bytes([
@@ -22,7 +22,7 @@ impl LicenseInfo {
2222
Uuid::new_v5(&NAMESPACE, self.license.to_lowercase().as_bytes())
2323
}
2424

25-
pub fn spdx_info(&self) -> (Vec<String>, Vec<String>) {
25+
pub fn spdx_info(&self) -> (Vec<String>, Vec<String>, Vec<String>) {
2626
SpdxExpression::parse(&self.license)
2727
.map(|parsed| {
2828
let spdx_licenses = parsed
@@ -38,9 +38,16 @@ impl LicenseInfo {
3838
.map(|e| e.to_string())
3939
.collect::<Vec<_>>();
4040

41-
(spdx_licenses, spdx_license_exceptions)
41+
let custom_license_refs = parsed
42+
.licenses()
43+
.iter()
44+
.filter(|e| e.license_ref)
45+
.map(|e| format!("LicenseRef-{}", e.identifier))
46+
.collect::<Vec<_>>();
47+
48+
(spdx_licenses, spdx_license_exceptions, custom_license_refs)
4249
})
43-
.unwrap_or((vec![], vec![]))
50+
.unwrap_or((vec![], vec![], vec![]))
4451
}
4552
}
4653

@@ -51,19 +58,50 @@ pub struct LicenseCreator {
5158
/// Uses a [`BTreeMap`] to ensure we have a stable insertion order, avoiding deadlocks on the
5259
/// database.
5360
pub licenses: BTreeMap<Uuid, license::ActiveModel>,
61+
62+
pub custom_license_list: Vec<licensing_infos::ActiveModel>,
5463
}
5564

5665
impl LicenseCreator {
5766
pub fn new() -> Self {
5867
Self {
5968
licenses: Default::default(),
69+
custom_license_list: vec![],
6070
}
6171
}
6272

73+
pub fn put_custom_license_list(
74+
&mut self,
75+
custom_license_list: Vec<licensing_infos::ActiveModel>,
76+
) {
77+
self.custom_license_list = custom_license_list;
78+
}
79+
6380
pub fn add(&mut self, info: &LicenseInfo) {
6481
let uuid = info.uuid();
6582

66-
let (spdx_licenses, spdx_exceptions) = info.spdx_info();
83+
let (spdx_licenses, spdx_exceptions, custom_license_refs) = info.spdx_info();
84+
let missing_custom_refs: Vec<_> = custom_license_refs
85+
.iter()
86+
.filter(|ref_id| {
87+
!self
88+
.custom_license_list
89+
.iter()
90+
.any(|c| c.license_id == Set((*ref_id).to_string()))
91+
})
92+
.cloned()
93+
.collect();
94+
if !missing_custom_refs.is_empty() {
95+
log::warn!(
96+
"The following custom license refs are missing from custom_license_list: {:?}",
97+
missing_custom_refs
98+
);
99+
}
100+
let custom_license_refs_value = if custom_license_refs.is_empty() {
101+
None
102+
} else {
103+
Some(self.construct_custom_license(custom_license_refs.clone()))
104+
};
67105

68106
self.licenses.entry(uuid).or_insert(license::ActiveModel {
69107
id: Set(uuid),
@@ -78,9 +116,30 @@ impl LicenseCreator {
78116
} else {
79117
Set(Some(spdx_exceptions))
80118
},
119+
custom_license_refs: Set(custom_license_refs_value),
81120
});
82121
}
83122

123+
fn construct_custom_license(&self, custom_license_ids: Vec<String>) -> Vec<String> {
124+
use std::collections::HashMap;
125+
// Build a HashMap from license_id to name for fast lookup
126+
let license_map: HashMap<&String, &String> = self
127+
.custom_license_list
128+
.iter()
129+
.filter_map(|c| {
130+
if let (Set(license_id), Set(name)) = (&c.license_id, &c.name) {
131+
Some((license_id, name))
132+
} else {
133+
None
134+
}
135+
})
136+
.collect();
137+
custom_license_ids
138+
.into_iter()
139+
.filter_map(|id| license_map.get(&id).map(|name| format!("{}:{}", id, name)))
140+
.collect()
141+
}
142+
84143
#[instrument(skip_all, fields(num = self.licenses.len()), err(level=tracing::Level::INFO))]
85144
pub async fn create<C>(self, db: &C) -> Result<(), DbErr>
86145
where

modules/ingestor/src/graph/sbom/common/licensing_info.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ impl LicensingInfoCreator {
6363
});
6464
}
6565

66+
pub fn get_copy_license_refs(&self) -> Vec<licensing_infos::ActiveModel> {
67+
self.license_refs.clone()
68+
}
69+
6670
#[instrument(skip_all, fields(num = self.license_refs.len()), err)]
6771
pub async fn create<C>(self, db: &C) -> Result<(), DbErr>
6872
where

modules/ingestor/src/graph/sbom/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ impl SbomContext {
424424
license: license.to_string(),
425425
};
426426

427-
let (spdx_licenses, spdx_exceptions) = license_info.spdx_info();
427+
let (spdx_licenses, spdx_exceptions, custom_license_refs) = license_info.spdx_info();
428428

429429
let license = license::Entity::find_by_id(license_info.uuid())
430430
.one(connection)
@@ -446,6 +446,11 @@ impl SbomContext {
446446
} else {
447447
Set(Some(spdx_exceptions))
448448
},
449+
custom_license_refs: if custom_license_refs.is_empty() {
450+
Set(None)
451+
} else {
452+
Set(Some(custom_license_refs))
453+
},
449454
}
450455
.insert(connection)
451456
.await?

modules/ingestor/src/graph/sbom/spdx.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ impl SbomContext {
198198
license_extracted_refs.add(extracted_licensing_info);
199199
}
200200

201+
licenses.put_custom_license_list(license_extracted_refs.get_copy_license_refs());
202+
201203
let mut packages =
202204
PackageCreator::with_capacity(self.sbom.sbom_id, sbom_data.package_information.len());
203205

0 commit comments

Comments
 (0)