Skip to content

Commit

Permalink
feat(scanner): Add branch name to FossID scan code
Browse files Browse the repository at this point in the history
Add branch name (revision) to FossID scan code for easier
identification of scanned source code.
As FossID accepts maximum of 255 characters in scan code, branch name
is trimmed to size, that is compliant with this constraint.

Signed-off-by: Kamil Bielecki <[email protected]>
  • Loading branch information
Kamil Bielecki committed May 24, 2024
1 parent 833dac3 commit f1628a0
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 11 deletions.
10 changes: 3 additions & 7 deletions plugins/scanners/fossid/src/main/kotlin/FossId.kt
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ class FossId internal constructor(
val scanCodeAndId = if (existingScan == null) {
logger.info { "No scan found for $url and revision $revision. Creating scan..." }

val scanCode = namingProvider.createScanCode(projectName)
val scanCode = namingProvider.createScanCode(projectName = projectName, branch = revision)
val newUrl = urlProvider.getUrl(url)
val scanId = createScan(projectCode, scanCode, newUrl, revision)

Expand Down Expand Up @@ -512,10 +512,6 @@ class FossId internal constructor(
if (defaultBranch != null) "Default branch is '$defaultBranch'." else "There is no default remote branch."
}

// If a scan for the default branch is created, put the default branch name in the scan code (the
// FossIdNamingProvider must also have a scan pattern that makes use of it).
val branchLabel = projectRevision.takeIf { defaultBranch == projectRevision }.orEmpty()

if (projectRevision == null) {
logger.warn { "No project revision has been given." }
} else {
Expand Down Expand Up @@ -547,13 +543,13 @@ class FossId internal constructor(
logger.info {
"No scan found for $mappedUrlWithoutCredentials and revision $revision. Creating origin scan..."
}
namingProvider.createScanCode(projectName, DeltaTag.ORIGIN, branchLabel)
namingProvider.createScanCode(projectName, DeltaTag.ORIGIN, revision)
} else {
logger.info { "Scan '${existingScan.code}' found for $mappedUrlWithoutCredentials and revision $revision." }
logger.info {
"Existing scan has for reference(s): ${existingScan.comment.orEmpty()}. Creating delta scan..."
}
namingProvider.createScanCode(projectName, DeltaTag.DELTA, branchLabel)
namingProvider.createScanCode(projectName, DeltaTag.DELTA, revision)
}

val scanId = createScan(projectCode, scanCode, mappedUrl, revision, projectRevision.orEmpty())
Expand Down
38 changes: 34 additions & 4 deletions plugins/scanners/fossid/src/main/kotlin/FossIdNamingProvider.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
* Copyright (C) 2024 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,6 +36,7 @@ import org.apache.logging.log4j.kotlin.logger
* * **projectName**: The name of the project (i.e. the part of the URL before .git).
* * **currentTimestamp**: The current time.
* * **deltaTag** (scan code only): If delta scans is enabled, this qualifies the scan as an *origin* scan or a *delta*
* * **branch**: branch name (revision) given to scan
* scan.
*/
class FossIdNamingProvider(
Expand All @@ -46,6 +47,8 @@ class FossIdNamingProvider(
companion object {
@JvmStatic
val FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")

const val MAX_SCAN_CODE_LEN = 254
}

fun createProjectCode(projectName: String): String =
Expand All @@ -58,15 +61,42 @@ class FossIdNamingProvider(

fun createScanCode(projectName: String, deltaTag: FossId.DeltaTag? = null, branch: String = ""): String {
var defaultPattern = "#projectName_#currentTimestamp"
val builtins = mutableMapOf("#projectName" to projectName, "#branch" to branch)
val builtins = mutableMapOf("#projectName" to projectName)

deltaTag?.let {
defaultPattern += "_#deltaTag"
builtins += "#deltaTag" to deltaTag.name.lowercase()
}

val pattern = namingScanPattern ?: defaultPattern
return replaceNamingConventionVariables(pattern, builtins, namingConventionVariables)
if (branch.isNotBlank()) {
val branchName = normalizeBranchName(branch, defaultPattern, builtins)
defaultPattern += "_#branch"
builtins += "#branch" to branchName
}

return replaceNamingConventionVariables(
namingScanPattern ?: defaultPattern, builtins, namingConventionVariables
)
}

/**
* Replaces non-standard characters in branch name and trimming it's length to one that will not exceed
* maximum length of FossID scan ID, when combined with rest of variables
*/
private fun normalizeBranchName(
branch: String,
defaultPattern: String,
scanCodeVariables: Map<String, String>
): String {
val noBranchScanCodeLength =
replaceNamingConventionVariables(
namingScanPattern ?: defaultPattern,
scanCodeVariables,
namingConventionVariables
).length

val maxBranchNameLength = MAX_SCAN_CODE_LEN - noBranchScanCodeLength
return branch.replace(Regex("[^a-zA-Z0-9-_]"), "_").take(maxBranchNameLength)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2021 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
*
* 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.scanners.fossid

import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.matchers.ints.shouldBeLessThanOrEqual

import io.mockk.every
import io.mockk.mockkStatic

import java.time.LocalDateTime

class FossIdNamingProviderTest : WordSpec({

Check warning on line 31 in plugins/scanners/fossid/src/test/kotlin/FossIdNamingProviderTest.kt

View workflow job for this annotation

GitHub Actions / qodana-scan

Unused symbol

Class "FossIdNamingProviderTest" is never used
"createScanCode" should {
val namingProvider = FossIdNamingProvider(null, null, emptyMap())

val mockedDateTime = LocalDateTime.of(2024, 4, 1, 10, 0)
val expectedTimestamp = "20240401_100000"

"create code without branch name, when it's empty" {
mockkStatic(LocalDateTime::class) {
every { LocalDateTime.now() } returns mockedDateTime

namingProvider.createScanCode(
"example-project-name", null, ""
) shouldBeEqual "example-project-name_$expectedTimestamp"
}
}

"create code with branch name" {
mockkStatic(LocalDateTime::class) {
every { LocalDateTime.now() } returns mockedDateTime

namingProvider.createScanCode(
"example-project-name", null, "CODE-2233_Red-dots-added-to-layout"
) shouldBeEqual "example-project-name_" + expectedTimestamp + "_CODE-2233_Red-dots-added-to-layout"
}
}

"create code with branch name and delta tag" {
mockkStatic(LocalDateTime::class) {
every { LocalDateTime.now() } returns mockedDateTime

namingProvider.createScanCode(
"example-project-name", FossId.DeltaTag.DELTA, "CODE-2233_Red-dots-added-to-layout"
) shouldBeEqual "example-project-name_" + expectedTimestamp +
"_delta_CODE-2233_Red-dots-added-to-layout"
}
}

"remove all non-standard signs from branch name when creating code" {
mockkStatic(LocalDateTime::class) {
every { LocalDateTime.now() } returns mockedDateTime

namingProvider.createScanCode(
"example-project-name", null, "feature/CODE-12%%$@@&^_SOME_*&^#!*text!!"
) shouldBeEqual "example-project-name_" +
expectedTimestamp + "_feature_CODE-12________SOME_______text__"
}
}

"truncate very long scan id to fit maximum length accepted by FossID (255 chars)" {
val veryLongBranchName =
"origin/feature/CODE-123321_some_more_detailed_description_of_what_that_feature_doing_just_to_make_" +
"it_as_descriptive_as_it's_possible_otherwise_this_test_is_pointless_so_lets_add_some_more_" +
"characters_to_test_it_properly_to_avoid_any_mistake_so_lets_add_some_even_more_to_it_yolo"

mockkStatic(LocalDateTime::class) {
every { LocalDateTime.now() } returns mockedDateTime

namingProvider.createScanCode(
"example-project-name", FossId.DeltaTag.DELTA, veryLongBranchName
).length shouldBeLessThanOrEqual 255
}
}
}
})

0 comments on commit f1628a0

Please sign in to comment.