From 9260cb5079113e6a2120a558f2d9e6b956c2d0d8 Mon Sep 17 00:00:00 2001 From: Luis Pollo <1323478+luispollo@users.noreply.github.com> Date: Fri, 7 Aug 2020 17:42:10 -0700 Subject: [PATCH] feat(plugins): Introduce KeelServiceSdk for external plugins (#1416) * fix(resources): Fix discovery of supported resource kinds * feat(plugins): Introduce KeelServiceSdk for plugins * fix(pr): Add KeelReadOnlyRepository for plugin usage --- .../api/persistence/KeelReadOnlyRepository.kt | 62 +++++++++++++++++++ .../keel/api/plugins/KeelServiceSdk.kt | 12 ++++ .../keel/persistence/KeelRepository.kt | 52 +--------------- .../config/KeelServiceSdkConfiguration.java | 15 +++++ .../keel/plugins/KeelServiceSdkFactory.kt | 43 +++++++++++++ .../keel/plugins/KeelServiceSdkImpl.kt | 10 +++ 6 files changed, 144 insertions(+), 50 deletions(-) create mode 100644 keel-api/src/main/kotlin/com/netflix/spinnaker/keel/api/persistence/KeelReadOnlyRepository.kt create mode 100644 keel-api/src/main/kotlin/com/netflix/spinnaker/keel/api/plugins/KeelServiceSdk.kt create mode 100644 keel-web/src/main/java/com/netflix/spinnaker/config/KeelServiceSdkConfiguration.java create mode 100644 keel-web/src/main/kotlin/com/netflix/spinnaker/keel/plugins/KeelServiceSdkFactory.kt create mode 100644 keel-web/src/main/kotlin/com/netflix/spinnaker/keel/plugins/KeelServiceSdkImpl.kt diff --git a/keel-api/src/main/kotlin/com/netflix/spinnaker/keel/api/persistence/KeelReadOnlyRepository.kt b/keel-api/src/main/kotlin/com/netflix/spinnaker/keel/api/persistence/KeelReadOnlyRepository.kt new file mode 100644 index 0000000000..aad2f570b0 --- /dev/null +++ b/keel-api/src/main/kotlin/com/netflix/spinnaker/keel/api/persistence/KeelReadOnlyRepository.kt @@ -0,0 +1,62 @@ +package com.netflix.spinnaker.keel.api.persistence + +import com.netflix.spinnaker.keel.api.DeliveryConfig +import com.netflix.spinnaker.keel.api.Environment +import com.netflix.spinnaker.keel.api.Resource +import com.netflix.spinnaker.keel.api.ResourceSpec +import com.netflix.spinnaker.keel.api.artifacts.ArtifactType +import com.netflix.spinnaker.keel.api.artifacts.DeliveryArtifact +import com.netflix.spinnaker.keel.api.constraints.ConstraintState + +/** + * A read-only repository for interacting with delivery configs, artifacts, and resources. + */ +interface KeelReadOnlyRepository { + fun getDeliveryConfig(name: String): DeliveryConfig + + fun environmentFor(resourceId: String): Environment + + fun deliveryConfigFor(resourceId: String): DeliveryConfig + + fun getDeliveryConfigForApplication(application: String): DeliveryConfig + + fun getConstraintState(deliveryConfigName: String, environmentName: String, artifactVersion: String, type: String, artifactReference: String?): ConstraintState? + + fun constraintStateFor(application: String): List + + fun constraintStateFor(deliveryConfigName: String, environmentName: String, limit: Int): List + + fun constraintStateFor(deliveryConfigName: String, environmentName: String, artifactVersion: String): List + + fun pendingConstraintVersionsFor(deliveryConfigName: String, environmentName: String): List + + fun getQueuedConstraintApprovals(deliveryConfigName: String, environmentName: String, artifactReference: String?): Set + + fun getResource(id: String): Resource + + fun hasManagedResources(application: String): Boolean + + fun getResourceIdsByApplication(application: String): List + + fun getResourcesByApplication(application: String): List> + + fun getArtifact(name: String, type: ArtifactType, deliveryConfigName: String): List + + fun getArtifact(name: String, type: ArtifactType, reference: String, deliveryConfigName: String): DeliveryArtifact + + fun getArtifact(deliveryConfigName: String, reference: String): DeliveryArtifact + + fun isRegistered(name: String, type: ArtifactType): Boolean + + fun artifactVersions(artifact: DeliveryArtifact): List + + fun artifactVersions(name: String, type: ArtifactType): List + + fun latestVersionApprovedIn(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, targetEnvironment: String): String? + + fun isApprovedFor(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, version: String, targetEnvironment: String): Boolean + + fun wasSuccessfullyDeployedTo(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, version: String, targetEnvironment: String): Boolean + + fun isCurrentlyDeployedTo(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, version: String, targetEnvironment: String): Boolean +} diff --git a/keel-api/src/main/kotlin/com/netflix/spinnaker/keel/api/plugins/KeelServiceSdk.kt b/keel-api/src/main/kotlin/com/netflix/spinnaker/keel/api/plugins/KeelServiceSdk.kt new file mode 100644 index 0000000000..ebc879c706 --- /dev/null +++ b/keel-api/src/main/kotlin/com/netflix/spinnaker/keel/api/plugins/KeelServiceSdk.kt @@ -0,0 +1,12 @@ +package com.netflix.spinnaker.keel.api.plugins + +import com.netflix.spinnaker.keel.api.actuation.TaskLauncher +import com.netflix.spinnaker.keel.api.persistence.KeelReadOnlyRepository + +/** + * A simple SDK that can be consumed by external plugins to access core Keel functionality. + */ +interface KeelServiceSdk { + val repository: KeelReadOnlyRepository + val taskLauncher: TaskLauncher +} diff --git a/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/persistence/KeelRepository.kt b/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/persistence/KeelRepository.kt index 59fe95eed9..14afcd05b1 100644 --- a/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/persistence/KeelRepository.kt +++ b/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/persistence/KeelRepository.kt @@ -1,7 +1,6 @@ package com.netflix.spinnaker.keel.persistence import com.netflix.spinnaker.keel.api.DeliveryConfig -import com.netflix.spinnaker.keel.api.Environment import com.netflix.spinnaker.keel.api.Resource import com.netflix.spinnaker.keel.api.ResourceSpec import com.netflix.spinnaker.keel.api.artifacts.ArtifactStatus @@ -9,6 +8,7 @@ import com.netflix.spinnaker.keel.api.artifacts.ArtifactType import com.netflix.spinnaker.keel.api.artifacts.DeliveryArtifact import com.netflix.spinnaker.keel.api.constraints.ConstraintState import com.netflix.spinnaker.keel.api.id +import com.netflix.spinnaker.keel.api.persistence.KeelReadOnlyRepository import com.netflix.spinnaker.keel.core.api.ApplicationSummary import com.netflix.spinnaker.keel.core.api.ArtifactSummaryInEnvironment import com.netflix.spinnaker.keel.core.api.EnvironmentArtifactPin @@ -37,7 +37,7 @@ import org.springframework.transaction.annotation.Transactional /** * A repository for interacting with delivery configs, artifacts, and resources. */ -interface KeelRepository { +interface KeelRepository : KeelReadOnlyRepository { val clock: Clock val publisher: ApplicationEventPublisher val log: Logger @@ -94,36 +94,16 @@ interface KeelRepository { // START Delivery config methods fun storeDeliveryConfig(deliveryConfig: DeliveryConfig) - fun getDeliveryConfig(name: String): DeliveryConfig - - fun environmentFor(resourceId: String): Environment - - fun deliveryConfigFor(resourceId: String): DeliveryConfig - - fun getDeliveryConfigForApplication(application: String): DeliveryConfig - fun deleteResourceFromEnv(deliveryConfigName: String, environmentName: String, resourceId: String) fun deleteEnvironment(deliveryConfigName: String, environmentName: String) fun storeConstraintState(state: ConstraintState) - fun getConstraintState(deliveryConfigName: String, environmentName: String, artifactVersion: String, type: String, artifactReference: String?): ConstraintState? - fun getConstraintStateById(uid: UID): ConstraintState? fun deleteConstraintState(deliveryConfigName: String, environmentName: String, type: String) - fun constraintStateFor(application: String): List - - fun constraintStateFor(deliveryConfigName: String, environmentName: String, limit: Int): List - - fun constraintStateFor(deliveryConfigName: String, environmentName: String, artifactVersion: String): List - - fun pendingConstraintVersionsFor(deliveryConfigName: String, environmentName: String): List - - fun getQueuedConstraintApprovals(deliveryConfigName: String, environmentName: String, artifactReference: String?): Set - fun queueAllConstraintsApproved(deliveryConfigName: String, environmentName: String, artifactVersion: String, artifactReference: String?) fun deleteQueuedConstraintApproval(deliveryConfigName: String, environmentName: String, artifactVersion: String, artifactReference: String?) @@ -136,14 +116,6 @@ interface KeelRepository { // START ResourceRepository methods fun allResources(callback: (ResourceHeader) -> Unit) - fun getResource(id: String): Resource - - fun hasManagedResources(application: String): Boolean - - fun getResourceIdsByApplication(application: String): List - - fun getResourcesByApplication(application: String): List> - fun storeResource(resource: Resource<*>) fun deleteResource(id: String) @@ -168,14 +140,6 @@ interface KeelRepository { // START ArtifactRepository methods fun register(artifact: DeliveryArtifact) - fun getArtifact(name: String, type: ArtifactType, deliveryConfigName: String): List - - fun getArtifact(name: String, type: ArtifactType, reference: String, deliveryConfigName: String): DeliveryArtifact - - fun getArtifact(deliveryConfigName: String, reference: String): DeliveryArtifact - - fun isRegistered(name: String, type: ArtifactType): Boolean - fun getAllArtifacts(type: ArtifactType? = null): List fun storeArtifact(name: String, type: ArtifactType, version: String, status: ArtifactStatus?): Boolean @@ -184,22 +148,10 @@ interface KeelRepository { fun deleteArtifact(artifact: DeliveryArtifact) - fun artifactVersions(artifact: DeliveryArtifact): List - - fun artifactVersions(name: String, type: ArtifactType): List - - fun latestVersionApprovedIn(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, targetEnvironment: String): String? - fun approveVersionFor(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, version: String, targetEnvironment: String): Boolean - fun isApprovedFor(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, version: String, targetEnvironment: String): Boolean - fun markAsDeployingTo(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, version: String, targetEnvironment: String) - fun wasSuccessfullyDeployedTo(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, version: String, targetEnvironment: String): Boolean - - fun isCurrentlyDeployedTo(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, version: String, targetEnvironment: String): Boolean - fun markAsSuccessfullyDeployedTo(deliveryConfig: DeliveryConfig, artifact: DeliveryArtifact, version: String, targetEnvironment: String) fun getEnvironmentSummaries(deliveryConfig: DeliveryConfig): List diff --git a/keel-web/src/main/java/com/netflix/spinnaker/config/KeelServiceSdkConfiguration.java b/keel-web/src/main/java/com/netflix/spinnaker/config/KeelServiceSdkConfiguration.java new file mode 100644 index 0000000000..db4df27e01 --- /dev/null +++ b/keel-web/src/main/java/com/netflix/spinnaker/config/KeelServiceSdkConfiguration.java @@ -0,0 +1,15 @@ +package com.netflix.spinnaker.config; + +import com.netflix.spinnaker.keel.plugins.KeelServiceSdkFactory; +import com.netflix.spinnaker.kork.plugins.sdk.SdkFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +class KeelServiceSdkConfiguration { + @Bean + public static SdkFactory serviceSdkFactory(ApplicationContext applicationContext) { + return new KeelServiceSdkFactory(applicationContext); + } +} diff --git a/keel-web/src/main/kotlin/com/netflix/spinnaker/keel/plugins/KeelServiceSdkFactory.kt b/keel-web/src/main/kotlin/com/netflix/spinnaker/keel/plugins/KeelServiceSdkFactory.kt new file mode 100644 index 0000000000..ba5d7d9141 --- /dev/null +++ b/keel-web/src/main/kotlin/com/netflix/spinnaker/keel/plugins/KeelServiceSdkFactory.kt @@ -0,0 +1,43 @@ +package com.netflix.spinnaker.keel.plugins + +import com.netflix.spinnaker.keel.api.actuation.TaskLauncher +import com.netflix.spinnaker.keel.persistence.KeelRepository +import com.netflix.spinnaker.kork.exceptions.SystemException +import com.netflix.spinnaker.kork.plugins.sdk.SdkFactory +import org.pf4j.PluginWrapper +import org.slf4j.LoggerFactory +import org.springframework.context.ApplicationContext + +/** + * Creates [KeelServiceSdk] objects that can be consumed by external plugins. + */ +class KeelServiceSdkFactory( + private val applicationContext: ApplicationContext +) : SdkFactory { + + private val log by lazy { LoggerFactory.getLogger(javaClass) } + + private val keelServiceSdk by lazy { + val repository = getFirstBeanOfType(KeelRepository::class.java) + val taskLauncher = getFirstBeanOfType(TaskLauncher::class.java) + KeelServiceSdkImpl(repository, taskLauncher) + } + + override fun create(extensionClass: Class<*>, pluginWrapper: PluginWrapper?): Any = + keelServiceSdk + + private inline fun getFirstBeanOfType(clazz: Class): T = + applicationContext.getBeansOfType(clazz) + .let { + if (it.isEmpty()) { + throw SystemException("Failed to locate bean of type ${T::class.java.name} in application context") + } else { + val first = it.entries.first() + if (it.size > 1) { + val options = it.keys.joinToString() + log.warn("Found more than one bean of type ${T::class.java.name} ($options), selecting '${first.key}'") + } + first.value + } + } +} diff --git a/keel-web/src/main/kotlin/com/netflix/spinnaker/keel/plugins/KeelServiceSdkImpl.kt b/keel-web/src/main/kotlin/com/netflix/spinnaker/keel/plugins/KeelServiceSdkImpl.kt new file mode 100644 index 0000000000..993e2fe524 --- /dev/null +++ b/keel-web/src/main/kotlin/com/netflix/spinnaker/keel/plugins/KeelServiceSdkImpl.kt @@ -0,0 +1,10 @@ +package com.netflix.spinnaker.keel.plugins + +import com.netflix.spinnaker.keel.api.actuation.TaskLauncher +import com.netflix.spinnaker.keel.api.persistence.KeelReadOnlyRepository +import com.netflix.spinnaker.keel.api.plugins.KeelServiceSdk + +class KeelServiceSdkImpl( + override val repository: KeelReadOnlyRepository, + override val taskLauncher: TaskLauncher +) : KeelServiceSdk