From 15af15428aa797f2ce462516e5f53564c102c0c1 Mon Sep 17 00:00:00 2001 From: Damien Coraboeuf Date: Wed, 11 Dec 2024 12:44:35 +0100 Subject: [PATCH] #1236 Refactoring of the file download service --- .../ontrack/extension/api/FileRefExtension.kt | 25 ++++++++++++ .../PredefinedPromotionLevelsAdminContext.kt | 6 +-- .../PredefinedValidationStampsAdminContext.kt | 6 +-- .../casc/EnvironmentsCascContext.kt | 39 ++++++++++++++----- .../casc/EnvironmentsCascModelTest.kt | 1 + .../support/DefaultIngestionImageService.kt | 7 ++-- .../scm/{service => files}/SCMRef.kt | 9 ++--- .../extension/scm/service/SCMRefService.kt | 15 ------- .../scm/service/SCMRefServiceExtensions.kt | 9 ----- .../scm/service/SCMRefServiceImpl.kt | 32 --------------- .../scm/service/SCMRefURIParsingException.kt | 7 ---- .../service/SCMRefUnknownSCMTypeException.kt | 7 ---- .../scm/files/SCMFileRefExtension.kt | 39 +++++++++++++++++++ .../scm/files/SCMRefParsingException.kt | 7 ++++ .../scm/{service => files}/SCMRefTest.kt | 17 +++----- .../files/SCMRefUnknownSCMTypeException.kt | 7 ++++ .../scm/BitbucketServerSCMExtensionRealIT.kt | 8 +++- .../support}/CoreExtensionFeature.kt | 4 +- .../nemerosa/ontrack/model/files/FileRef.kt | 19 +++++++++ .../ontrack/model/files/FileRefService.kt | 16 ++++++++ .../model/files/FileRefServiceExtensions.kt | 9 +++++ .../model/files/FileRefURIParsingException.kt | 7 ++++ .../ontrack/model/support/ImageHelper.kt | 2 +- .../ontrack/model/files/FileRefTest.kt | 36 +++++++++++++++++ ontrack-service/build.gradle.kts | 1 + .../service/files/Base64FileRefExtension.kt | 25 ++++++++++++ .../files/ClasspathFileRefExtension.kt | 32 +++++++++++++++ .../service/files/FileRefServiceImpl.kt | 27 +++++++++++++ .../FileRefUnsupportedProtocolException.kt | 7 ++++ .../ontrack/boot/BranchSearchProvider.kt | 1 + .../ontrack/boot/BuildSearchProvider.kt | 1 + .../ontrack/boot/ProjectSearchProvider.kt | 1 + .../boot/ui/CoreUserMenuGroupExtension.kt | 2 +- .../boot/ui/CoreUserMenuItemExtension.kt | 2 +- 34 files changed, 318 insertions(+), 115 deletions(-) create mode 100644 ontrack-extension-api/src/main/java/net/nemerosa/ontrack/extension/api/FileRefExtension.kt rename ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/{service => files}/SCMRef.kt (69%) delete mode 100644 ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefService.kt delete mode 100644 ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefServiceExtensions.kt delete mode 100644 ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefServiceImpl.kt delete mode 100644 ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefURIParsingException.kt delete mode 100644 ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefUnknownSCMTypeException.kt create mode 100644 ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMFileRefExtension.kt create mode 100644 ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefParsingException.kt rename ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/{service => files}/SCMRefTest.kt (53%) create mode 100644 ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefUnknownSCMTypeException.kt rename {ontrack-ui/src/main/java/net/nemerosa/ontrack/boot => ontrack-extension-support/src/main/java/net/nemerosa/ontrack/extension/support}/CoreExtensionFeature.kt (64%) create mode 100644 ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRef.kt create mode 100644 ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefService.kt create mode 100644 ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefServiceExtensions.kt create mode 100644 ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefURIParsingException.kt create mode 100644 ontrack-model/src/test/java/net/nemerosa/ontrack/model/files/FileRefTest.kt create mode 100644 ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/Base64FileRefExtension.kt create mode 100644 ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/ClasspathFileRefExtension.kt create mode 100644 ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/FileRefServiceImpl.kt create mode 100644 ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/FileRefUnsupportedProtocolException.kt diff --git a/ontrack-extension-api/src/main/java/net/nemerosa/ontrack/extension/api/FileRefExtension.kt b/ontrack-extension-api/src/main/java/net/nemerosa/ontrack/extension/api/FileRefExtension.kt new file mode 100644 index 0000000000..3a038a52f5 --- /dev/null +++ b/ontrack-extension-api/src/main/java/net/nemerosa/ontrack/extension/api/FileRefExtension.kt @@ -0,0 +1,25 @@ +package net.nemerosa.ontrack.extension.api + +import net.nemerosa.ontrack.common.Document +import net.nemerosa.ontrack.model.extension.Extension + +/** + * Extension used to support a protocol to download a file. + */ +interface FileRefExtension : Extension { + + /** + * Supported protocol + */ + val protocol: String + + /** + * Downloading a document using its path + * + * @param path Path extracted from the file ref URI + * @param type Expected MIME type of the document + * @return Document or null if not found + */ + fun download(path: String, type: String): Document? + +} \ No newline at end of file diff --git a/ontrack-extension-casc/src/main/java/net/nemerosa/ontrack/extension/casc/context/core/admin/PredefinedPromotionLevelsAdminContext.kt b/ontrack-extension-casc/src/main/java/net/nemerosa/ontrack/extension/casc/context/core/admin/PredefinedPromotionLevelsAdminContext.kt index 7a2935feb6..aa0fd01829 100644 --- a/ontrack-extension-casc/src/main/java/net/nemerosa/ontrack/extension/casc/context/core/admin/PredefinedPromotionLevelsAdminContext.kt +++ b/ontrack-extension-casc/src/main/java/net/nemerosa/ontrack/extension/casc/context/core/admin/PredefinedPromotionLevelsAdminContext.kt @@ -7,10 +7,10 @@ import net.nemerosa.ontrack.extension.casc.schema.CascType import net.nemerosa.ontrack.extension.casc.schema.cascArray import net.nemerosa.ontrack.extension.casc.schema.cascField import net.nemerosa.ontrack.extension.casc.schema.cascObject -import net.nemerosa.ontrack.extension.scm.service.SCMRefService -import net.nemerosa.ontrack.extension.scm.service.downloadDocument import net.nemerosa.ontrack.json.asJson import net.nemerosa.ontrack.json.parse +import net.nemerosa.ontrack.model.files.FileRefService +import net.nemerosa.ontrack.model.files.downloadDocument import net.nemerosa.ontrack.model.settings.PredefinedPromotionLevelService import net.nemerosa.ontrack.model.structure.ID import net.nemerosa.ontrack.model.structure.NameDescription @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component @Component class PredefinedPromotionLevelsAdminContext( private val predefinedPromotionLevelService: PredefinedPromotionLevelService, - private val scmRefService: SCMRefService, + private val scmRefService: FileRefService, ) : AbstractCascContext(), SubAdminContext { private val logger: Logger = LoggerFactory.getLogger(PredefinedPromotionLevelsAdminContext::class.java) diff --git a/ontrack-extension-casc/src/main/java/net/nemerosa/ontrack/extension/casc/context/core/admin/PredefinedValidationStampsAdminContext.kt b/ontrack-extension-casc/src/main/java/net/nemerosa/ontrack/extension/casc/context/core/admin/PredefinedValidationStampsAdminContext.kt index 7e5647b435..2fd5297426 100644 --- a/ontrack-extension-casc/src/main/java/net/nemerosa/ontrack/extension/casc/context/core/admin/PredefinedValidationStampsAdminContext.kt +++ b/ontrack-extension-casc/src/main/java/net/nemerosa/ontrack/extension/casc/context/core/admin/PredefinedValidationStampsAdminContext.kt @@ -7,10 +7,10 @@ import net.nemerosa.ontrack.extension.casc.schema.CascType import net.nemerosa.ontrack.extension.casc.schema.cascArray import net.nemerosa.ontrack.extension.casc.schema.cascField import net.nemerosa.ontrack.extension.casc.schema.cascObject -import net.nemerosa.ontrack.extension.scm.service.SCMRefService -import net.nemerosa.ontrack.extension.scm.service.downloadDocument import net.nemerosa.ontrack.json.asJson import net.nemerosa.ontrack.json.parse +import net.nemerosa.ontrack.model.files.FileRefService +import net.nemerosa.ontrack.model.files.downloadDocument import net.nemerosa.ontrack.model.settings.PredefinedValidationStampService import net.nemerosa.ontrack.model.structure.ID import net.nemerosa.ontrack.model.structure.NameDescription @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component @Component class PredefinedValidationStampsAdminContext( private val predefinedValidationStampService: PredefinedValidationStampService, - private val scmRefService: SCMRefService, + private val scmRefService: FileRefService, ) : AbstractCascContext(), SubAdminContext { private val logger: Logger = LoggerFactory.getLogger(PredefinedValidationStampsAdminContext::class.java) diff --git a/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/casc/EnvironmentsCascContext.kt b/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/casc/EnvironmentsCascContext.kt index ddfe919dd7..c7028b50db 100644 --- a/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/casc/EnvironmentsCascContext.kt +++ b/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/casc/EnvironmentsCascContext.kt @@ -16,7 +16,10 @@ import net.nemerosa.ontrack.extension.environments.workflows.SlotWorkflowService import net.nemerosa.ontrack.extension.workflows.definition.Workflow import net.nemerosa.ontrack.json.asJson import net.nemerosa.ontrack.json.parse +import net.nemerosa.ontrack.model.files.FileRef +import net.nemerosa.ontrack.model.files.FileRefService import net.nemerosa.ontrack.model.structure.StructureService +import net.nemerosa.ontrack.model.support.ImageHelper import org.slf4j.LoggerFactory import org.springframework.stereotype.Component import kotlin.io.encoding.Base64 @@ -29,6 +32,7 @@ class EnvironmentsCascContext( private val structureService: StructureService, private val slotService: SlotService, private val slotWorkflowService: SlotWorkflowService, + private val fileRefService: FileRefService, ) : AbstractCascContext(), SubConfigContext { private val logger = LoggerFactory.getLogger(EnvironmentsCascContext::class.java) @@ -166,15 +170,15 @@ class EnvironmentsCascContext( ) { equality { a, b -> a.name == b.name } onCreation { env -> - environmentService.save( - Environment( - name = env.name, - description = env.description, - order = env.order, - tags = env.tags, - image = false, // TODO - ) + val environment = Environment( + name = env.name, + description = env.description, + order = env.order, + tags = env.tags, + image = false, ) + environmentService.save(environment) + setImage(env, environment) } onModification { env, existing -> val adapted = Environment( @@ -183,9 +187,10 @@ class EnvironmentsCascContext( description = env.description, order = env.order, tags = env.tags, - image = false, // TODO + image = existing.image, ) environmentService.save(adapted) + setImage(env, adapted) } onDeletion { existing -> if (!model.keepEnvironments) { @@ -195,6 +200,22 @@ class EnvironmentsCascContext( } } + private fun setImage( + env: EnvironmentCasc, + environment: Environment + ) { + if (!env.image.isNullOrBlank()) { + val image = fileRefService.downloadDocument( + ref = FileRef.parseUri(env.image) ?: error("Cannot parse image URI: ${env.image}"), + type = ImageHelper.IMAGE_PNG, + ) ?: error("Cannot download image at ${env.image}") + environmentService.setEnvironmentImage( + id = environment.id, + document = image + ) + } + } + @OptIn(ExperimentalEncodingApi::class) override fun render(): JsonNode { val environments = environmentService.findAll() diff --git a/ontrack-extension-environments/src/test/java/net/nemerosa/ontrack/extension/environments/casc/EnvironmentsCascModelTest.kt b/ontrack-extension-environments/src/test/java/net/nemerosa/ontrack/extension/environments/casc/EnvironmentsCascModelTest.kt index 6020905777..2b6755ccb3 100644 --- a/ontrack-extension-environments/src/test/java/net/nemerosa/ontrack/extension/environments/casc/EnvironmentsCascModelTest.kt +++ b/ontrack-extension-environments/src/test/java/net/nemerosa/ontrack/extension/environments/casc/EnvironmentsCascModelTest.kt @@ -12,6 +12,7 @@ class EnvironmentsCascModelTest { structureService = mockk(), slotService = mockk(), slotWorkflowService = mockk(), + fileRefService = mockk(), ) val type = context.type println(type) diff --git a/ontrack-extension-github/src/main/java/net/nemerosa/ontrack/extension/github/ingestion/support/DefaultIngestionImageService.kt b/ontrack-extension-github/src/main/java/net/nemerosa/ontrack/extension/github/ingestion/support/DefaultIngestionImageService.kt index 674e54a74a..75824a865b 100644 --- a/ontrack-extension-github/src/main/java/net/nemerosa/ontrack/extension/github/ingestion/support/DefaultIngestionImageService.kt +++ b/ontrack-extension-github/src/main/java/net/nemerosa/ontrack/extension/github/ingestion/support/DefaultIngestionImageService.kt @@ -1,11 +1,10 @@ package net.nemerosa.ontrack.extension.github.ingestion.support import net.nemerosa.ontrack.common.Document -import net.nemerosa.ontrack.extension.github.client.OntrackGitHubClientFactory import net.nemerosa.ontrack.extension.github.model.GitHubEngineConfiguration import net.nemerosa.ontrack.extension.github.property.GitHubProjectConfigurationPropertyType -import net.nemerosa.ontrack.extension.scm.service.SCMRefService -import net.nemerosa.ontrack.extension.scm.service.downloadDocument +import net.nemerosa.ontrack.model.files.FileRefService +import net.nemerosa.ontrack.model.files.downloadDocument import net.nemerosa.ontrack.model.structure.Project import net.nemerosa.ontrack.model.structure.PropertyService import org.springframework.stereotype.Component @@ -15,7 +14,7 @@ import org.springframework.transaction.annotation.Transactional @Transactional class DefaultIngestionImageService( private val propertyService: PropertyService, - private val scmRefService: SCMRefService, + private val scmRefService: FileRefService, ) : IngestionImageService { override fun downloadImage(project: Project, ref: String): Document { diff --git a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRef.kt b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/files/SCMRef.kt similarity index 69% rename from ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRef.kt rename to ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/files/SCMRef.kt index 4ef86b493d..09f2894fee 100644 --- a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRef.kt +++ b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/files/SCMRef.kt @@ -1,18 +1,16 @@ -package net.nemerosa.ontrack.extension.scm.service +package net.nemerosa.ontrack.extension.scm.files data class SCMRef( - val protocol: String, val type: String, val config: String, val ref: String, ) { companion object { - const val PROTOCOL = "scm" - private val regex = "^${PROTOCOL}:\\/\\/([^\\/]*)\\/([^\\/]*)\\/(.*)\$".toRegex() + private val regex = "^\\/\\/([^\\/]*)\\/([^\\/]*)\\/(.*)\$".toRegex() /** - * The URI must be formatted as `scm:////` where: + * The URI must be formatted as `////` where: * * * type is the SCM type: github, bitbucket-server, etc. * * config is the name of the configuration for the SCM type as stored in Ontrack @@ -21,7 +19,6 @@ data class SCMRef( fun parseUri(uri: String): SCMRef? { val m = regex.matchEntire(uri) ?: return null return SCMRef( - protocol = PROTOCOL, type = m.groupValues[1], config = m.groupValues[2], ref = m.groupValues[3], diff --git a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefService.kt b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefService.kt deleted file mode 100644 index 7890956a4a..0000000000 --- a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefService.kt +++ /dev/null @@ -1,15 +0,0 @@ -package net.nemerosa.ontrack.extension.scm.service - -import net.nemerosa.ontrack.common.Document - -interface SCMRefService { - - /** - * Given a SCM Ref, returns a document. - * - * @param ref SCM reference - * @param type [Document type][Document.type] - */ - fun downloadDocument(ref: SCMRef, type: String): Document? - -} \ No newline at end of file diff --git a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefServiceExtensions.kt b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefServiceExtensions.kt deleted file mode 100644 index 7033364aac..0000000000 --- a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefServiceExtensions.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.nemerosa.ontrack.extension.scm.service - -import net.nemerosa.ontrack.common.Document - -fun SCMRefService.downloadDocument(uri: String, type: String): Document? { - val ref = SCMRef.parseUri(uri) - ?: throw SCMRefURIParsingException(uri) - return downloadDocument(ref, type) -} diff --git a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefServiceImpl.kt b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefServiceImpl.kt deleted file mode 100644 index efc56ef143..0000000000 --- a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefServiceImpl.kt +++ /dev/null @@ -1,32 +0,0 @@ -package net.nemerosa.ontrack.extension.scm.service - -import net.nemerosa.ontrack.common.Document -import net.nemerosa.ontrack.extension.api.ExtensionManager -import org.springframework.stereotype.Service - -@Service -class SCMRefServiceImpl( - private val extensionManager: ExtensionManager, -) : SCMRefService { - - override fun downloadDocument(ref: SCMRef, type: String): Document? { - check(ref.protocol == SCMRef.PROTOCOL) { - "Only the scm protocol is supported for now." - } - - val extension = extensionManager.getExtensions(SCMExtension::class.java).find { - it.type == ref.type - } ?: throw SCMRefUnknownSCMTypeException(ref.type) - - val (scm, path) = extension.getSCMPath(ref.config, ref.ref) ?: return null - - val bytes = scm.download( - scmBranch = null, // Using the default branch - path = path, - retryOnNotFound = false, - ) ?: return null - - return Document(type, bytes) - } - -} \ No newline at end of file diff --git a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefURIParsingException.kt b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefURIParsingException.kt deleted file mode 100644 index 381da3e946..0000000000 --- a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefURIParsingException.kt +++ /dev/null @@ -1,7 +0,0 @@ -package net.nemerosa.ontrack.extension.scm.service - -import net.nemerosa.ontrack.model.exceptions.InputException - -class SCMRefURIParsingException(uri: String) : InputException( - """Cannot parse the SCM URI: $uri""" -) diff --git a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefUnknownSCMTypeException.kt b/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefUnknownSCMTypeException.kt deleted file mode 100644 index 1c2782cf99..0000000000 --- a/ontrack-extension-scm/src/main/java/net/nemerosa/ontrack/extension/scm/service/SCMRefUnknownSCMTypeException.kt +++ /dev/null @@ -1,7 +0,0 @@ -package net.nemerosa.ontrack.extension.scm.service - -import net.nemerosa.ontrack.model.exceptions.NotFoundException - -class SCMRefUnknownSCMTypeException(type: String) : NotFoundException( - """SCM type is not supported: $type.""" -) \ No newline at end of file diff --git a/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMFileRefExtension.kt b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMFileRefExtension.kt new file mode 100644 index 0000000000..07e08308fa --- /dev/null +++ b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMFileRefExtension.kt @@ -0,0 +1,39 @@ +package net.nemerosa.ontrack.extension.scm.files + +import net.nemerosa.ontrack.common.Document +import net.nemerosa.ontrack.extension.api.ExtensionManager +import net.nemerosa.ontrack.extension.api.FileRefExtension +import net.nemerosa.ontrack.extension.scm.SCMExtensionFeature +import net.nemerosa.ontrack.extension.scm.service.SCMExtension +import net.nemerosa.ontrack.extension.support.AbstractExtension +import org.springframework.stereotype.Component + +@Component +class SCMFileRefExtension( + extensionFeature: SCMExtensionFeature, + private val extensionManager: ExtensionManager, +) : AbstractExtension(extensionFeature), FileRefExtension { + + override val protocol: String = "scm" + + private val scmExtensions: Map by lazy { + extensionManager.getExtensions(SCMExtension::class.java) + .associateBy { it.type } + } + + override fun download(path: String, type: String): Document? { + val ref = SCMRef.parseUri(path) ?: throw SCMRefParsingException(path) + val extension = scmExtensions[ref.type] + ?: throw SCMRefUnknownSCMTypeException(ref.type) + + val (scm, scmPath) = extension.getSCMPath(ref.config, ref.ref) ?: return null + + val bytes = scm.download( + scmBranch = null, // Using the default branch + path = scmPath, + retryOnNotFound = false, + ) ?: return null + + return Document(type, bytes) + } +} \ No newline at end of file diff --git a/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefParsingException.kt b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefParsingException.kt new file mode 100644 index 0000000000..b55517b0c4 --- /dev/null +++ b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefParsingException.kt @@ -0,0 +1,7 @@ +package net.nemerosa.ontrack.extension.scm.files + +import net.nemerosa.ontrack.common.BaseException + +class SCMRefParsingException(path: String): BaseException( + """Cannot parse SCM path: $path""" +) \ No newline at end of file diff --git a/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/service/SCMRefTest.kt b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefTest.kt similarity index 53% rename from ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/service/SCMRefTest.kt rename to ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefTest.kt index af81505f5b..3d5929e9e3 100644 --- a/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/service/SCMRefTest.kt +++ b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefTest.kt @@ -1,4 +1,4 @@ -package net.nemerosa.ontrack.extension.scm.service +package net.nemerosa.ontrack.extension.scm.files import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -7,25 +7,19 @@ import kotlin.test.assertNull class SCMRefTest { @Test - fun `Missing protocol`() { - assertNull(SCMRef.parseUri("//test/config/ref")) - } - - @Test - fun `Wrong protocol`() { - assertNull(SCMRef.parseUri("https://github.com/nemerosa/ontrack/some/path")) + fun `Wrong format, missing type`() { + assertNull(SCMRef.parseUri("/github.com/nemerosa/ontrack/some/path")) } @Test fun `GitHub URI`() { assertEquals( SCMRef( - protocol = SCMRef.PROTOCOL, type = "github", config = "github.com", ref = "nemerosa/ontrack/some/path" ), - SCMRef.parseUri("scm://github/github.com/nemerosa/ontrack/some/path") + SCMRef.parseUri("//github/github.com/nemerosa/ontrack/some/path") ) } @@ -33,12 +27,11 @@ class SCMRefTest { fun `Test URI`() { assertEquals( SCMRef( - protocol = SCMRef.PROTOCOL, type = "test", config = "my-config", ref = "some/path" ), - SCMRef.parseUri("scm://test/my-config/some/path") + SCMRef.parseUri("//test/my-config/some/path") ) } diff --git a/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefUnknownSCMTypeException.kt b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefUnknownSCMTypeException.kt new file mode 100644 index 0000000000..69c83a2baf --- /dev/null +++ b/ontrack-extension-scm/src/test/java/net/nemerosa/ontrack/extension/scm/files/SCMRefUnknownSCMTypeException.kt @@ -0,0 +1,7 @@ +package net.nemerosa.ontrack.extension.scm.files + +import net.nemerosa.ontrack.common.BaseException + +class SCMRefUnknownSCMTypeException(type: String): BaseException( + """SCM type is not supported: $type.""" +) \ No newline at end of file diff --git a/ontrack-extension-stash/src/test/java/net/nemerosa/ontrack/extension/stash/scm/BitbucketServerSCMExtensionRealIT.kt b/ontrack-extension-stash/src/test/java/net/nemerosa/ontrack/extension/stash/scm/BitbucketServerSCMExtensionRealIT.kt index 5623957505..56ee3740ed 100644 --- a/ontrack-extension-stash/src/test/java/net/nemerosa/ontrack/extension/stash/scm/BitbucketServerSCMExtensionRealIT.kt +++ b/ontrack-extension-stash/src/test/java/net/nemerosa/ontrack/extension/stash/scm/BitbucketServerSCMExtensionRealIT.kt @@ -1,12 +1,16 @@ package net.nemerosa.ontrack.extension.stash.scm -import net.nemerosa.ontrack.extension.scm.service.* +import net.nemerosa.ontrack.extension.scm.service.SCM +import net.nemerosa.ontrack.extension.scm.service.SCMDetector +import net.nemerosa.ontrack.extension.scm.service.SCMPullRequest import net.nemerosa.ontrack.extension.stash.AbstractBitbucketTestSupport import net.nemerosa.ontrack.extension.stash.TestOnBitbucketServer import net.nemerosa.ontrack.extension.stash.bitbucketServerEnv import net.nemerosa.ontrack.extension.stash.client.BitbucketClientFactory import net.nemerosa.ontrack.extension.stash.model.BitbucketRepository import net.nemerosa.ontrack.extension.stash.model.StashConfiguration +import net.nemerosa.ontrack.model.files.FileRefService +import net.nemerosa.ontrack.model.files.downloadDocument import net.nemerosa.ontrack.test.TestUtils.uid import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired @@ -19,7 +23,7 @@ class BitbucketServerSCMExtensionRealIT : AbstractBitbucketTestSupport() { private lateinit var extension: BitbucketServerSCMExtension @Autowired - private lateinit var scmRefService: SCMRefService + private lateinit var scmRefService: FileRefService @Autowired private lateinit var scmDetector: SCMDetector diff --git a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/CoreExtensionFeature.kt b/ontrack-extension-support/src/main/java/net/nemerosa/ontrack/extension/support/CoreExtensionFeature.kt similarity index 64% rename from ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/CoreExtensionFeature.kt rename to ontrack-extension-support/src/main/java/net/nemerosa/ontrack/extension/support/CoreExtensionFeature.kt index 93afea5164..0206230f03 100644 --- a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/CoreExtensionFeature.kt +++ b/ontrack-extension-support/src/main/java/net/nemerosa/ontrack/extension/support/CoreExtensionFeature.kt @@ -1,6 +1,4 @@ -package net.nemerosa.ontrack.boot - -import net.nemerosa.ontrack.extension.support.AbstractExtensionFeature +package net.nemerosa.ontrack.extension.support class CoreExtensionFeature : AbstractExtensionFeature( "core", diff --git a/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRef.kt b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRef.kt new file mode 100644 index 0000000000..786166275e --- /dev/null +++ b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRef.kt @@ -0,0 +1,19 @@ +package net.nemerosa.ontrack.model.files + +data class FileRef( + val protocol: String, + val path: String, +) { + companion object { + + private val regex = "^([a-z][a-z0-9]+):(.*)\$".toRegex() + + fun parseUri(uri: String): FileRef? { + val m = regex.matchEntire(uri) ?: return null + return FileRef( + protocol = m.groupValues[1], + path = m.groupValues[2], + ) + } + } +} diff --git a/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefService.kt b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefService.kt new file mode 100644 index 0000000000..a1e4b9cc8e --- /dev/null +++ b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefService.kt @@ -0,0 +1,16 @@ +package net.nemerosa.ontrack.model.files + +import net.nemerosa.ontrack.common.Document + +interface FileRefService { + + /** + * Given a file reference, returns a document. + * + * @param ref File reference + * @param type [Document type][Document.type] + * @return Document or null if not found + */ + fun downloadDocument(ref: FileRef, type: String): Document? + +} \ No newline at end of file diff --git a/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefServiceExtensions.kt b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefServiceExtensions.kt new file mode 100644 index 0000000000..48cb7f0a66 --- /dev/null +++ b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefServiceExtensions.kt @@ -0,0 +1,9 @@ +package net.nemerosa.ontrack.model.files + +import net.nemerosa.ontrack.common.Document + +fun FileRefService.downloadDocument(uri: String, type: String): Document? { + val ref = FileRef.parseUri(uri) + ?: throw FileRefURIParsingException(uri) + return downloadDocument(ref, type) +} diff --git a/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefURIParsingException.kt b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefURIParsingException.kt new file mode 100644 index 0000000000..c2e9881614 --- /dev/null +++ b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/files/FileRefURIParsingException.kt @@ -0,0 +1,7 @@ +package net.nemerosa.ontrack.model.files + +import net.nemerosa.ontrack.model.exceptions.InputException + +class FileRefURIParsingException(uri: String) : InputException( + """Cannot parse the file reference: $uri""" +) diff --git a/ontrack-model/src/main/java/net/nemerosa/ontrack/model/support/ImageHelper.kt b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/support/ImageHelper.kt index e6052efb32..bcd01a32d3 100644 --- a/ontrack-model/src/main/java/net/nemerosa/ontrack/model/support/ImageHelper.kt +++ b/ontrack-model/src/main/java/net/nemerosa/ontrack/model/support/ImageHelper.kt @@ -10,7 +10,7 @@ object ImageHelper { private const val ICON_IMAGE_SIZE_MAX = 16 * 1000L - private val IMAGE_PNG = "image/png" + const val IMAGE_PNG = "image/png" private val ACCEPTED_IMAGE_TYPES = listOf( IMAGE_PNG, diff --git a/ontrack-model/src/test/java/net/nemerosa/ontrack/model/files/FileRefTest.kt b/ontrack-model/src/test/java/net/nemerosa/ontrack/model/files/FileRefTest.kt new file mode 100644 index 0000000000..6b16e320bb --- /dev/null +++ b/ontrack-model/src/test/java/net/nemerosa/ontrack/model/files/FileRefTest.kt @@ -0,0 +1,36 @@ +package net.nemerosa.ontrack.model.files + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class FileRefTest { + + @Test + fun `Missing protocol`() { + assertNull(FileRef.parseUri("//test/config/ref")) + } + + @Test + fun `GitHub URI`() { + assertEquals( + FileRef( + protocol = "scm", + path = "//github/github.com/nemerosa/ontrack/some/path" + ), + FileRef.parseUri("scm://github/github.com/nemerosa/ontrack/some/path") + ) + } + + @Test + fun `Test URI`() { + assertEquals( + FileRef( + protocol = "scm", + path = "//test/my-config/some/path" + ), + FileRef.parseUri("scm://test/my-config/some/path") + ) + } + +} \ No newline at end of file diff --git a/ontrack-service/build.gradle.kts b/ontrack-service/build.gradle.kts index 4e58d9730c..534f996b0d 100644 --- a/ontrack-service/build.gradle.kts +++ b/ontrack-service/build.gradle.kts @@ -8,6 +8,7 @@ dependencies { implementation(project(":ontrack-model")) implementation(project(":ontrack-repository")) implementation(project(":ontrack-extension-api")) + implementation(project(":ontrack-extension-support")) implementation(project(":ontrack-job")) implementation("org.springframework.security:spring-security-core") implementation("org.springframework.security:spring-security-config") diff --git a/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/Base64FileRefExtension.kt b/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/Base64FileRefExtension.kt new file mode 100644 index 0000000000..bdc68ca170 --- /dev/null +++ b/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/Base64FileRefExtension.kt @@ -0,0 +1,25 @@ +package net.nemerosa.ontrack.service.files + +import net.nemerosa.ontrack.common.Document +import net.nemerosa.ontrack.extension.api.FileRefExtension +import net.nemerosa.ontrack.extension.support.AbstractExtension +import net.nemerosa.ontrack.extension.support.CoreExtensionFeature +import org.springframework.stereotype.Component +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi + +/** + * The path is the Base64 encoding of the file content + */ +@Component +class Base64FileRefExtension : AbstractExtension(CoreExtensionFeature.INSTANCE), FileRefExtension { + + override val protocol: String = "base64" + + @OptIn(ExperimentalEncodingApi::class) + override fun download(path: String, type: String): Document = + Document( + type = type, + content = Base64.decode(path), + ) +} \ No newline at end of file diff --git a/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/ClasspathFileRefExtension.kt b/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/ClasspathFileRefExtension.kt new file mode 100644 index 0000000000..2e602968cc --- /dev/null +++ b/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/ClasspathFileRefExtension.kt @@ -0,0 +1,32 @@ +package net.nemerosa.ontrack.service.files + +import net.nemerosa.ontrack.common.Document +import net.nemerosa.ontrack.common.RunProfile +import net.nemerosa.ontrack.extension.api.FileRefExtension +import net.nemerosa.ontrack.extension.support.AbstractExtension +import net.nemerosa.ontrack.extension.support.CoreExtensionFeature +import org.springframework.context.annotation.Profile +import org.springframework.stereotype.Component + +/** + * Looking for a file in the classpath + */ +@Component +@Profile(RunProfile.UNIT_TEST) +class ClasspathFileRefExtension : AbstractExtension(CoreExtensionFeature.INSTANCE), FileRefExtension { + + override val protocol: String = "classpath" + + override fun download(path: String, type: String): Document = + ClasspathFileRefExtension::class.java.getResourceAsStream(path) + ?.use { + it.readBytes() + } + ?.let { + Document( + type = type, + content = it, + ) + } + ?: Document.EMPTY +} \ No newline at end of file diff --git a/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/FileRefServiceImpl.kt b/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/FileRefServiceImpl.kt new file mode 100644 index 0000000000..7a7efaf7a2 --- /dev/null +++ b/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/FileRefServiceImpl.kt @@ -0,0 +1,27 @@ +package net.nemerosa.ontrack.service.files + +import net.nemerosa.ontrack.common.Document +import net.nemerosa.ontrack.extension.api.ExtensionManager +import net.nemerosa.ontrack.extension.api.FileRefExtension +import net.nemerosa.ontrack.model.files.FileRef +import net.nemerosa.ontrack.model.files.FileRefService +import org.springframework.stereotype.Service + +@Service +class FileRefServiceImpl( + private val extensionManager: ExtensionManager, +) : FileRefService { + + private val extensions: Map by lazy { + extensionManager.getExtensions(FileRefExtension::class.java).associateBy { it.protocol } + } + + override fun downloadDocument(ref: FileRef, type: String): Document? { + // Getting the extension + val extension = extensions[ref.protocol] + ?: throw FileRefUnsupportedProtocolException(ref.protocol) + // Using the extension + return extension.download(ref.path, type) + } + +} \ No newline at end of file diff --git a/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/FileRefUnsupportedProtocolException.kt b/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/FileRefUnsupportedProtocolException.kt new file mode 100644 index 0000000000..bc94e3a343 --- /dev/null +++ b/ontrack-service/src/main/java/net/nemerosa/ontrack/service/files/FileRefUnsupportedProtocolException.kt @@ -0,0 +1,7 @@ +package net.nemerosa.ontrack.service.files + +import net.nemerosa.ontrack.common.BaseException + +class FileRefUnsupportedProtocolException(protocol: String) : BaseException( + """Unsupported file ref protocol: $protocol.""" +) diff --git a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/BranchSearchProvider.kt b/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/BranchSearchProvider.kt index 630831bd58..127499ab5d 100644 --- a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/BranchSearchProvider.kt +++ b/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/BranchSearchProvider.kt @@ -1,6 +1,7 @@ package net.nemerosa.ontrack.boot import com.fasterxml.jackson.databind.JsonNode +import net.nemerosa.ontrack.extension.support.CoreExtensionFeature import net.nemerosa.ontrack.model.events.Event import net.nemerosa.ontrack.model.events.EventFactory import net.nemerosa.ontrack.model.events.EventListener diff --git a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/BuildSearchProvider.kt b/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/BuildSearchProvider.kt index 039f71fbf5..7a20836aec 100644 --- a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/BuildSearchProvider.kt +++ b/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/BuildSearchProvider.kt @@ -1,6 +1,7 @@ package net.nemerosa.ontrack.boot import com.fasterxml.jackson.databind.JsonNode +import net.nemerosa.ontrack.extension.support.CoreExtensionFeature import net.nemerosa.ontrack.model.events.Event import net.nemerosa.ontrack.model.events.EventFactory import net.nemerosa.ontrack.model.events.EventListener diff --git a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ProjectSearchProvider.kt b/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ProjectSearchProvider.kt index e000c8627c..087e01dfad 100644 --- a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ProjectSearchProvider.kt +++ b/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ProjectSearchProvider.kt @@ -1,6 +1,7 @@ package net.nemerosa.ontrack.boot import com.fasterxml.jackson.databind.JsonNode +import net.nemerosa.ontrack.extension.support.CoreExtensionFeature import net.nemerosa.ontrack.model.events.Event import net.nemerosa.ontrack.model.events.EventFactory import net.nemerosa.ontrack.model.events.EventListener diff --git a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ui/CoreUserMenuGroupExtension.kt b/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ui/CoreUserMenuGroupExtension.kt index ffd16255d8..96de289a58 100644 --- a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ui/CoreUserMenuGroupExtension.kt +++ b/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ui/CoreUserMenuGroupExtension.kt @@ -1,8 +1,8 @@ package net.nemerosa.ontrack.boot.ui -import net.nemerosa.ontrack.boot.CoreExtensionFeature import net.nemerosa.ontrack.extension.api.UserMenuGroupExtension import net.nemerosa.ontrack.extension.support.AbstractExtension +import net.nemerosa.ontrack.extension.support.CoreExtensionFeature import net.nemerosa.ontrack.model.support.CoreUserMenuGroups import net.nemerosa.ontrack.model.support.UserMenuGroup import org.springframework.stereotype.Component diff --git a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ui/CoreUserMenuItemExtension.kt b/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ui/CoreUserMenuItemExtension.kt index 51548bd48f..a8fefeee95 100644 --- a/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ui/CoreUserMenuItemExtension.kt +++ b/ontrack-ui/src/main/java/net/nemerosa/ontrack/boot/ui/CoreUserMenuItemExtension.kt @@ -1,8 +1,8 @@ package net.nemerosa.ontrack.boot.ui -import net.nemerosa.ontrack.boot.CoreExtensionFeature import net.nemerosa.ontrack.extension.api.UserMenuItemExtension import net.nemerosa.ontrack.extension.support.AbstractExtension +import net.nemerosa.ontrack.extension.support.CoreExtensionFeature import net.nemerosa.ontrack.model.security.GlobalSettings import net.nemerosa.ontrack.model.security.SecurityService import net.nemerosa.ontrack.model.security.isGlobalFunctionGranted