Skip to content

Commit a706ec7

Browse files
author
Kamil Bielecki
committed
feat(API): Extend ort run package API with curation info
This commit extends packages list for ORT Run API endpoint with information on curations applied. resolves #2324 Signed-off-by: Kamil Bielecki <[email protected]>
1 parent 8df9e09 commit a706ec7

File tree

6 files changed

+211
-88
lines changed

6 files changed

+211
-88
lines changed

api/v1/mapping/src/commonMain/kotlin/Mappings.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.eclipse.apoapsis.ortserver.api.v1.model.AnalyzerJob as ApiAnalyzerJob
2727
import org.eclipse.apoapsis.ortserver.api.v1.model.AnalyzerJobConfiguration as ApiAnalyzerJobConfiguration
2828
import org.eclipse.apoapsis.ortserver.api.v1.model.ComparisonOperator as ApiComparisonOperator
2929
import org.eclipse.apoapsis.ortserver.api.v1.model.CredentialsType as ApiCredentialsType
30+
import org.eclipse.apoapsis.ortserver.api.v1.model.Curation as ApiCuration
3031
import org.eclipse.apoapsis.ortserver.api.v1.model.EcosystemStats as ApiEcosystemStats
3132
import org.eclipse.apoapsis.ortserver.api.v1.model.EnvironmentConfig as ApiEnvironmentConfig
3233
import org.eclipse.apoapsis.ortserver.api.v1.model.EnvironmentVariableDeclaration as ApiEnvironmentVariableDeclaration
@@ -153,6 +154,7 @@ import org.eclipse.apoapsis.ortserver.model.runs.ShortestDependencyPath
153154
import org.eclipse.apoapsis.ortserver.model.runs.VcsInfo
154155
import org.eclipse.apoapsis.ortserver.model.runs.advisor.Vulnerability
155156
import org.eclipse.apoapsis.ortserver.model.runs.advisor.VulnerabilityReference
157+
import org.eclipse.apoapsis.ortserver.model.runs.repository.PackageCurationData
156158
import org.eclipse.apoapsis.ortserver.model.util.ComparisonOperator
157159
import org.eclipse.apoapsis.ortserver.model.util.FilterOperatorAndValue
158160
import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters
@@ -872,7 +874,17 @@ fun PackageRunData.mapToApi() = ApiPackage(
872874
pkg.vcsProcessed.mapToApi(),
873875
pkg.isMetadataOnly,
874876
pkg.isModified,
875-
shortestDependencyPaths.map { it.mapToApi() }
877+
shortestDependencyPaths.map { it.mapToApi() },
878+
curations.map { it.mapToApi() }
879+
)
880+
881+
fun PackageCurationData.mapToApi(): ApiCuration = ApiCuration(
882+
concludedLicense ?: "",
883+
comment ?: "",
884+
description ?: "",
885+
homepageUrl ?: "",
886+
authors?.toList() ?: emptyList(),
887+
declaredLicenseMapping
876888
)
877889

878890
fun ApiPackageFilters.mapToModel(): PackageFilters =
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (C) 2025 The ORT Server Authors (See <https://github.com/eclipse-apoapsis/ort-server/blob/main/NOTICE>)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.eclipse.apoapsis.ortserver.api.v1.model
21+
22+
import kotlinx.serialization.Serializable
23+
24+
@Serializable
25+
data class Curation(
26+
val concludedLicense: String,
27+
val comment: String,
28+
val description: String,
29+
val homepageUrl: String,
30+
val authors: List<String>,
31+
val declaredLicenseMapping: Map<String, String>
32+
)

api/v1/model/src/commonMain/kotlin/Package.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ data class Package(
3737
val vcsProcessed: VcsInfo,
3838
val isMetadataOnly: Boolean = false,
3939
val isModified: Boolean = false,
40-
val shortestDependencyPaths: List<ShortestDependencyPath>
40+
val shortestDependencyPaths: List<ShortestDependencyPath>,
41+
val curations: List<Curation>
4142
)
4243

4344
/**

core/src/main/kotlin/apiDocs/RunsDocs.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import kotlin.time.Duration.Companion.minutes
2828
import kotlinx.datetime.Clock
2929

3030
import org.eclipse.apoapsis.ortserver.api.v1.model.ComparisonOperator
31+
import org.eclipse.apoapsis.ortserver.api.v1.model.Curation
3132
import org.eclipse.apoapsis.ortserver.api.v1.model.EcosystemStats
3233
import org.eclipse.apoapsis.ortserver.api.v1.model.FilterOperatorAndValue
3334
import org.eclipse.apoapsis.ortserver.api.v1.model.Identifier
@@ -428,6 +429,16 @@ val getPackagesByRunId: RouteConfig.() -> Unit = {
428429
Identifier("Maven", "org.example", "other", "1.0")
429430
)
430431
)
432+
),
433+
curations = listOf(
434+
Curation(
435+
concludedLicense = "MIT",
436+
comment = "A comment",
437+
description = "",
438+
homepageUrl = "https://example.com/namespace/name",
439+
authors = listOf("John Doe <[email protected]>"),
440+
declaredLicenseMapping = emptyMap()
441+
)
431442
)
432443
)
433444
),

core/src/test/kotlin/api/RunsRouteIntegrationTest.kt

Lines changed: 152 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import io.kotest.assertions.ktor.client.shouldHaveStatus
2727
import io.kotest.engine.spec.tempdir
2828
import io.kotest.inspectors.forAll
2929
import io.kotest.matchers.collections.shouldBeSingleton
30+
import io.kotest.matchers.collections.shouldContain
3031
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
3132
import io.kotest.matchers.collections.shouldHaveSize
3233
import io.kotest.matchers.file.aFile
@@ -126,6 +127,13 @@ import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorResult
126127
import org.eclipse.apoapsis.ortserver.model.runs.advisor.Vulnerability
127128
import org.eclipse.apoapsis.ortserver.model.runs.advisor.VulnerabilityReference
128129
import org.eclipse.apoapsis.ortserver.model.runs.reporter.Report
130+
import org.eclipse.apoapsis.ortserver.model.runs.repository.Curations
131+
import org.eclipse.apoapsis.ortserver.model.runs.repository.Excludes
132+
import org.eclipse.apoapsis.ortserver.model.runs.repository.LicenseChoices
133+
import org.eclipse.apoapsis.ortserver.model.runs.repository.PackageCuration
134+
import org.eclipse.apoapsis.ortserver.model.runs.repository.PackageCurationData
135+
import org.eclipse.apoapsis.ortserver.model.runs.repository.RepositoryAnalyzerConfiguration
136+
import org.eclipse.apoapsis.ortserver.model.runs.repository.Resolutions
129137
import org.eclipse.apoapsis.ortserver.model.util.asPresent
130138
import org.eclipse.apoapsis.ortserver.services.DefaultAuthorizationService
131139
import org.eclipse.apoapsis.ortserver.services.OrganizationService
@@ -993,6 +1001,119 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({
9931001
val identifier1 = Identifier("Maven", "com.example", "example", "1.0")
9941002
val identifier2 = Identifier("Maven", "com.example", "example2", "1.0")
9951003

1004+
dbExtension.fixtures.repositoryConfigurationRepository.create(
1005+
ortRunId = ortRun.id,
1006+
curations = Curations(
1007+
packages = listOf(
1008+
PackageCuration(
1009+
id = identifier1,
1010+
data = PackageCurationData(
1011+
comment = "comment1_a",
1012+
description = "description1_a",
1013+
concludedLicense = "license1_a",
1014+
authors = setOf("auth1a_a", "auth1b_a")
1015+
)
1016+
),
1017+
PackageCuration(
1018+
id = identifier1,
1019+
data = PackageCurationData(
1020+
comment = "comment1_b",
1021+
description = "description1_b",
1022+
concludedLicense = "license1_b",
1023+
authors = setOf("auth1a_b", "auth1b_b")
1024+
)
1025+
)
1026+
)
1027+
),
1028+
analyzerConfig = RepositoryAnalyzerConfiguration(),
1029+
excludes = Excludes(),
1030+
resolutions = Resolutions(),
1031+
packageConfigurations = listOf(),
1032+
licenseChoices = LicenseChoices(),
1033+
provenanceSnippetChoices = listOf()
1034+
)
1035+
1036+
val package1 = Package(
1037+
identifier1,
1038+
purl = "pkg:maven/com.example/[email protected]",
1039+
cpe = null,
1040+
authors = setOf("Author One", "Author Two"),
1041+
declaredLicenses = setOf("License1", "License2", "License3"),
1042+
ProcessedDeclaredLicense(
1043+
spdxExpression = "Expression",
1044+
mappedLicenses = mapOf(
1045+
"License 1" to "Mapped License 1",
1046+
"License 2" to "Mapped License 2",
1047+
),
1048+
unmappedLicenses = setOf("License 1", "License 2", "License 3", "License 4")
1049+
),
1050+
description = "An example package",
1051+
homepageUrl = "https://example.com",
1052+
binaryArtifact = RemoteArtifact(
1053+
"https://example.com/example-1.0.jar",
1054+
"sha1:value",
1055+
"SHA-1"
1056+
),
1057+
sourceArtifact = RemoteArtifact(
1058+
"https://example.com/example-1.0-sources.jar",
1059+
"sha1:value",
1060+
"SHA-1"
1061+
),
1062+
vcs = VcsInfo(
1063+
RepositoryType("GIT"),
1064+
"https://example.com/git",
1065+
"revision",
1066+
"path"
1067+
),
1068+
vcsProcessed = VcsInfo(
1069+
RepositoryType("GIT"),
1070+
"https://example.com/git",
1071+
"revision",
1072+
"path"
1073+
),
1074+
isMetadataOnly = false,
1075+
isModified = false
1076+
)
1077+
1078+
val package2 = Package(
1079+
identifier2,
1080+
purl = "pkg:maven/com.example/[email protected]",
1081+
cpe = null,
1082+
authors = emptySet(),
1083+
declaredLicenses = emptySet(),
1084+
ProcessedDeclaredLicense(
1085+
spdxExpression = "Expression",
1086+
mappedLicenses = emptyMap(),
1087+
unmappedLicenses = emptySet()
1088+
),
1089+
description = "Another example package",
1090+
homepageUrl = "https://example.com",
1091+
binaryArtifact = RemoteArtifact(
1092+
"https://example.com/example2-1.0.jar",
1093+
"sha1:value",
1094+
"SHA-1"
1095+
),
1096+
sourceArtifact = RemoteArtifact(
1097+
"https://example.com/example2-1.0-sources.jar",
1098+
"sha1:value",
1099+
"SHA-1"
1100+
),
1101+
vcs = VcsInfo(
1102+
RepositoryType("GIT"),
1103+
"https://example.com/git",
1104+
"revision",
1105+
"path"
1106+
),
1107+
vcsProcessed = VcsInfo(
1108+
RepositoryType("GIT"),
1109+
"https://example.com/git",
1110+
"revision",
1111+
"path"
1112+
),
1113+
isMetadataOnly = false,
1114+
isModified = false
1115+
)
1116+
9961117
dbExtension.fixtures.analyzerRunRepository.create(
9971118
analyzerJobId = analyzerJob.id,
9981119
startTime = Clock.System.now().toDatabasePrecision(),
@@ -1014,87 +1135,7 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({
10141135
skipExcluded = true
10151136
),
10161137
projects = setOf(project),
1017-
packages = setOf(
1018-
Package(
1019-
identifier1,
1020-
purl = "pkg:maven/com.example/[email protected]",
1021-
cpe = null,
1022-
authors = setOf("Author One", "Author Two"),
1023-
declaredLicenses = setOf("License1", "License2", "License3"),
1024-
ProcessedDeclaredLicense(
1025-
spdxExpression = "Expression",
1026-
mappedLicenses = mapOf(
1027-
"License 1" to "Mapped License 1",
1028-
"License 2" to "Mapped License 2",
1029-
),
1030-
unmappedLicenses = setOf("License 1", "License 2", "License 3", "License 4")
1031-
),
1032-
description = "An example package",
1033-
homepageUrl = "https://example.com",
1034-
binaryArtifact = RemoteArtifact(
1035-
"https://example.com/example-1.0.jar",
1036-
"sha1:value",
1037-
"SHA-1"
1038-
),
1039-
sourceArtifact = RemoteArtifact(
1040-
"https://example.com/example-1.0-sources.jar",
1041-
"sha1:value",
1042-
"SHA-1"
1043-
),
1044-
vcs = VcsInfo(
1045-
RepositoryType("GIT"),
1046-
"https://example.com/git",
1047-
"revision",
1048-
"path"
1049-
),
1050-
vcsProcessed = VcsInfo(
1051-
RepositoryType("GIT"),
1052-
"https://example.com/git",
1053-
"revision",
1054-
"path"
1055-
),
1056-
isMetadataOnly = false,
1057-
isModified = false
1058-
),
1059-
Package(
1060-
identifier2,
1061-
purl = "pkg:maven/com.example/[email protected]",
1062-
cpe = null,
1063-
authors = emptySet(),
1064-
declaredLicenses = emptySet(),
1065-
ProcessedDeclaredLicense(
1066-
spdxExpression = "Expression",
1067-
mappedLicenses = emptyMap(),
1068-
unmappedLicenses = emptySet()
1069-
),
1070-
description = "Another example package",
1071-
homepageUrl = "https://example.com",
1072-
binaryArtifact = RemoteArtifact(
1073-
"https://example.com/example2-1.0.jar",
1074-
"sha1:value",
1075-
"SHA-1"
1076-
),
1077-
sourceArtifact = RemoteArtifact(
1078-
"https://example.com/example2-1.0-sources.jar",
1079-
"sha1:value",
1080-
"SHA-1"
1081-
),
1082-
vcs = VcsInfo(
1083-
RepositoryType("GIT"),
1084-
"https://example.com/git",
1085-
"revision",
1086-
"path"
1087-
),
1088-
vcsProcessed = VcsInfo(
1089-
RepositoryType("GIT"),
1090-
"https://example.com/git",
1091-
"revision",
1092-
"path"
1093-
),
1094-
isMetadataOnly = false,
1095-
isModified = false
1096-
)
1097-
),
1138+
packages = setOf(package1, package2),
10981139
issues = emptyList(),
10991140
dependencyGraphs = emptyMap(),
11001141
shortestDependencyPaths = mapOf(
@@ -1135,14 +1176,39 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({
11351176
it.scope shouldBe "compileClassPath"
11361177
it.path shouldBe emptyList()
11371178
}
1179+
1180+
curations shouldHaveSize 2
1181+
1182+
val curr1 = curations.find { it.comment == "comment1_a" }
1183+
curr1.shouldNotBeNull()
1184+
curr1.comment shouldBe "comment1_a"
1185+
curr1.description shouldBe "description1_a"
1186+
curr1.concludedLicense shouldBe "license1_a"
1187+
curr1.authors shouldHaveSize 2
1188+
curr1.authors shouldContain "auth1a_a"
1189+
curr1.authors shouldContain "auth1b_a"
1190+
1191+
val curr2 = curations.find { it.comment == "comment1_b" }
1192+
curr2.shouldNotBeNull()
1193+
curr2.comment shouldBe "comment1_b"
1194+
curr2.description shouldBe "description1_b"
1195+
curr2.concludedLicense shouldBe "license1_b"
1196+
curr2.authors shouldHaveSize 2
1197+
curr2.authors shouldContain "auth1a_b"
1198+
curr2.authors shouldContain "auth1b_b"
11381199
}
11391200

1140-
last().shortestDependencyPaths.shouldBeSingleton {
1141-
it.projectIdentifier shouldBe project.identifier.mapToApi()
1142-
it.scope shouldBe "compileClassPath"
1143-
it.path shouldBe listOf(identifier1.mapToApi())
1201+
with(last()) {
1202+
identifier.name shouldBe "example2"
1203+
1204+
shortestDependencyPaths.shouldBeSingleton {
1205+
it.projectIdentifier shouldBe project.identifier.mapToApi()
1206+
it.scope shouldBe "compileClassPath"
1207+
it.path shouldBe listOf(identifier1.mapToApi())
1208+
}
1209+
1210+
curations shouldHaveSize(0)
11441211
}
1145-
last().identifier.name shouldBe "example2"
11461212
}
11471213
}
11481214
}

dao/src/testFixtures/kotlin/Fixtures.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import org.jetbrains.exposed.sql.Database
7777
* A helper class to manage test fixtures. It provides default instances as well as helper functions to create custom
7878
* instances.
7979
*/
80+
@Suppress("TooManyFunctions")
8081
class Fixtures(private val db: Database) {
8182
val advisorJobRepository = DaoAdvisorJobRepository(db)
8283
val advisorRunRepository = DaoAdvisorRunRepository(db)

0 commit comments

Comments
 (0)