Skip to content

Commit b03c3b8

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

File tree

8 files changed

+178
-9
lines changed

8 files changed

+178
-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: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ use sea_orm::{ActiveValue::Set, ConnectionTrait, DbErr, EntityTrait};
22
use sea_query::OnConflict;
33
use spdx_expression::SpdxExpression;
44
use std::collections::BTreeMap;
5+
// use test_context::futures::StreamExt;
56
use tracing::instrument;
67
use trustify_common::db::chunk::EntityChunkedIter;
7-
use trustify_entity::license;
8+
use trustify_entity::{license, licensing_infos};
89
use uuid::Uuid;
910

1011
const NAMESPACE: Uuid = Uuid::from_bytes([
@@ -22,7 +23,7 @@ impl LicenseInfo {
2223
Uuid::new_v5(&NAMESPACE, self.license.to_lowercase().as_bytes())
2324
}
2425

25-
pub fn spdx_info(&self) -> (Vec<String>, Vec<String>) {
26+
pub fn spdx_info(&self) -> (Vec<String>, Vec<String>, Vec<String>) {
2627
SpdxExpression::parse(&self.license)
2728
.map(|parsed| {
2829
let spdx_licenses = parsed
@@ -38,9 +39,16 @@ impl LicenseInfo {
3839
.map(|e| e.to_string())
3940
.collect::<Vec<_>>();
4041

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

@@ -51,19 +59,50 @@ pub struct LicenseCreator {
5159
/// Uses a [`BTreeMap`] to ensure we have a stable insertion order, avoiding deadlocks on the
5260
/// database.
5361
pub licenses: BTreeMap<Uuid, license::ActiveModel>,
62+
63+
pub custom_license_list: Vec<licensing_infos::ActiveModel>,
5464
}
5565

5666
impl LicenseCreator {
5767
pub fn new() -> Self {
5868
Self {
5969
licenses: Default::default(),
70+
custom_license_list: vec![],
6071
}
6172
}
6273

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

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

68107
self.licenses.entry(uuid).or_insert(license::ActiveModel {
69108
id: Set(uuid),
@@ -78,9 +117,30 @@ impl LicenseCreator {
78117
} else {
79118
Set(Some(spdx_exceptions))
80119
},
120+
custom_license_refs: Set(custom_license_refs_value),
81121
});
82122
}
83123

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