From f1bf77cb9326b6a56821a3331aa5758fe01e8118 Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Tue, 21 May 2024 18:22:09 +0200 Subject: [PATCH 1/3] refactor: Simplify the secrets API via an enum for the id's entity Signed-off-by: Sebastian Schuberth --- .../src/main/kotlin/api/OrganizationsRoute.kt | 6 +- core/src/main/kotlin/api/ProductsRoute.kt | 6 +- core/src/main/kotlin/api/RepositoriesRoute.kt | 4 +- .../api/OrganizationsRouteIntegrationTest.kt | 3 +- .../api/ProductsRouteIntegrationTest.kt | 3 +- .../api/RepositoriesRouteIntegrationTest.kt | 3 +- .../repositories/DaoSecretRepository.kt | 29 ++++---- .../DaoInfrastructureServiceRepositoryTest.kt | 22 +++--- .../repositories/DaoSecretRepositoryTest.kt | 71 +++++++------------ .../kotlin/repositories/SecretRepository.kt | 20 +++--- .../main/kotlin/FileBasedSecretsProvider.kt | 25 ------- .../test/kotlin/FileBasedSecretStorageTest.kt | 7 +- secrets/spi/build.gradle.kts | 1 + secrets/spi/src/main/kotlin/SecretStorage.kt | 8 +-- .../spi/src/main/kotlin/SecretsProvider.kt | 8 ++- .../spi/src/test/kotlin/SecretStorageTest.kt | 7 +- .../SecretsProviderFactoryForTesting.kt | 25 ------- .../src/main/kotlin/VaultSecretsProvider.kt | 12 ---- .../test/kotlin/VaultSecretsProviderTest.kt | 7 +- .../secret/src/main/kotlin/SecretService.kt | 20 ++---- 20 files changed, 104 insertions(+), 183 deletions(-) diff --git a/core/src/main/kotlin/api/OrganizationsRoute.kt b/core/src/main/kotlin/api/OrganizationsRoute.kt index 6dfb7f48f..bfa06b559 100644 --- a/core/src/main/kotlin/api/OrganizationsRoute.kt +++ b/core/src/main/kotlin/api/OrganizationsRoute.kt @@ -67,6 +67,7 @@ import org.eclipse.apoapsis.ortserver.core.utils.pagingOptions import org.eclipse.apoapsis.ortserver.core.utils.requireIdParameter import org.eclipse.apoapsis.ortserver.core.utils.requireParameter import org.eclipse.apoapsis.ortserver.model.authorization.OrganizationPermission +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.services.InfrastructureServiceService import org.eclipse.apoapsis.ortserver.services.OrganizationService import org.eclipse.apoapsis.ortserver.services.SecretService @@ -238,9 +239,8 @@ fun Route.organizations() = route("organizations") { createSecret.name, createSecret.value, createSecret.description, - organizationId, - null, - null + Entity.ORGANIZATION, + organizationId ).mapToApi() ) } diff --git a/core/src/main/kotlin/api/ProductsRoute.kt b/core/src/main/kotlin/api/ProductsRoute.kt index 68c71c242..54864a8b9 100644 --- a/core/src/main/kotlin/api/ProductsRoute.kt +++ b/core/src/main/kotlin/api/ProductsRoute.kt @@ -55,6 +55,7 @@ import org.eclipse.apoapsis.ortserver.core.utils.pagingOptions import org.eclipse.apoapsis.ortserver.core.utils.requireIdParameter import org.eclipse.apoapsis.ortserver.core.utils.requireParameter import org.eclipse.apoapsis.ortserver.model.authorization.ProductPermission +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.services.ProductService import org.eclipse.apoapsis.ortserver.services.SecretService @@ -201,9 +202,8 @@ fun Route.products() = route("products/{productId}") { createSecret.name, createSecret.value, createSecret.description, - null, - productId, - null + Entity.PRODUCT, + productId ).mapToApi() ) } diff --git a/core/src/main/kotlin/api/RepositoriesRoute.kt b/core/src/main/kotlin/api/RepositoriesRoute.kt index c9a69d6c8..bedf5ee29 100644 --- a/core/src/main/kotlin/api/RepositoriesRoute.kt +++ b/core/src/main/kotlin/api/RepositoriesRoute.kt @@ -60,6 +60,7 @@ import org.eclipse.apoapsis.ortserver.core.utils.pagingOptions import org.eclipse.apoapsis.ortserver.core.utils.requireIdParameter import org.eclipse.apoapsis.ortserver.core.utils.requireParameter import org.eclipse.apoapsis.ortserver.model.authorization.RepositoryPermission +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.services.RepositoryService import org.eclipse.apoapsis.ortserver.services.SecretService @@ -230,8 +231,7 @@ fun Route.repositories() = route("repositories/{repositoryId}") { createSecret.name, createSecret.value, createSecret.description, - null, - null, + Entity.REPOSITORY, repositoryId ).mapToApi() ) diff --git a/core/src/test/kotlin/api/OrganizationsRouteIntegrationTest.kt b/core/src/test/kotlin/api/OrganizationsRouteIntegrationTest.kt index 2f9408c26..eceae5005 100644 --- a/core/src/test/kotlin/api/OrganizationsRouteIntegrationTest.kt +++ b/core/src/test/kotlin/api/OrganizationsRouteIntegrationTest.kt @@ -72,6 +72,7 @@ import org.eclipse.apoapsis.ortserver.model.authorization.ProductRole import org.eclipse.apoapsis.ortserver.model.authorization.Superuser import org.eclipse.apoapsis.ortserver.model.repositories.InfrastructureServiceRepository import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters.Companion.DEFAULT_LIMIT import org.eclipse.apoapsis.ortserver.secrets.Path import org.eclipse.apoapsis.ortserver.secrets.SecretsProviderFactoryForTesting @@ -133,7 +134,7 @@ class OrganizationsRouteIntegrationTest : AbstractIntegrationTest({ path: String = secretPath, name: String = secretName, description: String = secretDescription, - ) = secretRepository.create(path, name, description, organizationId, null, null) + ) = secretRepository.create(path, name, description, Entity.ORGANIZATION, organizationId) "GET /organizations" should { "return all existing organizations for the superuser" { diff --git a/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt b/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt index 2bb7d97d7..b25142389 100644 --- a/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt +++ b/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt @@ -62,6 +62,7 @@ import org.eclipse.apoapsis.ortserver.model.authorization.RepositoryPermission import org.eclipse.apoapsis.ortserver.model.authorization.RepositoryRole import org.eclipse.apoapsis.ortserver.model.repositories.InfrastructureServiceRepository import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters.Companion.DEFAULT_LIMIT import org.eclipse.apoapsis.ortserver.secrets.Path import org.eclipse.apoapsis.ortserver.secrets.SecretsProviderFactoryForTesting @@ -128,7 +129,7 @@ class ProductsRouteIntegrationTest : AbstractIntegrationTest({ path: String = secretPath, name: String = secretName, description: String = secretDescription, - ) = secretRepository.create(path, name, description, null, productId, null) + ) = secretRepository.create(path, name, description, Entity.PRODUCT, productId) "GET /products/{productId}" should { "return a single product" { diff --git a/core/src/test/kotlin/api/RepositoriesRouteIntegrationTest.kt b/core/src/test/kotlin/api/RepositoriesRouteIntegrationTest.kt index f7a8e00a5..4e76689be 100644 --- a/core/src/test/kotlin/api/RepositoriesRouteIntegrationTest.kt +++ b/core/src/test/kotlin/api/RepositoriesRouteIntegrationTest.kt @@ -74,6 +74,7 @@ import org.eclipse.apoapsis.ortserver.model.authorization.RepositoryPermission import org.eclipse.apoapsis.ortserver.model.authorization.RepositoryRole import org.eclipse.apoapsis.ortserver.model.repositories.OrtRunRepository import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters.Companion.DEFAULT_LIMIT import org.eclipse.apoapsis.ortserver.secrets.Path import org.eclipse.apoapsis.ortserver.secrets.SecretsProviderFactoryForTesting @@ -155,7 +156,7 @@ class RepositoriesRouteIntegrationTest : AbstractIntegrationTest({ path: String = secretPath, name: String = secretName, description: String = secretDescription, - ) = secretRepository.create(path, name, description, null, null, repositoryId) + ) = secretRepository.create(path, name, description, Entity.REPOSITORY, repositoryId) "GET /repositories/{repositoryId}" should { "return a single repository" { diff --git a/dao/src/main/kotlin/repositories/DaoSecretRepository.kt b/dao/src/main/kotlin/repositories/DaoSecretRepository.kt index 0b5e49883..787842dfc 100644 --- a/dao/src/main/kotlin/repositories/DaoSecretRepository.kt +++ b/dao/src/main/kotlin/repositories/DaoSecretRepository.kt @@ -32,6 +32,7 @@ import org.eclipse.apoapsis.ortserver.dao.tables.SecretsTable import org.eclipse.apoapsis.ortserver.dao.utils.apply import org.eclipse.apoapsis.ortserver.model.Secret import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters import org.eclipse.apoapsis.ortserver.model.util.OptionalValue @@ -43,23 +44,17 @@ import org.slf4j.LoggerFactory private val logger = LoggerFactory.getLogger(DaoSecretRepository::class.java) class DaoSecretRepository(private val db: Database) : SecretRepository { - override fun create( - path: String, - name: String, - description: String?, - organizationId: Long?, - productId: Long?, - repositoryId: Long? - ) = db.blockingQuery { - SecretDao.new { - this.path = path - this.name = name - this.description = description - this.organization = organizationId?.let { OrganizationDao[it] } - this.product = productId?.let { ProductDao[it] } - this.repository = repositoryId?.let { RepositoryDao[it] } - }.mapToModel() - } + override fun create(path: String, name: String, description: String?, entity: Entity, id: Long) = + db.blockingQuery { + SecretDao.new { + this.path = path + this.name = name + this.description = description + this.organization = id.takeIf { entity == Entity.ORGANIZATION }?.let { OrganizationDao[it] } + this.product = id.takeIf { entity == Entity.PRODUCT }?.let { ProductDao[it] } + this.repository = id.takeIf { entity == Entity.REPOSITORY }?.let { RepositoryDao[it] } + }.mapToModel() + } override fun getByOrganizationIdAndName(organizationId: Long, name: String) = db.entityQuery { findSecretByParentEntityId(organizationId, null, null, name)?.mapToModel() diff --git a/dao/src/test/kotlin/repositories/DaoInfrastructureServiceRepositoryTest.kt b/dao/src/test/kotlin/repositories/DaoInfrastructureServiceRepositoryTest.kt index 6fbbb877d..f340e0f10 100644 --- a/dao/src/test/kotlin/repositories/DaoInfrastructureServiceRepositoryTest.kt +++ b/dao/src/test/kotlin/repositories/DaoInfrastructureServiceRepositoryTest.kt @@ -42,6 +42,7 @@ import org.eclipse.apoapsis.ortserver.model.InfrastructureService import org.eclipse.apoapsis.ortserver.model.Organization import org.eclipse.apoapsis.ortserver.model.Product import org.eclipse.apoapsis.ortserver.model.Secret +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters import org.eclipse.apoapsis.ortserver.model.util.OptionalValue import org.eclipse.apoapsis.ortserver.model.util.OrderDirection @@ -68,8 +69,8 @@ class DaoInfrastructureServiceRepositoryTest : WordSpec() { ortRunRepository = dbExtension.fixtures.ortRunRepository fixtures = dbExtension.fixtures - usernameSecret = secretRepository.create("p1", "user", null, fixtures.organization.id, null, null) - passwordSecret = secretRepository.create("p2", "pass", null, fixtures.organization.id, null, null) + usernameSecret = secretRepository.create("p1", "user", null, Entity.ORGANIZATION, fixtures.organization.id) + passwordSecret = secretRepository.create("p2", "pass", null, Entity.ORGANIZATION, fixtures.organization.id) } "create" should { @@ -297,8 +298,10 @@ class DaoInfrastructureServiceRepositoryTest : WordSpec() { "updateForOrganizationAndName" should { "update the properties of a service" { - val newUser = secretRepository.create("p3", "newUser", null, fixtures.organization.id, null, null) - val newPassword = secretRepository.create("p4", "newPass", null, fixtures.organization.id, null, null) + val newUser = secretRepository + .create("p3", "newUser", null, Entity.ORGANIZATION, fixtures.organization.id) + val newPassword = secretRepository + .create("p4", "newPass", null, Entity.ORGANIZATION, fixtures.organization.id) val service = createInfrastructureService(organization = fixtures.organization) val updatedService = createInfrastructureService( url = "https://repo.example.org/newRepo", @@ -345,8 +348,10 @@ class DaoInfrastructureServiceRepositoryTest : WordSpec() { "updateForProductAndName" should { "update the properties of a service" { - val newUser = secretRepository.create("p3", "newUser", null, fixtures.organization.id, null, null) - val newPassword = secretRepository.create("p4", "newPass", null, fixtures.organization.id, null, null) + val newUser = secretRepository + .create("p3", "newUser", null, Entity.ORGANIZATION, fixtures.organization.id) + val newPassword = secretRepository + .create("p4", "newPass", null, Entity.ORGANIZATION, fixtures.organization.id) val service = createInfrastructureService(product = fixtures.product) val updatedService = createInfrastructureService( url = "https://repo.example.org/newRepo", @@ -548,9 +553,8 @@ class DaoInfrastructureServiceRepositoryTest : WordSpec() { path = "p3", name = "otherUser", description = null, - organizationId = fixtures.organization.id, - productId = null, - repositoryId = null + entity = Entity.ORGANIZATION, + id = fixtures.organization.id ) val service3 = createInfrastructureService( diff --git a/dao/src/test/kotlin/repositories/DaoSecretRepositoryTest.kt b/dao/src/test/kotlin/repositories/DaoSecretRepositoryTest.kt index be14fc6a6..e0c2fcfd8 100644 --- a/dao/src/test/kotlin/repositories/DaoSecretRepositoryTest.kt +++ b/dao/src/test/kotlin/repositories/DaoSecretRepositoryTest.kt @@ -19,20 +19,18 @@ package org.eclipse.apoapsis.ortserver.dao.repositories -import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.collections.containExactlyInAnyOrder import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.should import io.kotest.matchers.shouldBe -import java.lang.IllegalArgumentException -import java.sql.SQLException - import org.eclipse.apoapsis.ortserver.dao.test.DatabaseTestExtension import org.eclipse.apoapsis.ortserver.dao.test.Fixtures import org.eclipse.apoapsis.ortserver.model.RepositoryType import org.eclipse.apoapsis.ortserver.model.Secret +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.model.util.asPresent class DaoSecretRepositoryTest : StringSpec() { @@ -61,7 +59,7 @@ class DaoSecretRepositoryTest : StringSpec() { "create should create an entry in the database" { val name = "secret1" - val secret = createSecret(name, organizationId, null, null) + val secret = createSecret(name, Entity.ORGANIZATION, organizationId) val dbEntry = secretRepository.getByOrganizationIdAndName(organizationId, name) @@ -71,7 +69,7 @@ class DaoSecretRepositoryTest : StringSpec() { "update should update an organization secret in the database" { val name = "secret2" - val secret = createSecret(name, organizationId, null, null) + val secret = createSecret(name, Entity.ORGANIZATION, organizationId) secretRepository.updateForOrganizationAndName( organizationId, @@ -95,7 +93,7 @@ class DaoSecretRepositoryTest : StringSpec() { "update should update a product secret in the database" { val name = "secret3" - val secret = createSecret(name, null, productId, null) + val secret = createSecret(name, Entity.PRODUCT, productId) secretRepository.updateForProductAndName( productId, @@ -119,7 +117,7 @@ class DaoSecretRepositoryTest : StringSpec() { "update should update a repository secret in the database" { val name = "secret2" - val secret = createSecret(name, null, null, repositoryId) + val secret = createSecret(name, Entity.REPOSITORY, repositoryId) secretRepository.updateForRepositoryAndName( repositoryId, @@ -143,31 +141,24 @@ class DaoSecretRepositoryTest : StringSpec() { "delete should delete the database entry" { val name = "secret3" - createSecret(name, null, null, repositoryId) + createSecret(name, Entity.REPOSITORY, repositoryId) secretRepository.deleteForRepositoryAndName(repositoryId, name) secretRepository.listForRepository(repositoryId) shouldBe emptyList() } - "adding an ambiguous secret should cause an exception" { - shouldThrow { - secretRepository.create(path, name, description, organizationId, productId, repositoryId) - } - } - "Reading all secrets of specific type" should { "return all stored results for a specific organization" { - val organizationSecret1 = createSecret("secret4", organizationId, null, null) - val organizationSecret2 = createSecret("secret5", organizationId, null, null) + val organizationSecret1 = createSecret("secret4", Entity.ORGANIZATION, organizationId) + val organizationSecret2 = createSecret("secret5", Entity.ORGANIZATION, organizationId) createSecret( "secret6", - fixtures.createOrganization("extra organization", "org description").id, - null, - null + Entity.ORGANIZATION, + fixtures.createOrganization("extra organization", "org description").id ) - createSecret("productSecret1", null, productId, null) - createSecret("repositorySecret1", null, null, repositoryId) + createSecret("productSecret1", Entity.PRODUCT, productId) + createSecret("repositorySecret1", Entity.REPOSITORY, repositoryId) secretRepository.listForOrganization(organizationId) should containExactlyInAnyOrder( organizationSecret1, @@ -176,16 +167,15 @@ class DaoSecretRepositoryTest : StringSpec() { } "return all stored results for a specific product" { - val productSecret1 = createSecret("secret7", null, productId, null) - val productSecret2 = createSecret("secret8", null, productId, null) + val productSecret1 = createSecret("secret7", Entity.PRODUCT, productId) + val productSecret2 = createSecret("secret8", Entity.PRODUCT, productId) createSecret( "secret9", - null, - fixtures.createProduct("extra product", "prod description", fixtures.organization.id).id, - null + Entity.PRODUCT, + fixtures.createProduct("extra product", "prod description", fixtures.organization.id).id ) - createSecret("organizationSecret1", organizationId, null, null) - createSecret("repositorySecret2", null, null, repositoryId) + createSecret("organizationSecret1", Entity.ORGANIZATION, organizationId) + createSecret("repositorySecret2", Entity.REPOSITORY, repositoryId) secretRepository.listForProduct(productId) should containExactlyInAnyOrder( productSecret1, @@ -194,16 +184,15 @@ class DaoSecretRepositoryTest : StringSpec() { } "return all stored results for a specific repository" { - val repositorySecret1 = createSecret("secret10", null, null, repositoryId) - val repositorySecret2 = createSecret("secret11", null, null, repositoryId) + val repositorySecret1 = createSecret("secret10", Entity.REPOSITORY, repositoryId) + val repositorySecret2 = createSecret("secret11", Entity.REPOSITORY, repositoryId) createSecret( "secret12", - null, - null, + Entity.REPOSITORY, fixtures.createRepository(RepositoryType.GIT, "repo description", fixtures.product.id).id ) - createSecret("organizationSecret2", organizationId, null, null) - createSecret("productSecret2", organizationId, null, null) + createSecret("organizationSecret2", Entity.ORGANIZATION, organizationId) + createSecret("productSecret2", Entity.ORGANIZATION, organizationId) secretRepository.listForRepository(repositoryId) should containExactlyInAnyOrder( repositorySecret1, @@ -215,13 +204,7 @@ class DaoSecretRepositoryTest : StringSpec() { private fun createSecret( name: String, - organizationId: Long?, - productId: Long?, - repositoryId: Long? - ) = when { - organizationId != null -> secretRepository.create("$path$name", name, description, organizationId, null, null) - productId != null -> secretRepository.create("$path$name", name, description, null, productId, null) - repositoryId != null -> secretRepository.create("$path$name", name, description, null, null, repositoryId) - else -> throw IllegalArgumentException("The secret wasn't created") - } + entity: SecretRepository.Entity, + id: Long + ) = secretRepository.create("$path$name", name, description, entity, id) } diff --git a/model/src/commonMain/kotlin/repositories/SecretRepository.kt b/model/src/commonMain/kotlin/repositories/SecretRepository.kt index 659226d53..4a590e474 100644 --- a/model/src/commonMain/kotlin/repositories/SecretRepository.kt +++ b/model/src/commonMain/kotlin/repositories/SecretRepository.kt @@ -29,16 +29,18 @@ import org.eclipse.apoapsis.ortserver.model.util.OptionalValue @Suppress("TooManyFunctions") interface SecretRepository { /** - * Create a secret for either [organization][organizationId], [product][productId] or [repository][repositoryId] + * The entity to which a secret can be attached. */ - fun create( - path: String, - name: String, - description: String?, - organizationId: Long?, - productId: Long?, - repositoryId: Long? - ): Secret + enum class Entity { + ORGANIZATION, + PRODUCT, + REPOSITORY + } + + /** + * Create a secret of the given [name] at [path] attached to [entity] with [id] and an optional [description]. + */ + fun create(path: String, name: String, description: String?, entity: Entity, id: Long): Secret /** * Get a secret by [organizationId] and [name]. Returns null if the secret is not found. diff --git a/secrets/file/src/main/kotlin/FileBasedSecretsProvider.kt b/secrets/file/src/main/kotlin/FileBasedSecretsProvider.kt index 1aec15194..a7a2dc170 100644 --- a/secrets/file/src/main/kotlin/FileBasedSecretsProvider.kt +++ b/secrets/file/src/main/kotlin/FileBasedSecretsProvider.kt @@ -116,29 +116,4 @@ class FileBasedSecretsProvider(config: Config) : SecretsProvider { secrets -= path writeSecrets(secrets) } - - override fun createPath( - organizationId: Long?, - productId: Long?, - repositoryId: Long?, - secretName: String - ): Path { - val secretType = when { - organizationId != null -> "organization" - productId != null -> "product" - repositoryId != null -> "repository" - else -> throw IllegalArgumentException( - "Either one of organizationId, productId or repositoryId should be specified to create a path." - ) - } - return Path( - listOfNotNull( - secretType, - organizationId, - productId, - repositoryId, - secretName - ).joinToString("_") - ) - } } diff --git a/secrets/file/src/test/kotlin/FileBasedSecretStorageTest.kt b/secrets/file/src/test/kotlin/FileBasedSecretStorageTest.kt index 925f0f119..9675604da 100644 --- a/secrets/file/src/test/kotlin/FileBasedSecretStorageTest.kt +++ b/secrets/file/src/test/kotlin/FileBasedSecretStorageTest.kt @@ -31,6 +31,7 @@ import java.util.Base64 import kotlinx.serialization.json.Json import org.eclipse.apoapsis.ortserver.config.ConfigManager +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.secrets.SecretStorage.Companion.CONFIG_PREFIX import org.eclipse.apoapsis.ortserver.secrets.SecretStorage.Companion.NAME_PROPERTY import org.eclipse.apoapsis.ortserver.secrets.file.FileBasedSecretsProvider @@ -109,19 +110,19 @@ class FileBasedSecretStorageTest : WordSpec() { "createPath" should { "generate a path for an organization secret" { - val result = storage.createPath(1, null, null, "newSecret") + val result = storage.createPath(Entity.ORGANIZATION, 1, "newSecret") result shouldBe Path("organization_1_newSecret") } "generate a path for a product secret" { - val result = storage.createPath(null, 1, null, "newSecret") + val result = storage.createPath(Entity.PRODUCT, 1, "newSecret") result shouldBe Path("product_1_newSecret") } "generate a path for a repository secret" { - val result = storage.createPath(null, null, 1, "newSecret") + val result = storage.createPath(Entity.REPOSITORY, 1, "newSecret") result shouldBe Path("repository_1_newSecret") } diff --git a/secrets/spi/build.gradle.kts b/secrets/spi/build.gradle.kts index 74333c18b..42eaa44c8 100644 --- a/secrets/spi/build.gradle.kts +++ b/secrets/spi/build.gradle.kts @@ -28,6 +28,7 @@ group = "org.eclipse.apoapsis.ortserver.secrets" dependencies { api(projects.config.configSpi) + api(projects.model) testImplementation(projects.utils.test) diff --git a/secrets/spi/src/main/kotlin/SecretStorage.kt b/secrets/spi/src/main/kotlin/SecretStorage.kt index ba8cde35a..2bf33c974 100644 --- a/secrets/spi/src/main/kotlin/SecretStorage.kt +++ b/secrets/spi/src/main/kotlin/SecretStorage.kt @@ -23,6 +23,7 @@ import java.util.ServiceLoader import org.eclipse.apoapsis.ortserver.config.ConfigManager import org.eclipse.apoapsis.ortserver.config.Path as ConfigPath +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository /** * A class providing convenient access to secrets based on a [SecretsProvider]. @@ -132,11 +133,10 @@ class SecretStorage( fun removeSecretCatching(path: Path): Result = runCatching { removeSecret(path) } /** - * Generate a [Path] for the secret basing on the [organizationId], [productId] or [repositoryId] they belong to - * and the [secretName]. + * Generate a [Path] for the secret belonging to the given [entity] with [id], as well as the [secretName]. */ - fun createPath(organizationId: Long?, productId: Long?, repositoryId: Long?, secretName: String) = - wrapExceptions { provider.createPath(organizationId, productId, repositoryId, secretName) } + fun createPath(entity: SecretRepository.Entity, id: Long, secretName: String) = + wrapExceptions { provider.createPath(entity, id, secretName) } } /** diff --git a/secrets/spi/src/main/kotlin/SecretsProvider.kt b/secrets/spi/src/main/kotlin/SecretsProvider.kt index 70a13dfab..47c0041e7 100644 --- a/secrets/spi/src/main/kotlin/SecretsProvider.kt +++ b/secrets/spi/src/main/kotlin/SecretsProvider.kt @@ -19,6 +19,8 @@ package org.eclipse.apoapsis.ortserver.secrets +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository + /** * Definition of an interface for accessing secrets stored in a specific storage implementation. * @@ -50,8 +52,8 @@ interface SecretsProvider { fun removeSecret(path: Path) /** - * Generate a [Path] for the secret belonging to the given [organizationId], [productId] and [repositoryId], as well - * as the [secretName]. + * Generate a [Path] for the secret belonging to the given [entity] with [id], as well as the [secretName]. */ - fun createPath(organizationId: Long?, productId: Long?, repositoryId: Long?, secretName: String): Path + fun createPath(entity: SecretRepository.Entity, id: Long, secretName: String): Path = + Path("${entity.name.lowercase()}_${id}_$secretName") } diff --git a/secrets/spi/src/test/kotlin/SecretStorageTest.kt b/secrets/spi/src/test/kotlin/SecretStorageTest.kt index 05193a4a6..e82b96f6b 100644 --- a/secrets/spi/src/test/kotlin/SecretStorageTest.kt +++ b/secrets/spi/src/test/kotlin/SecretStorageTest.kt @@ -34,6 +34,7 @@ import io.kotest.matchers.types.beInstanceOf import kotlin.IllegalArgumentException import org.eclipse.apoapsis.ortserver.config.ConfigManager +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.secrets.SecretsProviderFactoryForTesting.Companion.PASSWORD_PATH import org.eclipse.apoapsis.ortserver.secrets.SecretsProviderFactoryForTesting.Companion.PASSWORD_SECRET @@ -222,21 +223,21 @@ class SecretStorageTest : WordSpec({ "createPath" should { "generate a path for an organization secret" { val storage = createStorage() - val result = storage.createPath(1, null, null, "newSecret") + val result = storage.createPath(Entity.ORGANIZATION, 1, "newSecret") result shouldBe Path("organization_1_newSecret") } "generate a path for a product secret" { val storage = createStorage() - val result = storage.createPath(null, 1, null, "newSecret") + val result = storage.createPath(Entity.PRODUCT, 1, "newSecret") result shouldBe Path("product_1_newSecret") } "generate a path for a repository secret" { val storage = createStorage() - val result = storage.createPath(null, null, 1, "newSecret") + val result = storage.createPath(Entity.REPOSITORY, 1, "newSecret") result shouldBe Path("repository_1_newSecret") } diff --git a/secrets/spi/src/testFixtures/kotlin/SecretsProviderFactoryForTesting.kt b/secrets/spi/src/testFixtures/kotlin/SecretsProviderFactoryForTesting.kt index 0056a863a..59e7720ce 100644 --- a/secrets/spi/src/testFixtures/kotlin/SecretsProviderFactoryForTesting.kt +++ b/secrets/spi/src/testFixtures/kotlin/SecretsProviderFactoryForTesting.kt @@ -103,31 +103,6 @@ class SecretsProviderFactoryForTesting : SecretsProviderFactory { override fun removeSecret(path: Path) { storage -= checkPath(path) } - - override fun createPath( - organizationId: Long?, - productId: Long?, - repositoryId: Long?, - secretName: String - ): Path { - val secretType = when { - organizationId != null -> "organization" - productId != null -> "product" - repositoryId != null -> "repository" - else -> throw IllegalArgumentException( - "Either one of organizationId, productId or repositoryId should be specified to create a path." - ) - } - return Path( - listOfNotNull( - secretType, - organizationId, - productId, - repositoryId, - secretName - ).joinToString("_") - ) - } }.also { latestInstance = it } } } diff --git a/secrets/vault/src/main/kotlin/VaultSecretsProvider.kt b/secrets/vault/src/main/kotlin/VaultSecretsProvider.kt index 553ab4720..931a5a1f2 100644 --- a/secrets/vault/src/main/kotlin/VaultSecretsProvider.kt +++ b/secrets/vault/src/main/kotlin/VaultSecretsProvider.kt @@ -118,18 +118,6 @@ class VaultSecretsProvider( } } - override fun createPath(organizationId: Long?, productId: Long?, repositoryId: Long?, secretName: String): Path { - val secretType = when { - organizationId != null -> "organization" - productId != null -> "product" - repositoryId != null -> "repository" - else -> throw IllegalArgumentException( - "Either one of organizationId, productId or repositoryId should be specified to create a path." - ) - } - return Path(listOfNotNull(secretType, organizationId, productId, repositoryId, secretName).joinToString("_")) - } - /** * Create an [HttpClient] with a configuration to communicate with the Vault service. The client is prepared to * obtain a new client token if necessary. diff --git a/secrets/vault/src/test/kotlin/VaultSecretsProviderTest.kt b/secrets/vault/src/test/kotlin/VaultSecretsProviderTest.kt index 798f1d301..70ec74cda 100644 --- a/secrets/vault/src/test/kotlin/VaultSecretsProviderTest.kt +++ b/secrets/vault/src/test/kotlin/VaultSecretsProviderTest.kt @@ -27,6 +27,7 @@ import io.kotest.matchers.shouldBe import io.ktor.client.plugins.ClientRequestException +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.secrets.Path import org.eclipse.apoapsis.ortserver.secrets.Secret @@ -119,21 +120,21 @@ class VaultSecretsProviderTest : WordSpec() { "createPath" should { "generate a path for an organization secret" { val provider = vault.createProvider() - val result = provider.createPath(1, null, null, "newSecret") + val result = provider.createPath(Entity.ORGANIZATION, 1, "newSecret") result shouldBe Path("organization_1_newSecret") } "generate a path for a product secret" { val provider = vault.createProvider() - val result = provider.createPath(null, 1, null, "newSecret") + val result = provider.createPath(Entity.PRODUCT, 1, "newSecret") result shouldBe Path("product_1_newSecret") } "generate a path for a repository secret" { val provider = vault.createProvider() - val result = provider.createPath(null, null, 1, "newSecret") + val result = provider.createPath(Entity.REPOSITORY, 1, "newSecret") result shouldBe Path("repository_1_newSecret") } diff --git a/services/secret/src/main/kotlin/SecretService.kt b/services/secret/src/main/kotlin/SecretService.kt index 829ac42df..b1f4b412d 100644 --- a/services/secret/src/main/kotlin/SecretService.kt +++ b/services/secret/src/main/kotlin/SecretService.kt @@ -42,22 +42,18 @@ class SecretService( ) { /** * Create a secret with the given metadata [name] and [description], and the provided [value]. As the secret can - * only belong to an organization, a product, or a repository, a respective [check][requireUnambiguousSecret] - * validates the input data. + * only belong to an organization, a product, or a repository, as specified by the [entity]. */ suspend fun createSecret( name: String, value: String, description: String?, - organizationId: Long?, - productId: Long?, - repositoryId: Long? + entity: SecretRepository.Entity, + id: Long ): Secret = db.dbQuery { - requireUnambiguousSecret(organizationId, productId, repositoryId) + val path = secretStorage.createPath(entity, id, name) - val path = secretStorage.createPath(organizationId, productId, repositoryId, name) - - val secret = secretRepository.create(path.path, name, description, organizationId, productId, repositoryId) + val secret = secretRepository.create(path.path, name, description, entity, id) secretStorage.writeSecret(path, SecretValue(value)) @@ -210,12 +206,6 @@ class SecretService( secret } - private fun requireUnambiguousSecret(organizationId: Long?, productId: Long?, repositoryId: Long?) { - require(listOfNotNull(organizationId, productId, repositoryId).size == 1) { - "The secret should belong to one of the following: Organization, Product or Repository." - } - } - /** * Update the [value] of this [Secret] in the [SecretStorage]. */ From 1aa1216c7371d65a2d82f4b19d5349e42f8633d2 Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Tue, 21 May 2024 19:31:20 +0200 Subject: [PATCH 2/3] fix(core): Use the correct product id in tests While it did not seem to make a difference regarding the success of the test, semantically the product id is the correct one to use. Signed-off-by: Sebastian Schuberth --- core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt b/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt index b25142389..39aa0d68d 100644 --- a/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt +++ b/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt @@ -555,7 +555,7 @@ class ProductsRouteIntegrationTest : AbstractIntegrationTest({ response shouldHaveStatus HttpStatusCode.OK response shouldHaveBody Secret(secret.name, updatedDescription) - secretRepository.getByProductIdAndName(orgId, updateSecret.name.valueOrThrow)?.mapToApi() shouldBe + secretRepository.getByProductIdAndName(productId, updateSecret.name.valueOrThrow)?.mapToApi() shouldBe Secret(secret.name, updatedDescription) } } @@ -588,7 +588,7 @@ class ProductsRouteIntegrationTest : AbstractIntegrationTest({ setBody(updateSecret) } shouldHaveStatus HttpStatusCode.InternalServerError - secretRepository.getByProductIdAndName(orgId, secret.name) shouldBe secret + secretRepository.getByProductIdAndName(productId, secret.name) shouldBe secret } } From e51f3e62cf4dbb3f29c5b86aa7f75c3e5954849c Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Tue, 21 May 2024 18:54:08 +0200 Subject: [PATCH 3/3] refactor: Further simplify secrets APIs via the enum for entities Signed-off-by: Sebastian Schuberth --- .../src/main/kotlin/api/OrganizationsRoute.kt | 10 +- core/src/main/kotlin/api/ProductsRoute.kt | 9 +- core/src/main/kotlin/api/RepositoriesRoute.kt | 10 +- .../api/OrganizationsRouteIntegrationTest.kt | 12 +- .../api/ProductsRouteIntegrationTest.kt | 12 +- .../api/RepositoriesRouteIntegrationTest.kt | 12 +- .../repositories/DaoSecretRepository.kt | 127 +++-------------- .../repositories/DaoSecretRepositoryTest.kt | 27 ++-- .../kotlin/repositories/SecretRepository.kt | 77 ++--------- .../kotlin/InfrastructureServiceService.kt | 3 +- .../InfrastructureServiceServiceTest.kt | 7 +- .../secret/src/main/kotlin/SecretService.kt | 129 +++--------------- .../src/test/kotlin/AnalyzerEndpointTest.kt | 3 +- .../env/config/EnvironmentConfigLoader.kt | 7 +- .../env/config/EnvironmentConfigLoaderTest.kt | 11 +- 15 files changed, 112 insertions(+), 344 deletions(-) diff --git a/core/src/main/kotlin/api/OrganizationsRoute.kt b/core/src/main/kotlin/api/OrganizationsRoute.kt index bfa06b559..38c53721b 100644 --- a/core/src/main/kotlin/api/OrganizationsRoute.kt +++ b/core/src/main/kotlin/api/OrganizationsRoute.kt @@ -176,7 +176,8 @@ fun Route.organizations() = route("organizations") { val orgId = call.requireIdParameter("organizationId") val pagingOptions = call.pagingOptions(SortProperty("name", SortDirection.ASCENDING)) - val secretsForOrganization = secretService.listForOrganization(orgId, pagingOptions.mapToModel()) + val secretsForOrganization = secretService + .listSecrets(Entity.ORGANIZATION, orgId, pagingOptions.mapToModel()) val pagedResponse = PagedResponse( secretsForOrganization.map { it.mapToApi() }, pagingOptions @@ -192,7 +193,7 @@ fun Route.organizations() = route("organizations") { val organizationId = call.requireIdParameter("organizationId") val secretName = call.requireParameter("secretName") - secretService.getSecretByOrganizationIdAndName(organizationId, secretName) + secretService.getSecret(Entity.ORGANIZATION, organizationId, secretName) ?.let { call.respond(HttpStatusCode.OK, it.mapToApi()) } ?: call.respond(HttpStatusCode.NotFound) } @@ -206,7 +207,8 @@ fun Route.organizations() = route("organizations") { call.respond( HttpStatusCode.OK, - secretService.updateSecretByOrganizationAndName( + secretService.updateSecret( + Entity.ORGANIZATION, organizationId, secretName, updateSecret.value.mapToModel(), @@ -221,7 +223,7 @@ fun Route.organizations() = route("organizations") { val organizationId = call.requireIdParameter("organizationId") val secretName = call.requireParameter("secretName") - secretService.deleteSecretByOrganizationAndName(organizationId, secretName) + secretService.deleteSecret(Entity.ORGANIZATION, organizationId, secretName) call.respond(HttpStatusCode.NoContent) } diff --git a/core/src/main/kotlin/api/ProductsRoute.kt b/core/src/main/kotlin/api/ProductsRoute.kt index 54864a8b9..d420716ac 100644 --- a/core/src/main/kotlin/api/ProductsRoute.kt +++ b/core/src/main/kotlin/api/ProductsRoute.kt @@ -139,7 +139,7 @@ fun Route.products() = route("products/{productId}") { val productId = call.requireIdParameter("productId") val pagingOptions = call.pagingOptions(SortProperty("name", SortDirection.ASCENDING)) - val secretsForProduct = secretService.listForProduct(productId, pagingOptions.mapToModel()) + val secretsForProduct = secretService.listSecrets(Entity.PRODUCT, productId, pagingOptions.mapToModel()) val pagedResponse = PagedResponse( secretsForProduct.map { it.mapToApi() }, pagingOptions @@ -155,7 +155,7 @@ fun Route.products() = route("products/{productId}") { val productId = call.requireIdParameter("productId") val secretName = call.requireParameter("secretName") - secretService.getSecretByProductIdAndName(productId, secretName) + secretService.getSecret(Entity.PRODUCT, productId, secretName) ?.let { call.respond(HttpStatusCode.OK, it.mapToApi()) } ?: call.respond(HttpStatusCode.NotFound) } @@ -169,7 +169,8 @@ fun Route.products() = route("products/{productId}") { call.respond( HttpStatusCode.OK, - secretService.updateSecretByProductAndName( + secretService.updateSecret( + Entity.PRODUCT, productId, secretName, updateSecret.value.mapToModel(), @@ -184,7 +185,7 @@ fun Route.products() = route("products/{productId}") { val productId = call.requireIdParameter("productId") val secretName = call.requireParameter("secretName") - secretService.deleteSecretByProductAndName(productId, secretName) + secretService.deleteSecret(Entity.PRODUCT, productId, secretName) call.respond(HttpStatusCode.NoContent) } diff --git a/core/src/main/kotlin/api/RepositoriesRoute.kt b/core/src/main/kotlin/api/RepositoriesRoute.kt index bedf5ee29..27e84810a 100644 --- a/core/src/main/kotlin/api/RepositoriesRoute.kt +++ b/core/src/main/kotlin/api/RepositoriesRoute.kt @@ -168,7 +168,8 @@ fun Route.repositories() = route("repositories/{repositoryId}") { val repositoryId = call.requireIdParameter("repositoryId") val pagingOptions = call.pagingOptions(SortProperty("name", SortDirection.ASCENDING)) - val secretsForRepository = secretService.listForRepository(repositoryId, pagingOptions.mapToModel()) + val secretsForRepository = secretService + .listSecrets(Entity.REPOSITORY, repositoryId, pagingOptions.mapToModel()) val pagedResponse = PagedResponse( secretsForRepository.map { it.mapToApi() }, pagingOptions @@ -184,7 +185,7 @@ fun Route.repositories() = route("repositories/{repositoryId}") { val repositoryId = call.requireIdParameter("repositoryId") val secretName = call.requireParameter("secretName") - secretService.getSecretByRepositoryIdAndName(repositoryId, secretName) + secretService.getSecret(Entity.REPOSITORY, repositoryId, secretName) ?.let { call.respond(HttpStatusCode.OK, it.mapToApi()) } ?: call.respond(HttpStatusCode.NotFound) } @@ -198,7 +199,8 @@ fun Route.repositories() = route("repositories/{repositoryId}") { call.respond( HttpStatusCode.OK, - secretService.updateSecretByRepositoryAndName( + secretService.updateSecret( + Entity.REPOSITORY, repositoryId, secretName, updateSecret.value.mapToModel(), @@ -213,7 +215,7 @@ fun Route.repositories() = route("repositories/{repositoryId}") { val repositoryId = call.requireIdParameter("repositoryId") val secretName = call.requireParameter("secretName") - secretService.deleteSecretByRepositoryAndName(repositoryId, secretName) + secretService.deleteSecret(Entity.REPOSITORY, repositoryId, secretName) call.respond(HttpStatusCode.NoContent) } diff --git a/core/src/test/kotlin/api/OrganizationsRouteIntegrationTest.kt b/core/src/test/kotlin/api/OrganizationsRouteIntegrationTest.kt index eceae5005..9ea325397 100644 --- a/core/src/test/kotlin/api/OrganizationsRouteIntegrationTest.kt +++ b/core/src/test/kotlin/api/OrganizationsRouteIntegrationTest.kt @@ -694,7 +694,7 @@ class OrganizationsRouteIntegrationTest : AbstractIntegrationTest({ response shouldHaveStatus HttpStatusCode.Created response shouldHaveBody Secret(secret.name, secret.description) - secretRepository.getByOrganizationIdAndName(organizationId, secret.name)?.mapToApi() shouldBe + secretRepository.get(Entity.ORGANIZATION, organizationId, secret.name)?.mapToApi() shouldBe Secret(secret.name, secret.description) val provider = SecretsProviderFactoryForTesting.instance() @@ -732,7 +732,7 @@ class OrganizationsRouteIntegrationTest : AbstractIntegrationTest({ body.message shouldBe "Request validation has failed." body.cause shouldContain "Validation failed for CreateSecret" - secretRepository.getByOrganizationIdAndName(organizationId, secret.name)?.mapToApi().shouldBeNull() + secretRepository.get(Entity.ORGANIZATION, organizationId, secret.name)?.mapToApi().shouldBeNull() val provider = SecretsProviderFactoryForTesting.instance() provider.readSecret(Path("organization_${organizationId}_${secret.name}"))?.value.shouldBeNull() @@ -767,7 +767,7 @@ class OrganizationsRouteIntegrationTest : AbstractIntegrationTest({ response shouldHaveStatus HttpStatusCode.OK response shouldHaveBody Secret(secret.name, updatedDescription) - secretRepository.getByOrganizationIdAndName(organizationId, updateSecret.name.valueOrThrow) + secretRepository.get(Entity.ORGANIZATION, organizationId, updateSecret.name.valueOrThrow) ?.mapToApi() shouldBe Secret(secret.name, updatedDescription) } } @@ -800,7 +800,7 @@ class OrganizationsRouteIntegrationTest : AbstractIntegrationTest({ setBody(updateSecret) } shouldHaveStatus HttpStatusCode.InternalServerError - secretRepository.getByOrganizationIdAndName(organizationId, secret.name) shouldBe secret + secretRepository.get(Entity.ORGANIZATION, organizationId, secret.name) shouldBe secret } } @@ -825,7 +825,7 @@ class OrganizationsRouteIntegrationTest : AbstractIntegrationTest({ superuserClient.delete("/api/v1/organizations/$organizationId/secrets/${secret.name}") shouldHaveStatus HttpStatusCode.NoContent - secretRepository.listForOrganization(organizationId) shouldBe emptyList() + secretRepository.list(Entity.ORGANIZATION, organizationId) shouldBe emptyList() val provider = SecretsProviderFactoryForTesting.instance() provider.readSecret(Path(secret.path)) should beNull() @@ -868,7 +868,7 @@ class OrganizationsRouteIntegrationTest : AbstractIntegrationTest({ superuserClient.delete("/api/v1/organizations/$organizationId/secrets/${secret.name}") shouldHaveStatus HttpStatusCode.InternalServerError - secretRepository.getByOrganizationIdAndName(organizationId, secret.name) shouldBe secret + secretRepository.get(Entity.ORGANIZATION, organizationId, secret.name) shouldBe secret } } diff --git a/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt b/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt index 39aa0d68d..ab1bc2222 100644 --- a/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt +++ b/core/src/test/kotlin/api/ProductsRouteIntegrationTest.kt @@ -482,7 +482,7 @@ class ProductsRouteIntegrationTest : AbstractIntegrationTest({ response shouldHaveStatus HttpStatusCode.Created response shouldHaveBody Secret(secret.name, secret.description) - secretRepository.getByProductIdAndName(productId, secret.name)?.mapToApi() shouldBe + secretRepository.get(Entity.PRODUCT, productId, secret.name)?.mapToApi() shouldBe Secret(secret.name, secret.description) val provider = SecretsProviderFactoryForTesting.instance() @@ -520,7 +520,7 @@ class ProductsRouteIntegrationTest : AbstractIntegrationTest({ body.message shouldBe "Request validation has failed." body.cause shouldContain "Validation failed for CreateSecret" - secretRepository.getByProductIdAndName(productId, secret.name)?.mapToApi().shouldBeNull() + secretRepository.get(Entity.PRODUCT, productId, secret.name)?.mapToApi().shouldBeNull() val provider = SecretsProviderFactoryForTesting.instance() provider.readSecret(Path("product_${productId}_${secret.name}"))?.value shouldBe null @@ -555,7 +555,7 @@ class ProductsRouteIntegrationTest : AbstractIntegrationTest({ response shouldHaveStatus HttpStatusCode.OK response shouldHaveBody Secret(secret.name, updatedDescription) - secretRepository.getByProductIdAndName(productId, updateSecret.name.valueOrThrow)?.mapToApi() shouldBe + secretRepository.get(Entity.PRODUCT, productId, updateSecret.name.valueOrThrow)?.mapToApi() shouldBe Secret(secret.name, updatedDescription) } } @@ -588,7 +588,7 @@ class ProductsRouteIntegrationTest : AbstractIntegrationTest({ setBody(updateSecret) } shouldHaveStatus HttpStatusCode.InternalServerError - secretRepository.getByProductIdAndName(productId, secret.name) shouldBe secret + secretRepository.get(Entity.PRODUCT, productId, secret.name) shouldBe secret } } @@ -613,7 +613,7 @@ class ProductsRouteIntegrationTest : AbstractIntegrationTest({ superuserClient.delete("/api/v1/products/$productId/secrets/${secret.name}") shouldHaveStatus HttpStatusCode.NoContent - secretRepository.listForProduct(productId) shouldBe emptyList() + secretRepository.list(Entity.PRODUCT, productId) shouldBe emptyList() val provider = SecretsProviderFactoryForTesting.instance() provider.readSecret(Path(secret.path)) should beNull() @@ -655,7 +655,7 @@ class ProductsRouteIntegrationTest : AbstractIntegrationTest({ superuserClient.delete("/api/v1/products/$productId/secrets/${secret.name}") shouldHaveStatus HttpStatusCode.InternalServerError - secretRepository.getByProductIdAndName(productId, secret.name) shouldBe secret + secretRepository.get(Entity.PRODUCT, productId, secret.name) shouldBe secret } } diff --git a/core/src/test/kotlin/api/RepositoriesRouteIntegrationTest.kt b/core/src/test/kotlin/api/RepositoriesRouteIntegrationTest.kt index 4e76689be..2029dc583 100644 --- a/core/src/test/kotlin/api/RepositoriesRouteIntegrationTest.kt +++ b/core/src/test/kotlin/api/RepositoriesRouteIntegrationTest.kt @@ -654,7 +654,7 @@ class RepositoriesRouteIntegrationTest : AbstractIntegrationTest({ response shouldHaveStatus HttpStatusCode.Created response shouldHaveBody Secret(secret.name, secret.description) - secretRepository.getByRepositoryIdAndName(repositoryId, secret.name)?.mapToApi() shouldBe + secretRepository.get(Entity.REPOSITORY, repositoryId, secret.name)?.mapToApi() shouldBe Secret(secret.name, secret.description) val provider = SecretsProviderFactoryForTesting.instance() @@ -703,7 +703,7 @@ class RepositoriesRouteIntegrationTest : AbstractIntegrationTest({ body.message shouldBe "Request validation has failed." body.cause shouldContain "Validation failed for CreateSecret" - secretRepository.getByRepositoryIdAndName(repositoryId, secret.name)?.mapToApi().shouldBeNull() + secretRepository.get(Entity.REPOSITORY, repositoryId, secret.name)?.mapToApi().shouldBeNull() val provider = SecretsProviderFactoryForTesting.instance() provider.readSecret(Path("repository_${repositoryId}_${secret.name}"))?.value.shouldBeNull() @@ -727,7 +727,7 @@ class RepositoriesRouteIntegrationTest : AbstractIntegrationTest({ response shouldHaveStatus HttpStatusCode.OK response shouldHaveBody Secret(secret.name, updatedDescription) - secretRepository.getByRepositoryIdAndName(repositoryId, updateSecret.name.valueOrThrow) + secretRepository.get(Entity.REPOSITORY, repositoryId, updateSecret.name.valueOrThrow) ?.mapToApi() shouldBe Secret(secret.name, updatedDescription) } } @@ -760,7 +760,7 @@ class RepositoriesRouteIntegrationTest : AbstractIntegrationTest({ setBody(updateSecret) } shouldHaveStatus HttpStatusCode.InternalServerError - secretRepository.getByRepositoryIdAndName(repositoryId, secret.name) shouldBe secret + secretRepository.get(Entity.REPOSITORY, repositoryId, secret.name) shouldBe secret } } @@ -785,7 +785,7 @@ class RepositoriesRouteIntegrationTest : AbstractIntegrationTest({ superuserClient.delete("/api/v1/repositories/$repositoryId/secrets/${secret.name}") shouldHaveStatus HttpStatusCode.NoContent - secretRepository.listForRepository(repositoryId) shouldBe emptyList() + secretRepository.list(Entity.REPOSITORY, repositoryId) shouldBe emptyList() val provider = SecretsProviderFactoryForTesting.instance() provider.readSecret(Path(secret.path)) should beNull() @@ -800,7 +800,7 @@ class RepositoriesRouteIntegrationTest : AbstractIntegrationTest({ superuserClient.delete("/api/v1/repositories/$repositoryId/secrets/${secret.name}") shouldHaveStatus HttpStatusCode.InternalServerError - secretRepository.getByRepositoryIdAndName(repositoryId, secret.name) shouldBe secret + secretRepository.get(Entity.REPOSITORY, repositoryId, secret.name) shouldBe secret } } diff --git a/dao/src/main/kotlin/repositories/DaoSecretRepository.kt b/dao/src/main/kotlin/repositories/DaoSecretRepository.kt index 787842dfc..9fc11ec36 100644 --- a/dao/src/main/kotlin/repositories/DaoSecretRepository.kt +++ b/dao/src/main/kotlin/repositories/DaoSecretRepository.kt @@ -19,7 +19,6 @@ package org.eclipse.apoapsis.ortserver.dao.repositories -import org.eclipse.apoapsis.ortserver.dao.ConditionBuilder import org.eclipse.apoapsis.ortserver.dao.blockingQuery import org.eclipse.apoapsis.ortserver.dao.blockingQueryCatching import org.eclipse.apoapsis.ortserver.dao.entityQuery @@ -37,6 +36,7 @@ import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters import org.eclipse.apoapsis.ortserver.model.util.OptionalValue import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.and import org.slf4j.LoggerFactory @@ -56,126 +56,35 @@ class DaoSecretRepository(private val db: Database) : SecretRepository { }.mapToModel() } - override fun getByOrganizationIdAndName(organizationId: Long, name: String) = db.entityQuery { - findSecretByParentEntityId(organizationId, null, null, name)?.mapToModel() + override fun get(entity: Entity, id: Long, name: String) = db.entityQuery { + SecretDao.find(entity.condition(id) and (SecretsTable.name eq name)).firstOrNull()?.mapToModel() } - override fun getByProductIdAndName(productId: Long, name: String): Secret? = db.entityQuery { - findSecretByParentEntityId(null, productId, null, name)?.mapToModel() - } - - override fun getByRepositoryIdAndName(repositoryId: Long, name: String): Secret? = db.entityQuery { - findSecretByParentEntityId(null, null, repositoryId, name)?.mapToModel() - } - - override fun listForOrganization(organizationId: Long, parameters: ListQueryParameters) = db.blockingQueryCatching { - SecretDao.find { SecretsTable.organizationId eq organizationId } - .apply(SecretsTable, parameters) - .map { it.mapToModel() } + override fun list(entity: Entity, id: Long, parameters: ListQueryParameters) = db.blockingQueryCatching { + SecretDao.find(entity.condition(id)).apply(SecretsTable, parameters).map { it.mapToModel() } }.getOrElse { - logger.error("Cannot list secrets for organization id $organizationId.", it) + logger.error("Cannot list secrets for organization id $id.", it) throw it } - override fun listForProduct(productId: Long, parameters: ListQueryParameters): List = - db.blockingQueryCatching { - SecretDao.find { SecretsTable.productId eq productId } - .apply(SecretsTable, parameters) - .map { it.mapToModel() } - }.getOrElse { - logger.error("Cannot list secrets for product id $productId.", it) - throw it - } - - override fun listForRepository(repositoryId: Long, parameters: ListQueryParameters): List = - db.blockingQueryCatching { - SecretDao.find { SecretsTable.repositoryId eq repositoryId } - .apply(SecretsTable, parameters) - .map { it.mapToModel() } - }.getOrElse { - logger.error("Cannot list secrets for repository id $repositoryId.", it) - throw it - } - - override fun updateForOrganizationAndName( - organizationId: Long, - name: String, - description: OptionalValue - ): Secret = db.blockingQuery { - val secret = SecretDao.findSingle(byNameCondition(organizationId, null, null, name)) - - description.ifPresent { secret.description = it } - - secret.mapToModel() - } - - override fun updateForProductAndName( - productId: Long, - name: String, - description: OptionalValue - ): Secret = db.blockingQuery { - val secret = SecretDao.findSingle(byNameCondition(null, productId, null, name)) - - description.ifPresent { secret.description = it } - - secret.mapToModel() - } - - override fun updateForRepositoryAndName( - repositoryId: Long, - name: String, - description: OptionalValue - ): Secret = db.blockingQuery { - val secret = SecretDao.findSingle(byNameCondition(null, null, repositoryId, name)) - - description.ifPresent { secret.description = it } - - secret.mapToModel() - } - - override fun deleteForOrganizationAndName(organizationId: Long, name: String) = db.blockingQuery { - val secret = SecretDao.findSingle(byNameCondition(organizationId, null, null, name)) - - secret.delete() - } + override fun update(entity: Entity, id: Long, name: String, description: OptionalValue): Secret = + db.blockingQuery { + val secret = SecretDao.findSingle { entity.condition(id) and (SecretsTable.name eq name) } - override fun deleteForProductAndName(productId: Long, name: String) = db.blockingQuery { - val secret = SecretDao.findSingle(byNameCondition(null, productId, null, name)) + description.ifPresent { secret.description = it } - secret.delete() - } + secret.mapToModel() + } - override fun deleteForRepositoryAndName(repositoryId: Long, name: String) = db.blockingQuery { - val secret = SecretDao.findSingle(byNameCondition(null, null, repositoryId, name)) + override fun delete(entity: Entity, id: Long, name: String) = db.blockingQuery { + val secret = SecretDao.findSingle { entity.condition(id) and (SecretsTable.name eq name) } secret.delete() } } -/** - * Generate a WHERE condition to find a [Secret] entity with a specific [name] that is associated with one of the - * given [organizationId], [productId], or [repositoryId]. - */ -private fun byNameCondition( - organizationId: Long?, - productId: Long?, - repositoryId: Long?, - name: String -): ConditionBuilder = { - SecretsTable.organizationId eq organizationId and - (SecretsTable.productId eq productId) and - (SecretsTable.repositoryId eq repositoryId) and - (SecretsTable.name eq name) +private fun Entity.condition(id: Long) = when (this) { + Entity.ORGANIZATION -> SecretsTable.organizationId eq id + Entity.PRODUCT -> SecretsTable.productId eq id + Entity.REPOSITORY -> SecretsTable.repositoryId eq id } - -/** - * Find a [Secret] entity with a specific [name] that is associated with one of the given [organizationId], - * [productId], or [repositoryId]. - */ -private fun findSecretByParentEntityId( - organizationId: Long?, - productId: Long?, - repositoryId: Long?, - name: String -): SecretDao? = - SecretDao.find(byNameCondition(organizationId, productId, repositoryId, name)).firstOrNull() diff --git a/dao/src/test/kotlin/repositories/DaoSecretRepositoryTest.kt b/dao/src/test/kotlin/repositories/DaoSecretRepositoryTest.kt index e0c2fcfd8..c82ff082e 100644 --- a/dao/src/test/kotlin/repositories/DaoSecretRepositoryTest.kt +++ b/dao/src/test/kotlin/repositories/DaoSecretRepositoryTest.kt @@ -61,7 +61,7 @@ class DaoSecretRepositoryTest : StringSpec() { val name = "secret1" val secret = createSecret(name, Entity.ORGANIZATION, organizationId) - val dbEntry = secretRepository.getByOrganizationIdAndName(organizationId, name) + val dbEntry = secretRepository.get(Entity.ORGANIZATION, organizationId, name) dbEntry.shouldNotBeNull() dbEntry shouldBe secret @@ -71,13 +71,14 @@ class DaoSecretRepositoryTest : StringSpec() { val name = "secret2" val secret = createSecret(name, Entity.ORGANIZATION, organizationId) - secretRepository.updateForOrganizationAndName( + secretRepository.update( + Entity.ORGANIZATION, organizationId, name, description.asPresent() ) - val dbEntry = secretRepository.getByOrganizationIdAndName(organizationId, name) + val dbEntry = secretRepository.get(Entity.ORGANIZATION, organizationId, name) dbEntry.shouldNotBeNull() dbEntry shouldBe Secret( @@ -95,13 +96,14 @@ class DaoSecretRepositoryTest : StringSpec() { val name = "secret3" val secret = createSecret(name, Entity.PRODUCT, productId) - secretRepository.updateForProductAndName( + secretRepository.update( + Entity.PRODUCT, productId, name, description.asPresent() ) - val dbEntry = secretRepository.getByProductIdAndName(productId, name) + val dbEntry = secretRepository.get(Entity.PRODUCT, productId, name) dbEntry.shouldNotBeNull() dbEntry shouldBe Secret( @@ -119,13 +121,14 @@ class DaoSecretRepositoryTest : StringSpec() { val name = "secret2" val secret = createSecret(name, Entity.REPOSITORY, repositoryId) - secretRepository.updateForRepositoryAndName( + secretRepository.update( + Entity.REPOSITORY, repositoryId, name, description.asPresent() ) - val dbEntry = secretRepository.getByRepositoryIdAndName(repositoryId, name) + val dbEntry = secretRepository.get(Entity.REPOSITORY, repositoryId, name) dbEntry.shouldNotBeNull() dbEntry shouldBe Secret( @@ -143,9 +146,9 @@ class DaoSecretRepositoryTest : StringSpec() { val name = "secret3" createSecret(name, Entity.REPOSITORY, repositoryId) - secretRepository.deleteForRepositoryAndName(repositoryId, name) + secretRepository.delete(Entity.REPOSITORY, repositoryId, name) - secretRepository.listForRepository(repositoryId) shouldBe emptyList() + secretRepository.list(Entity.REPOSITORY, repositoryId) shouldBe emptyList() } "Reading all secrets of specific type" should { @@ -160,7 +163,7 @@ class DaoSecretRepositoryTest : StringSpec() { createSecret("productSecret1", Entity.PRODUCT, productId) createSecret("repositorySecret1", Entity.REPOSITORY, repositoryId) - secretRepository.listForOrganization(organizationId) should containExactlyInAnyOrder( + secretRepository.list(Entity.ORGANIZATION, organizationId) should containExactlyInAnyOrder( organizationSecret1, organizationSecret2 ) @@ -177,7 +180,7 @@ class DaoSecretRepositoryTest : StringSpec() { createSecret("organizationSecret1", Entity.ORGANIZATION, organizationId) createSecret("repositorySecret2", Entity.REPOSITORY, repositoryId) - secretRepository.listForProduct(productId) should containExactlyInAnyOrder( + secretRepository.list(Entity.PRODUCT, productId) should containExactlyInAnyOrder( productSecret1, productSecret2 ) @@ -194,7 +197,7 @@ class DaoSecretRepositoryTest : StringSpec() { createSecret("organizationSecret2", Entity.ORGANIZATION, organizationId) createSecret("productSecret2", Entity.ORGANIZATION, organizationId) - secretRepository.listForRepository(repositoryId) should containExactlyInAnyOrder( + secretRepository.list(Entity.REPOSITORY, repositoryId) should containExactlyInAnyOrder( repositorySecret1, repositorySecret2 ) diff --git a/model/src/commonMain/kotlin/repositories/SecretRepository.kt b/model/src/commonMain/kotlin/repositories/SecretRepository.kt index 4a590e474..6af4bee74 100644 --- a/model/src/commonMain/kotlin/repositories/SecretRepository.kt +++ b/model/src/commonMain/kotlin/repositories/SecretRepository.kt @@ -43,83 +43,22 @@ interface SecretRepository { fun create(path: String, name: String, description: String?, entity: Entity, id: Long): Secret /** - * Get a secret by [organizationId] and [name]. Returns null if the secret is not found. + * Get a secret by [id] and [name]. Return null if the secret is not found. */ - fun getByOrganizationIdAndName(organizationId: Long, name: String): Secret? + fun get(entity: Entity, id: Long, name: String): Secret? /** - * Get a secret by [productId] and [name]. Returns null if the secret is not found. + * List all secrets for an [entity] with [id] according to the given [parameters]. */ - fun getByProductIdAndName(productId: Long, name: String): Secret? + fun list(entity: Entity, id: Long, parameters: ListQueryParameters = ListQueryParameters.DEFAULT): List /** - * Get a secret by [repositoryId] and [name]. Returns null if the secret is not found. + * Update the [description] for the secret with [name] in [entity] with [id]. */ - fun getByRepositoryIdAndName(repositoryId: Long, name: String): Secret? + fun update(entity: Entity, id: Long, name: String, description: OptionalValue): Secret /** - * List all secrets for an [organization][organizationId] according to the given [parameters]. + * Delete the secret with [name] in [entity] with [id]. */ - fun listForOrganization( - organizationId: Long, - parameters: ListQueryParameters = ListQueryParameters.DEFAULT - ): List - - /** - * List all secrets for a [product][productId] according to the given [parameters]. - */ - fun listForProduct( - productId: Long, - parameters: ListQueryParameters = ListQueryParameters.DEFAULT - ): List - - /** - * List all secrets for a [repository][repositoryId] according to the given [parameters]. - */ - fun listForRepository( - repositoryId: Long, - parameters: ListQueryParameters = ListQueryParameters.DEFAULT - ): List - - /** - * Update a secret by [organizationId] and name with the [present][OptionalValue.Present] values. - */ - fun updateForOrganizationAndName( - organizationId: Long, - name: String, - description: OptionalValue - ): Secret - - /** - * Update a secret by [productId] and name with the [present][OptionalValue.Present] values. - */ - fun updateForProductAndName( - productId: Long, - name: String, - description: OptionalValue - ): Secret - - /** - * Update a secret by [repositoryId] and name with the [present][OptionalValue.Present] values. - */ - fun updateForRepositoryAndName( - repositoryId: Long, - name: String, - description: OptionalValue - ): Secret - - /** - * Delete a secret by [organizationId] and secret's [name]. - */ - fun deleteForOrganizationAndName(organizationId: Long, name: String) - - /** - * Delete a secret by [productId] and secret's [name]. - */ - fun deleteForProductAndName(productId: Long, name: String) - - /** - * Delete a secret by [repositoryId] and secret's [name]. - */ - fun deleteForRepositoryAndName(repositoryId: Long, name: String) + fun delete(entity: Entity, id: Long, name: String) } diff --git a/services/infrastructure/src/main/kotlin/InfrastructureServiceService.kt b/services/infrastructure/src/main/kotlin/InfrastructureServiceService.kt index 2ca659ddd..16e85a7ad 100644 --- a/services/infrastructure/src/main/kotlin/InfrastructureServiceService.kt +++ b/services/infrastructure/src/main/kotlin/InfrastructureServiceService.kt @@ -24,6 +24,7 @@ import org.eclipse.apoapsis.ortserver.model.CredentialsType import org.eclipse.apoapsis.ortserver.model.InfrastructureService import org.eclipse.apoapsis.ortserver.model.Secret import org.eclipse.apoapsis.ortserver.model.repositories.InfrastructureServiceRepository +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters import org.eclipse.apoapsis.ortserver.model.util.OptionalValue import org.eclipse.apoapsis.ortserver.model.util.asPresent @@ -131,7 +132,7 @@ class InfrastructureServiceService( * reference cannot be resolved. */ private suspend fun resolveOrganizationSecret(organizationId: Long, secretName: String): Secret = - secretService.getSecretByOrganizationIdAndName(organizationId, secretName) + secretService.getSecret(Entity.ORGANIZATION, organizationId, secretName) ?: throw InvalidSecretReferenceException(secretName) /** diff --git a/services/infrastructure/src/test/kotlin/InfrastructureServiceServiceTest.kt b/services/infrastructure/src/test/kotlin/InfrastructureServiceServiceTest.kt index 46015a1bc..64ddc1786 100644 --- a/services/infrastructure/src/test/kotlin/InfrastructureServiceServiceTest.kt +++ b/services/infrastructure/src/test/kotlin/InfrastructureServiceServiceTest.kt @@ -42,6 +42,7 @@ import org.eclipse.apoapsis.ortserver.model.CredentialsType import org.eclipse.apoapsis.ortserver.model.InfrastructureService import org.eclipse.apoapsis.ortserver.model.Secret import org.eclipse.apoapsis.ortserver.model.repositories.InfrastructureServiceRepository +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters import org.eclipse.apoapsis.ortserver.model.util.OptionalValue import org.eclipse.apoapsis.ortserver.model.util.asPresent @@ -99,7 +100,7 @@ class InfrastructureServiceServiceTest : WordSpec({ testWithHelper(verifyTx = false) { mockOrganizationSecret(USERNAME_SECRET) coEvery { - secretService.getSecretByOrganizationIdAndName(ORGANIZATION_ID, PASSWORD_SECRET) + secretService.getSecret(Entity.ORGANIZATION, ORGANIZATION_ID, PASSWORD_SECRET) } returns null val exception = shouldThrow { @@ -180,7 +181,7 @@ class InfrastructureServiceServiceTest : WordSpec({ "throw an exception if a secret reference cannot be resolved" { testWithHelper(verifyTx = false) { - coEvery { secretService.getSecretByOrganizationIdAndName(any(), any()) } returns null + coEvery { secretService.getSecret(Entity.ORGANIZATION, any(), any()) } returns null shouldThrow { service.updateForOrganization( @@ -262,7 +263,7 @@ private class TestHelper( fun mockOrganizationSecret(name: String): Secret { val secret = mockk() coEvery { - secretService.getSecretByOrganizationIdAndName(ORGANIZATION_ID, name) + secretService.getSecret(Entity.ORGANIZATION, ORGANIZATION_ID, name) } returns secret return secret diff --git a/services/secret/src/main/kotlin/SecretService.kt b/services/secret/src/main/kotlin/SecretService.kt index b1f4b412d..4ed345ebf 100644 --- a/services/secret/src/main/kotlin/SecretService.kt +++ b/services/secret/src/main/kotlin/SecretService.kt @@ -61,10 +61,10 @@ class SecretService( } /** - * Delete a secret by [organizationId] and [name]. + * Delete a secret by [entity], [id] and [name]. */ - suspend fun deleteSecretByOrganizationAndName(organizationId: Long, name: String) = db.dbQuery { - val secret = secretRepository.getByOrganizationIdAndName(organizationId, name)?.also { + suspend fun deleteSecret(entity: SecretRepository.Entity, id: Long, name: String) = db.dbQuery { + val secret = secretRepository.get(entity, id, name)?.also { val services = infrastructureServiceRepository.listForSecret(it.id).map { service -> service.name } if (services.isNotEmpty()) { @@ -72,135 +72,42 @@ class SecretService( } } - secretRepository.deleteForOrganizationAndName(organizationId, name) + secretRepository.delete(entity, id, name) secret?.deleteValue() } /** - * Delete a secret by [productId] and [name]. + * Get a secret by [entity], [id] and [name]. Return null if the secret is not found. */ - suspend fun deleteSecretByProductAndName(productId: Long, name: String) = db.dbQuery { - val secret = secretRepository.getByProductIdAndName(productId, name)?.also { - val services = infrastructureServiceRepository.listForSecret(it.id).map { service -> service.name } - - if (services.isNotEmpty()) { - throw ReferencedEntityException("Could not delete secret '${it.name}' due to usage in $services.") - } - } - - secretRepository.deleteForProductAndName(productId, name) - secret?.deleteValue() - } - - /** - * Delete a secret by [repositoryId] and [name]. - */ - suspend fun deleteSecretByRepositoryAndName(repositoryId: Long, name: String) = db.dbQuery { - val secret = secretRepository.getByRepositoryIdAndName(repositoryId, name) - secretRepository.deleteForRepositoryAndName(repositoryId, name) - secret?.deleteValue() - } - - /** - * Get a secret by [organizationId] and [name]. Returns null if the secret is not found. - */ - suspend fun getSecretByOrganizationIdAndName(organizationId: Long, name: String): Secret? = db.dbQuery { - secretRepository.getByOrganizationIdAndName(organizationId, name) + suspend fun getSecret(entity: SecretRepository.Entity, id: Long, name: String): Secret? = db.dbQuery { + secretRepository.get(entity, id, name) } /** - * Get a secret by [productId] and [name]. Returns null if the secret is not found. + * List all secrets for a specific [entity] with [id] and according to the given [parameters]. */ - suspend fun getSecretByProductIdAndName(productId: Long, name: String): Secret? = db.dbQuery { - secretRepository.getByProductIdAndName(productId, name) - } - - /** - * Get a secret by [repositoryId] and [name]. Returns null if the secret is not found. - */ - suspend fun getSecretByRepositoryIdAndName(repositoryId: Long, name: String): Secret? = db.dbQuery { - secretRepository.getByRepositoryIdAndName(repositoryId, name) - } - - /** - * List all secrets for a specific [organization][organizationId] and according to the given [parameters]. - */ - suspend fun listForOrganization( - organizationId: Long, - parameters: ListQueryParameters = ListQueryParameters.DEFAULT - ): List = db.dbQuery { - secretRepository.listForOrganization(organizationId, parameters) - } - - /** - * List all secrets for a specific [product][productId] and according to the given [parameters]. - */ - suspend fun listForProduct( - productId: Long, - parameters: ListQueryParameters = ListQueryParameters.DEFAULT - ): List = db.dbQuery { - secretRepository.listForProduct(productId, parameters) - } - - /** - * List all secrets for a specific [repository][repositoryId] and according to the given [parameters]. - */ - suspend fun listForRepository( - repositoryId: Long, + suspend fun listSecrets( + entity: SecretRepository.Entity, + id: Long, parameters: ListQueryParameters = ListQueryParameters.DEFAULT ): List = db.dbQuery { - secretRepository.listForRepository(repositoryId, parameters) - } - - /** - * Update a secret by [organizationId] and [name] with the [present][OptionalValue.Present] values. - */ - suspend fun updateSecretByOrganizationAndName( - organizationId: Long, - name: String, - value: OptionalValue, - description: OptionalValue - ): Secret = db.dbQuery { - val secret = secretRepository.updateForOrganizationAndName(organizationId, name, description) - - value.ifPresent { - secretRepository.getByOrganizationIdAndName(organizationId, name)?.updateValue(it) - } - - secret - } - - /** - * Update a secret by [productId] and [name] with the [present][OptionalValue.Present] values. - */ - suspend fun updateSecretByProductAndName( - productId: Long, - name: String, - value: OptionalValue, - description: OptionalValue - ): Secret = db.dbQuery { - val secret = secretRepository.updateForProductAndName(productId, name, description) - - value.ifPresent { - secretRepository.getByProductIdAndName(productId, name)?.updateValue(it) - } - - secret + secretRepository.list(entity, id, parameters) } /** - * Update a secret by [repositoryId] and [name][name] with the [present][OptionalValue.Present] values. + * Update the [named][name] secret in [entity] with [id] to the optional [value] and [description]. */ - suspend fun updateSecretByRepositoryAndName( - repositoryId: Long, + suspend fun updateSecret( + entity: SecretRepository.Entity, + id: Long, name: String, value: OptionalValue, description: OptionalValue ): Secret = db.dbQuery { - val secret = secretRepository.updateForRepositoryAndName(repositoryId, name, description) + val secret = secretRepository.update(entity, id, name, description) value.ifPresent { - secretRepository.getByRepositoryIdAndName(repositoryId, name)?.updateValue(it) + secretRepository.get(entity, id, name)?.updateValue(it) } secret diff --git a/workers/analyzer/src/test/kotlin/AnalyzerEndpointTest.kt b/workers/analyzer/src/test/kotlin/AnalyzerEndpointTest.kt index b17a36a4e..c22434c91 100644 --- a/workers/analyzer/src/test/kotlin/AnalyzerEndpointTest.kt +++ b/workers/analyzer/src/test/kotlin/AnalyzerEndpointTest.kt @@ -63,6 +63,7 @@ import org.eclipse.apoapsis.ortserver.model.orchestrator.AnalyzerWorkerError import org.eclipse.apoapsis.ortserver.model.orchestrator.AnalyzerWorkerResult import org.eclipse.apoapsis.ortserver.model.repositories.InfrastructureServiceRepository import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.transport.AnalyzerEndpoint import org.eclipse.apoapsis.ortserver.transport.Message import org.eclipse.apoapsis.ortserver.transport.MessageHeader @@ -303,7 +304,7 @@ class AnalyzerEndpointTest : KoinTest, StringSpec() { val usernameSecret = Secret(20230627040646L, "p1", "repositoryUsername", null, null, null, repository) val passwordSecret = Secret(20230627070543L, "p2", "repositoryPassword", null, null, null, repository) declareMock { - every { listForRepository(repository.id) } returns listOf(usernameSecret, passwordSecret) + every { list(Entity.REPOSITORY, repository.id) } returns listOf(usernameSecret, passwordSecret) } declareMock { diff --git a/workers/common/src/main/kotlin/common/env/config/EnvironmentConfigLoader.kt b/workers/common/src/main/kotlin/common/env/config/EnvironmentConfigLoader.kt index 55e5edfdc..34cd12469 100644 --- a/workers/common/src/main/kotlin/common/env/config/EnvironmentConfigLoader.kt +++ b/workers/common/src/main/kotlin/common/env/config/EnvironmentConfigLoader.kt @@ -29,6 +29,7 @@ import org.eclipse.apoapsis.ortserver.model.InfrastructureServiceDeclaration import org.eclipse.apoapsis.ortserver.model.Secret import org.eclipse.apoapsis.ortserver.model.repositories.InfrastructureServiceRepository import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.utils.yaml.YamlReader import org.eclipse.apoapsis.ortserver.workers.common.env.definition.EnvironmentServiceDefinition import org.eclipse.apoapsis.ortserver.workers.common.env.definition.EnvironmentVariableDefinition @@ -195,9 +196,9 @@ class EnvironmentConfigLoader( } } - fetchSecrets { secretRepository.listForRepository(hierarchy.repository.id) } - fetchSecrets { secretRepository.listForProduct(hierarchy.product.id) } - fetchSecrets { secretRepository.listForOrganization(hierarchy.organization.id) } + fetchSecrets { secretRepository.list(Entity.ORGANIZATION, hierarchy.repository.id) } + fetchSecrets { secretRepository.list(Entity.PRODUCT, hierarchy.product.id) } + fetchSecrets { secretRepository.list(Entity.REPOSITORY, hierarchy.organization.id) } if (allSecretsNames.isNotEmpty()) { val message = "Invalid secret names. The following names cannot be resolved: $allSecretsNames" diff --git a/workers/common/src/test/kotlin/common/env/config/EnvironmentConfigLoaderTest.kt b/workers/common/src/test/kotlin/common/env/config/EnvironmentConfigLoaderTest.kt index 10cc71c32..53402a6fc 100644 --- a/workers/common/src/test/kotlin/common/env/config/EnvironmentConfigLoaderTest.kt +++ b/workers/common/src/test/kotlin/common/env/config/EnvironmentConfigLoaderTest.kt @@ -49,6 +49,7 @@ import org.eclipse.apoapsis.ortserver.model.RepositoryType import org.eclipse.apoapsis.ortserver.model.Secret import org.eclipse.apoapsis.ortserver.model.repositories.InfrastructureServiceRepository import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository +import org.eclipse.apoapsis.ortserver.model.repositories.SecretRepository.Entity import org.eclipse.apoapsis.ortserver.workers.common.env.config.EnvironmentConfigException import org.eclipse.apoapsis.ortserver.workers.common.env.config.EnvironmentConfigLoader import org.eclipse.apoapsis.ortserver.workers.common.env.config.EnvironmentDefinitionFactory @@ -133,8 +134,8 @@ class EnvironmentConfigLoaderTest : StringSpec() { loadConfig(".ort.env.simple.yml", helper) verify(exactly = 0) { - helper.secretRepository.listForProduct(any(), any()) - helper.secretRepository.listForOrganization(any(), any()) + helper.secretRepository.list(Entity.PRODUCT, any(), any()) + helper.secretRepository.list(Entity.ORGANIZATION, any(), any()) } } @@ -467,13 +468,13 @@ private class TestHelper( */ private fun initSecretRepository() { every { - secretRepository.listForRepository(repository.id) + secretRepository.list(Entity.REPOSITORY, repository.id) } returns secrets.filter { it.repository != null } every { - secretRepository.listForProduct(product.id) + secretRepository.list(Entity.PRODUCT, product.id) } returns secrets.filter { it.product != null } every { - secretRepository.listForOrganization(organization.id) + secretRepository.list(Entity.ORGANIZATION, organization.id) } returns secrets.filter { it.organization != null } }