diff --git a/plugins/package-configuration-providers/dos/build.gradle.kts b/plugins/package-configuration-providers/dos/build.gradle.kts new file mode 100644 index 0000000000000..a704e4cb52522 --- /dev/null +++ b/plugins/package-configuration-providers/dos/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The ORT Project Authors (see ) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +plugins { + // Apply precompiled plugins. + id("ort-library-conventions") +} + +dependencies { + api(projects.plugins.packageConfigurationProviders.packageConfigurationProviderApi) + + implementation(projects.clients.dosClient) + implementation(libs.kotlinx.coroutines) +} diff --git a/plugins/package-configuration-providers/dos/src/main/kotlin/DosPackageConfigurationProvider.kt b/plugins/package-configuration-providers/dos/src/main/kotlin/DosPackageConfigurationProvider.kt new file mode 100644 index 0000000000000..ca107fc4ba43d --- /dev/null +++ b/plugins/package-configuration-providers/dos/src/main/kotlin/DosPackageConfigurationProvider.kt @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2024 The ORT Project Authors (see ) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +package org.ossreviewtoolkit.plugins.packageconfigurationproviders.dos + +import java.time.Duration + +import kotlinx.coroutines.runBlocking + +import org.apache.logging.log4j.kotlin.logger + +import org.ossreviewtoolkit.clients.dos.DosClient +import org.ossreviewtoolkit.clients.dos.DosService +import org.ossreviewtoolkit.clients.dos.PackageConfigurationResponseBody +import org.ossreviewtoolkit.model.ArtifactProvenance +import org.ossreviewtoolkit.model.Identifier +import org.ossreviewtoolkit.model.Provenance +import org.ossreviewtoolkit.model.RepositoryProvenance +import org.ossreviewtoolkit.model.config.LicenseFindingCuration +import org.ossreviewtoolkit.model.config.LicenseFindingCurationReason +import org.ossreviewtoolkit.model.config.PackageConfiguration +import org.ossreviewtoolkit.model.config.PathExclude +import org.ossreviewtoolkit.model.config.PathExcludeReason +import org.ossreviewtoolkit.model.config.VcsMatcher +import org.ossreviewtoolkit.model.utils.PackageConfigurationProvider +import org.ossreviewtoolkit.model.utils.associateLicensesWithExceptions +import org.ossreviewtoolkit.model.utils.toPurl +import org.ossreviewtoolkit.model.utils.toPurlExtras +import org.ossreviewtoolkit.plugins.packageconfigurationproviders.api.PackageConfigurationProviderFactory +import org.ossreviewtoolkit.utils.common.Options +import org.ossreviewtoolkit.utils.spdx.SpdxExpression + +data class DosPackageConfigurationProviderConfig( + /** The URL where the DOS backend is running. */ + val url: String, + + /** The secret token to use with the DOS backend. */ + val token: String, + + /** The timeout for communicating with the DOS backend, in seconds. */ + val timeout: Long? +) + +open class DosPackageConfigurationProviderFactory : + PackageConfigurationProviderFactory { + override val type = "DOS" + + override fun create(config: DosPackageConfigurationProviderConfig) = DosPackageConfigurationProvider(config) + + override fun parseConfig(options: Options, secrets: Options) = + DosPackageConfigurationProviderConfig( + url = options.getValue("url"), + timeout = options["timeout"]?.toLongOrNull(), + token = secrets.getValue("token") + ) +} + +/** + * A [PackageConfigurationProvider] that loads [PackageConfiguration]s from a Double Open Server instance. + */ +class DosPackageConfigurationProvider(config: DosPackageConfigurationProviderConfig) : PackageConfigurationProvider { + private val service = DosService.create( + config.url, + config.token, + config.timeout?.let { Duration.ofSeconds(it) } + ) + + private val client = DosClient(service) + + override fun getPackageConfigurations(packageId: Identifier, provenance: Provenance): List { + val purl = packageId.toPurl(provenance.toPurlExtras()) + + val packageResults = runBlocking { client.getPackageConfiguration(purl) } + if (packageResults == null) { + logger.info { "Unable to find package configuration for '$purl'." } + return emptyList() + } + + return if (packageResults.licenseConclusions.isEmpty() && packageResults.pathExclusions.isEmpty()) { + emptyList() + } else { + val packageConfiguration = generatePackageConfiguration( + id = packageId, + provenance = provenance, + packageResults = packageResults + ) + + logger.info { "Found license conclusions for $purl: ${packageResults.licenseConclusions}" } + logger.info { "Found path exclusions for $purl: ${packageResults.pathExclusions}" } + + listOf(packageConfiguration) + } + } +} + +internal fun generatePackageConfiguration( + id: Identifier, + provenance: Provenance, + packageResults: PackageConfigurationResponseBody +): PackageConfiguration { + val sourceArtifactUrl = if (provenance is ArtifactProvenance) { + provenance.sourceArtifact.url + } else { + null + } + + val vcs = if (provenance is RepositoryProvenance) { + VcsMatcher( + type = provenance.vcsInfo.type, + url = provenance.vcsInfo.url, + revision = provenance.resolvedRevision + ) + } else { + null + } + + val licenseFindingCurations = packageResults.licenseConclusions.map { licenseConclusion -> + val detected = licenseConclusion.detectedLicenseExpressionSPDX?.takeUnless { it.isEmpty() } + ?.let { SpdxExpression.parse(it) } + + check(licenseConclusion.concludedLicenseExpressionSPDX.isNotEmpty()) + val concluded = SpdxExpression.parse(licenseConclusion.concludedLicenseExpressionSPDX) + + LicenseFindingCuration( + path = licenseConclusion.path, + detectedLicense = detected?.let { associateLicensesWithExceptions(it) }, + concludedLicense = concluded, + reason = LicenseFindingCurationReason.INCORRECT, + comment = licenseConclusion.comment.orEmpty() + ) + } + + val pathExcludes = packageResults.pathExclusions.map { + PathExclude( + pattern = it.pattern, + reason = PathExcludeReason.valueOf(it.reason), + comment = it.comment.orEmpty() + ) + } + + return PackageConfiguration( + id = id, + sourceArtifactUrl = sourceArtifactUrl, + vcs = vcs, + pathExcludes = pathExcludes, + licenseFindingCurations = licenseFindingCurations + ) +} diff --git a/plugins/package-configuration-providers/dos/src/main/resources/META-INF/services/org.ossreviewtoolkit.plugins.packageconfigurationproviders.api.PackageConfigurationProviderFactory b/plugins/package-configuration-providers/dos/src/main/resources/META-INF/services/org.ossreviewtoolkit.plugins.packageconfigurationproviders.api.PackageConfigurationProviderFactory new file mode 100644 index 0000000000000..2f73570f9a04a --- /dev/null +++ b/plugins/package-configuration-providers/dos/src/main/resources/META-INF/services/org.ossreviewtoolkit.plugins.packageconfigurationproviders.api.PackageConfigurationProviderFactory @@ -0,0 +1,2 @@ +org.ossreviewtoolkit.plugins.packageconfigurationproviders.dos.DosPackageConfigurationProviderFactory +