Skip to content

Commit 568a5c0

Browse files
authored
feat(api): Standardize mechanism for ResourceHandlers to notify artifact deployments (#1466)
* refactor(api): Move artifact deployment events to keel-api * feat(api): Add artifact deployment notification methods to ResourceHandler * fix(pr): Use EventPublisher where needed
1 parent 09bf4b4 commit 568a5c0

File tree

26 files changed

+133
-138
lines changed

26 files changed

+133
-138
lines changed

keel-api/src/main/kotlin/com/netflix/spinnaker/keel/api/events/artifacts.kt

+16
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,19 @@ data class ArtifactRegisteredEvent(
2525
data class ArtifactSyncEvent(
2626
val controllerTriggered: Boolean = false
2727
)
28+
29+
/**
30+
* An event fired to signal that an artifact version is deploying to a resource.
31+
*/
32+
data class ArtifactVersionDeploying(
33+
val resourceId: String,
34+
val artifactVersion: String
35+
)
36+
37+
/**
38+
* An event fired to signal that an artifact version was successfully deployed to a resource.
39+
*/
40+
data class ArtifactVersionDeployed(
41+
val resourceId: String,
42+
val artifactVersion: String
43+
)

keel-api/src/main/kotlin/com/netflix/spinnaker/keel/api/plugins/ResourceHandler.kt

+36
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import com.netflix.spinnaker.keel.api.ResourceKind
77
import com.netflix.spinnaker.keel.api.ResourceSpec
88
import com.netflix.spinnaker.keel.api.actuation.Task
99
import com.netflix.spinnaker.keel.api.artifacts.DeliveryArtifact
10+
import com.netflix.spinnaker.keel.api.artifacts.PublishedArtifact
11+
import com.netflix.spinnaker.keel.api.events.ArtifactPublishedEvent
12+
import com.netflix.spinnaker.keel.api.events.ArtifactVersionDeployed
13+
import com.netflix.spinnaker.keel.api.events.ArtifactVersionDeploying
14+
import com.netflix.spinnaker.keel.api.support.EventPublisher
1015
import com.netflix.spinnaker.kork.exceptions.SystemException
1116
import com.netflix.spinnaker.kork.plugins.api.internal.SpinnakerExtensionPoint
1217

@@ -25,6 +30,11 @@ import com.netflix.spinnaker.kork.plugins.api.internal.SpinnakerExtensionPoint
2530
* 3. Act to resolve the drift when requested by keel. This is done via the [create], [update] and [delete]
2631
* methods, which receive a [ResourceDiff] as a parameter.
2732
*
33+
* a. A [ResourceHandler] whose [Resource] type supports [DeliveryArtifact] deployments is additionally
34+
* responsible for notifying core Keel when new versions of those artifacts transition to a deploying
35+
* state and to a successfully deployed state via the [notifyArtifactDeploying] and [notifyArtifactDeployed]
36+
* methods, respectively.
37+
*
2838
* @param S the spec type.
2939
* @param R the resolved model type.
3040
*/
@@ -38,6 +48,12 @@ interface ResourceHandler<S : ResourceSpec, R : Any> : SpinnakerExtensionPoint {
3848
*/
3949
val supportedKind: SupportedKind<S>
4050

51+
/**
52+
* Optional [EventPublisher] which can be used to signal other modules of relevant events.
53+
*/
54+
val eventPublisher: EventPublisher?
55+
get() = null
56+
4157
/**
4258
* Resolve and convert the resource spec into the type that represents the diff-able desired
4359
* state.
@@ -137,6 +153,26 @@ interface ResourceHandler<S : ResourceSpec, R : Any> : SpinnakerExtensionPoint {
137153
*/
138154
@JvmDefault
139155
suspend fun actuationInProgress(resource: Resource<S>): Boolean = false
156+
157+
/**
158+
* Notifies core Keel that a new artifact version is being deployed to the specified resource.
159+
*
160+
* The default implementation achieves this by publishing an [ArtifactVersionDeploying] event via the
161+
* [EventPublisher], and should *not* be overridden by plugin implementations.
162+
*/
163+
@JvmDefault
164+
fun notifyArtifactDeploying(resource: Resource<S>, artifactVersion: String) =
165+
eventPublisher?.publishEvent(ArtifactVersionDeploying(resource.id, artifactVersion))
166+
167+
/**
168+
* Notifies core Keel that a new artifact version was successfully deployed to the specified resource.
169+
*
170+
* The default implementation achieves this by publishing an [ArtifactVersionDeployed] event via the
171+
* [EventPublisher], and should *not* be overridden by plugin implementations.
172+
*/
173+
@JvmDefault
174+
fun notifyArtifactDeployed(resource: Resource<S>, artifactVersion: String) =
175+
eventPublisher?.publishEvent(ArtifactVersionDeployed(resource.id, artifactVersion))
140176
}
141177

142178
/**

keel-artifact/src/main/kotlin/com/netflix/spinnaker/keel/artifacts/ArtifactDeployedListener.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.netflix.spinnaker.keel.artifacts
22

3-
import com.netflix.spinnaker.keel.events.ArtifactVersionDeployed
3+
import com.netflix.spinnaker.keel.api.events.ArtifactVersionDeployed
44
import com.netflix.spinnaker.keel.persistence.KeelRepository
55
import kotlinx.coroutines.runBlocking
66
import org.slf4j.LoggerFactory

keel-artifact/src/main/kotlin/com/netflix/spinnaker/keel/artifacts/ArtifactDeployingListener.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.netflix.spinnaker.keel.artifacts
22

3-
import com.netflix.spinnaker.keel.events.ArtifactVersionDeploying
3+
import com.netflix.spinnaker.keel.api.events.ArtifactVersionDeploying
44
import com.netflix.spinnaker.keel.persistence.KeelRepository
55
import kotlinx.coroutines.runBlocking
66
import org.slf4j.LoggerFactory

keel-artifact/src/test/kotlin/com/netflix/spinnaker/keel/artifacts/ArtifactDeployedListenerTests.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.netflix.spinnaker.keel.artifacts
22

33
import com.netflix.spinnaker.keel.api.Resource
4-
import com.netflix.spinnaker.keel.events.ArtifactVersionDeployed
4+
import com.netflix.spinnaker.keel.api.events.ArtifactVersionDeployed
55
import com.netflix.spinnaker.keel.persistence.KeelRepository
66
import com.netflix.spinnaker.keel.test.DummyResourceSpec
77
import com.netflix.spinnaker.keel.test.deliveryConfig

keel-artifact/src/test/kotlin/com/netflix/spinnaker/keel/artifacts/ArtifactDeployingListenerTests.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.netflix.spinnaker.keel.artifacts
22

33
import com.netflix.spinnaker.keel.api.Resource
4-
import com.netflix.spinnaker.keel.events.ArtifactVersionDeploying
4+
import com.netflix.spinnaker.keel.api.events.ArtifactVersionDeploying
55
import com.netflix.spinnaker.keel.persistence.KeelRepository
66
import com.netflix.spinnaker.keel.test.DummyResourceSpec
77
import com.netflix.spinnaker.keel.test.deliveryConfig

keel-core/src/main/kotlin/com/netflix/spinnaker/keel/actuation/ResourceActuator.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ class ResourceActuator(
200200
}
201201
}
202202
} catch (e: ResourceCurrentlyUnresolvable) {
203-
log.warn("Resource check for {} failed (hopefully temporarily) due to {}", id, e.message, e)
203+
log.warn("Resource check for {} failed (hopefully temporarily) due to {}", id, e.message)
204204
publisher.publishEvent(ResourceCheckUnresolvable(resource, e, clock))
205205
} catch (e: Exception) {
206206
log.error("Resource check for $id failed", e)

keel-core/src/main/kotlin/com/netflix/spinnaker/keel/events/ArtifactVersionDeployed.kt

-27
This file was deleted.

keel-core/src/main/kotlin/com/netflix/spinnaker/keel/events/ArtifactVersionDeploying.kt

-9
This file was deleted.

keel-ec2-plugin/src/main/kotlin/com/netflix/spinnaker/config/EC2Config.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.netflix.spinnaker.config
1818

1919
import com.netflix.spinnaker.keel.api.actuation.TaskLauncher
2020
import com.netflix.spinnaker.keel.api.plugins.Resolver
21+
import com.netflix.spinnaker.keel.api.support.EventPublisher
2122
import com.netflix.spinnaker.keel.clouddriver.CloudDriverCache
2223
import com.netflix.spinnaker.keel.clouddriver.CloudDriverService
2324
import com.netflix.spinnaker.keel.clouddriver.ImageService
@@ -31,12 +32,11 @@ import com.netflix.spinnaker.keel.ec2.resource.ClusterHandler
3132
import com.netflix.spinnaker.keel.ec2.resource.SecurityGroupHandler
3233
import com.netflix.spinnaker.keel.orca.ClusterExportHelper
3334
import com.netflix.spinnaker.keel.orca.OrcaService
34-
import java.time.Clock
3535
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
3636
import org.springframework.boot.context.properties.EnableConfigurationProperties
37-
import org.springframework.context.ApplicationEventPublisher
3837
import org.springframework.context.annotation.Bean
3938
import org.springframework.context.annotation.Configuration
39+
import java.time.Clock
4040

4141
@Configuration
4242
@EnableConfigurationProperties(CanaryConstraintConfigurationProperties::class)
@@ -50,7 +50,7 @@ class EC2Config {
5050
taskLauncher: TaskLauncher,
5151
clock: Clock,
5252
normalizers: List<Resolver<*>>,
53-
publisher: ApplicationEventPublisher,
53+
eventPublisher: EventPublisher,
5454
clusterExportHelper: ClusterExportHelper
5555
): ClusterHandler =
5656
ClusterHandler(
@@ -59,7 +59,7 @@ class EC2Config {
5959
orcaService,
6060
taskLauncher,
6161
clock,
62-
publisher,
62+
eventPublisher,
6363
normalizers,
6464
clusterExportHelper
6565
)

keel-ec2-plugin/src/main/kotlin/com/netflix/spinnaker/keel/ec2/resource/ApplicationLoadBalancerHandler.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ import com.netflix.spinnaker.keel.ec2.toEc2Api
2626
import com.netflix.spinnaker.keel.model.Job
2727
import com.netflix.spinnaker.keel.orca.OrcaService
2828
import com.netflix.spinnaker.keel.retrofit.isNotFound
29-
import java.time.Duration
3029
import kotlinx.coroutines.async
3130
import kotlinx.coroutines.coroutineScope
3231
import retrofit2.HttpException
32+
import java.time.Duration
3333

3434
class ApplicationLoadBalancerHandler(
3535
private val cloudDriverService: CloudDriverService,

keel-ec2-plugin/src/main/kotlin/com/netflix/spinnaker/keel/ec2/resource/ClassicLoadBalancerHandler.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ import com.netflix.spinnaker.keel.diff.toIndividualDiffs
2828
import com.netflix.spinnaker.keel.model.Job
2929
import com.netflix.spinnaker.keel.orca.OrcaService
3030
import com.netflix.spinnaker.keel.retrofit.isNotFound
31-
import java.time.Duration
3231
import kotlinx.coroutines.async
3332
import kotlinx.coroutines.coroutineScope
3433
import retrofit2.HttpException
34+
import java.time.Duration
3535

3636
class ClassicLoadBalancerHandler(
3737
private val cloudDriverService: CloudDriverService,

keel-ec2-plugin/src/main/kotlin/com/netflix/spinnaker/keel/ec2/resource/ClusterHandler.kt

+10-27
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import com.netflix.spinnaker.keel.api.ec2.byRegion
4444
import com.netflix.spinnaker.keel.api.ec2.resolve
4545
import com.netflix.spinnaker.keel.api.plugins.ResolvableResourceHandler
4646
import com.netflix.spinnaker.keel.api.plugins.Resolver
47+
import com.netflix.spinnaker.keel.api.support.EventPublisher
4748
import com.netflix.spinnaker.keel.api.withDefaultsOmitted
4849
import com.netflix.spinnaker.keel.artifacts.DebianArtifact
4950
import com.netflix.spinnaker.keel.clouddriver.CloudDriverCache
@@ -64,8 +65,6 @@ import com.netflix.spinnaker.keel.core.serverGroup
6465
import com.netflix.spinnaker.keel.diff.toIndividualDiffs
6566
import com.netflix.spinnaker.keel.ec2.MissingAppVersionException
6667
import com.netflix.spinnaker.keel.ec2.toEc2Api
67-
import com.netflix.spinnaker.keel.events.ArtifactVersionDeployed
68-
import com.netflix.spinnaker.keel.events.ArtifactVersionDeploying
6968
import com.netflix.spinnaker.keel.exceptions.ActiveServerGroupsException
7069
import com.netflix.spinnaker.keel.exceptions.ExportError
7170
import com.netflix.spinnaker.keel.orca.ClusterExportHelper
@@ -77,25 +76,24 @@ import com.netflix.spinnaker.keel.orca.waitStage
7776
import com.netflix.spinnaker.keel.plugin.buildSpecFromDiff
7877
import com.netflix.spinnaker.keel.retrofit.isNotFound
7978
import com.netflix.spinnaker.keel.serialization.configuredObjectMapper
80-
import java.time.Clock
81-
import java.time.Duration
82-
import java.time.Instant
83-
import java.time.ZoneId
84-
import java.time.format.DateTimeFormatter
8579
import kotlinx.coroutines.Deferred
8680
import kotlinx.coroutines.async
8781
import kotlinx.coroutines.coroutineScope
8882
import kotlinx.coroutines.runBlocking
89-
import org.springframework.context.ApplicationEventPublisher
9083
import retrofit2.HttpException
84+
import java.time.Clock
85+
import java.time.Duration
86+
import java.time.Instant
87+
import java.time.ZoneId
88+
import java.time.format.DateTimeFormatter
9189

9290
class ClusterHandler(
9391
private val cloudDriverService: CloudDriverService,
9492
private val cloudDriverCache: CloudDriverCache,
9593
private val orcaService: OrcaService,
9694
private val taskLauncher: TaskLauncher,
9795
private val clock: Clock,
98-
private val publisher: ApplicationEventPublisher,
96+
override val eventPublisher: EventPublisher,
9997
resolvers: List<Resolver<*>>,
10098
private val clusterExportHelper: ClusterExportHelper
10199
) : ResolvableResourceHandler<ClusterSpec, Map<String, ServerGroup>>(resolvers) {
@@ -148,12 +146,7 @@ class ClusterHandler(
148146
)
149147

150148
if (createDiffs.isNotEmpty()) {
151-
publisher.publishEvent(
152-
ArtifactVersionDeploying(
153-
resourceId = resource.id,
154-
artifactVersion = version
155-
)
156-
)
149+
notifyArtifactDeploying(resource, version)
157150
}
158151

159152
return@coroutineScope deferred.map { it.await() }
@@ -311,12 +304,7 @@ class ClusterHandler(
311304
)
312305
}
313306

314-
publisher.publishEvent(
315-
ArtifactVersionDeploying(
316-
resourceId = resource.id,
317-
artifactVersion = version
318-
)
319-
)
307+
notifyArtifactDeploying(resource, version)
320308

321309
val task = deferred.await()
322310
priorExecutionId = task.id
@@ -887,12 +875,7 @@ class ClusterHandler(
887875
// // only publish a successfully deployed event if the server group is healthy
888876
val appVersion = activeServerGroups.first().launchConfiguration.appVersion
889877
if (appVersion != null) {
890-
publisher.publishEvent(
891-
ArtifactVersionDeployed(
892-
resourceId = resource.id,
893-
artifactVersion = appVersion
894-
)
895-
)
878+
notifyArtifactDeployed(resource, appVersion)
896879
}
897880
}
898881

keel-ec2-plugin/src/test/kotlin/com/netflix/spinnaker/keel/api/ec2/export/ClusterExportTests.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ import com.netflix.spinnaker.keel.api.ec2.TerminationPolicy
2626
import com.netflix.spinnaker.keel.api.ec2.VirtualMachineImage
2727
import com.netflix.spinnaker.keel.api.ec2.resolve
2828
import com.netflix.spinnaker.keel.api.plugins.Resolver
29+
import com.netflix.spinnaker.keel.api.support.EventPublisher
2930
import com.netflix.spinnaker.keel.artifacts.DebianArtifact
3031
import com.netflix.spinnaker.keel.clouddriver.CloudDriverCache
3132
import com.netflix.spinnaker.keel.clouddriver.CloudDriverService
3233
import com.netflix.spinnaker.keel.clouddriver.model.ActiveServerGroup
33-
import com.netflix.spinnaker.keel.clouddriver.model.Capacity as ClouddriverCapacity
3434
import com.netflix.spinnaker.keel.clouddriver.model.Network
3535
import com.netflix.spinnaker.keel.clouddriver.model.SecurityGroupSummary
3636
import com.netflix.spinnaker.keel.clouddriver.model.Subnet
@@ -48,11 +48,7 @@ import io.mockk.clearAllMocks
4848
import io.mockk.coEvery
4949
import io.mockk.every
5050
import io.mockk.mockk
51-
import java.time.Clock
52-
import java.time.Duration
53-
import java.util.UUID
5451
import kotlinx.coroutines.runBlocking
55-
import org.springframework.context.ApplicationEventPublisher
5652
import strikt.api.expect
5753
import strikt.api.expectThat
5854
import strikt.assertions.hasSize
@@ -62,6 +58,10 @@ import strikt.assertions.isEqualTo
6258
import strikt.assertions.isNotEmpty
6359
import strikt.assertions.isNotNull
6460
import strikt.assertions.isNull
61+
import java.time.Clock
62+
import java.time.Duration
63+
import java.util.UUID
64+
import com.netflix.spinnaker.keel.clouddriver.model.Capacity as ClouddriverCapacity
6565

6666
internal class ClusterExportTests : JUnit5Minutests {
6767

@@ -70,7 +70,7 @@ internal class ClusterExportTests : JUnit5Minutests {
7070
val orcaService = mockk<OrcaService>()
7171
val normalizers = emptyList<Resolver<ClusterSpec>>()
7272
val clock = Clock.systemUTC()
73-
val publisher: ApplicationEventPublisher = mockk(relaxUnitFun = true)
73+
val publisher: EventPublisher = mockk(relaxUnitFun = true)
7474
val repository = mockk<KeelRepository>()
7575
val taskLauncher = OrcaTaskLauncher(
7676
orcaService,

keel-ec2-plugin/src/test/kotlin/com/netflix/spinnaker/keel/ec2/resource/ApplicationLoadBalancerHandlerTests.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.netflix.spinnaker.keel.api.ec2.ApplicationLoadBalancerSpec
77
import com.netflix.spinnaker.keel.api.ec2.CLOUD_PROVIDER
88
import com.netflix.spinnaker.keel.api.ec2.EC2_APPLICATION_LOAD_BALANCER_V1_1
99
import com.netflix.spinnaker.keel.api.plugins.Resolver
10+
import com.netflix.spinnaker.keel.api.support.EventPublisher
1011
import com.netflix.spinnaker.keel.clouddriver.CloudDriverCache
1112
import com.netflix.spinnaker.keel.clouddriver.CloudDriverService
1213
import com.netflix.spinnaker.keel.clouddriver.model.ApplicationLoadBalancerModel
@@ -40,23 +41,22 @@ import io.mockk.confirmVerified
4041
import io.mockk.every
4142
import io.mockk.mockk
4243
import io.mockk.slot
43-
import java.util.UUID
4444
import kotlinx.coroutines.runBlocking
45-
import org.springframework.context.ApplicationEventPublisher
4645
import strikt.api.expectThat
4746
import strikt.assertions.first
4847
import strikt.assertions.get
4948
import strikt.assertions.getValue
5049
import strikt.assertions.isEqualTo
5150
import strikt.assertions.isFalse
5251
import strikt.assertions.isTrue
52+
import java.util.UUID
5353

5454
@Suppress("UNCHECKED_CAST")
5555
internal class ApplicationLoadBalancerHandlerTests : JUnit5Minutests {
5656
private val cloudDriverService = mockk<CloudDriverService>()
5757
private val cloudDriverCache = mockk<CloudDriverCache>()
5858
private val orcaService = mockk<OrcaService>()
59-
private val publisher: ApplicationEventPublisher = mockk(relaxUnitFun = true)
59+
private val publisher: EventPublisher = mockk(relaxUnitFun = true)
6060
private val repository = mockk<KeelRepository> {
6161
// we're just using this to get notifications
6262
every { environmentFor(any()) } returns Environment("test")

0 commit comments

Comments
 (0)