From d8e756f8812328be8f74ae3761621416a514941a Mon Sep 17 00:00:00 2001 From: Rob Zienert Date: Thu, 25 Jan 2018 16:19:33 -0800 Subject: [PATCH] feat(intent): Pipeline intent processing (#51) --- .../clouddriver/model/BaseModelParsingTest.kt | 2 +- .../spinnaker/config/KeelConfiguration.kt | 8 +- .../keel/model/OrchestrationRequest.kt | 4 +- .../spinnaker/keel/objectMapperUtil.kt | 15 +- .../keel/dryrun/DryRunIntentLauncherTest.kt | 4 +- .../keel/model/OrchestrationRequestTest.kt | 2 +- .../spinnaker/keel/front50/Front50Service.kt | 11 +- .../keel/front50/model/Application.kt | 2 +- .../keel/front50/model/PipelineConfig.kt | 49 +++++ .../spinnaker/config/IntentConfiguration.kt | 4 + .../spinnaker/keel/intent/PipelineIntent.kt | 58 ++++-- .../processor/ApplicationIntentProcessor.kt | 4 +- .../intent/processor/ParrotIntentProcessor.kt | 4 +- .../processor/PipelineIntentProcessor.kt | 126 ++++++++++++ .../processor/SecurityGroupIntentProcessor.kt | 6 +- .../processor/converter/PipelineConverter.kt | 116 ++++++++++++ .../keel/intent/AvailabilityZoneConfigTest.kt | 2 +- .../keel/intent/LoadBalancerIntentTest.kt | 2 +- .../keel/intent/PipelineIntentTest.kt | 148 +++++++++++++++ .../processor/PipelineIntentProcessorTest.kt | 132 +++++++++++++ .../converter/PipelineConverterTest.kt | 179 ++++++++++++++++++ .../RedisIntentActivityRepositoryTest.kt | 2 +- 22 files changed, 838 insertions(+), 42 deletions(-) create mode 100644 keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/model/PipelineConfig.kt create mode 100644 keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/PipelineIntentProcessor.kt create mode 100644 keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/converter/PipelineConverter.kt create mode 100644 keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/PipelineIntentTest.kt create mode 100644 keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/processor/PipelineIntentProcessorTest.kt create mode 100644 keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/processor/converter/PipelineConverterTest.kt diff --git a/keel-clouddriver/src/test/kotlin/com/netflix/spinnaker/keel/clouddriver/model/BaseModelParsingTest.kt b/keel-clouddriver/src/test/kotlin/com/netflix/spinnaker/keel/clouddriver/model/BaseModelParsingTest.kt index 4bb0c22fa7..09bd8747c6 100644 --- a/keel-clouddriver/src/test/kotlin/com/netflix/spinnaker/keel/clouddriver/model/BaseModelParsingTest.kt +++ b/keel-clouddriver/src/test/kotlin/com/netflix/spinnaker/keel/clouddriver/model/BaseModelParsingTest.kt @@ -26,7 +26,7 @@ abstract class BaseModelParsingTest { private val mapper = KeelConfiguration() .apply { properties = KeelProperties() } - .objectMapper(ObjectMapper()) + .objectMapper(ObjectMapper(), listOf()) private val client = mock() private val cloudDriver = RestAdapter.Builder() .setEndpoint(newFixedEndpoint("https://spinnaker.💩")) diff --git a/keel-core/src/main/kotlin/com/netflix/spinnaker/config/KeelConfiguration.kt b/keel-core/src/main/kotlin/com/netflix/spinnaker/config/KeelConfiguration.kt index 54df38dd91..364b2beac5 100644 --- a/keel-core/src/main/kotlin/com/netflix/spinnaker/config/KeelConfiguration.kt +++ b/keel-core/src/main/kotlin/com/netflix/spinnaker/config/KeelConfiguration.kt @@ -56,13 +56,19 @@ open class KeelConfiguration { // have the subtypes registered. // TODO rz - Move keiko subtype configurer into kork so we can use it here instead @Autowired - open fun objectMapper(objectMapper: ObjectMapper) = + open fun objectMapper(objectMapper: ObjectMapper, subtypeLocators: List) = objectMapper.apply { registerSubtypes(*findAllSubtypes(log, Intent::class.java, "com.netflix.spinnaker.keel.intent")) registerSubtypes(*findAllSubtypes(log, IntentSpec::class.java, "com.netflix.spinnaker.keel.intent")) registerSubtypes(*findAllSubtypes(log, Policy::class.java, "com.netflix.spinnaker.keel.policy")) registerSubtypes(*findAllSubtypes(log, PolicySpec::class.java, "com.netflix.spinnaker.keel.policy")) registerSubtypes(*findAllSubtypes(log, Attribute::class.java, "com.netflix.spinnaker.keel.attribute")) + + subtypeLocators.forEach { subtype -> + subtype.packages.forEach { pkg -> + registerSubtypes(*findAllSubtypes(log, subtype.cls, pkg)) + } + } } .registerModule(KotlinModule()) .registerModule(VersioningModule()) diff --git a/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/model/OrchestrationRequest.kt b/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/model/OrchestrationRequest.kt index 9520e53f88..e267e749b9 100644 --- a/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/model/OrchestrationRequest.kt +++ b/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/model/OrchestrationRequest.kt @@ -20,12 +20,12 @@ data class OrchestrationRequest( val application: String, val description: String, val job: List, - val trigger: Trigger + val trigger: OrchestrationTrigger ) class Job(type: String, m: MutableMap): HashMap(m.apply { put("type", type) }) -data class Trigger( +data class OrchestrationTrigger( val correlationId: String, val type: String = "keel", val user: String = "keel" diff --git a/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/objectMapperUtil.kt b/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/objectMapperUtil.kt index 2a035c9506..18c6be8b03 100644 --- a/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/objectMapperUtil.kt +++ b/keel-core/src/main/kotlin/com/netflix/spinnaker/keel/objectMapperUtil.kt @@ -15,18 +15,27 @@ */ package com.netflix.spinnaker.keel +import com.fasterxml.jackson.annotation.JsonTypeName +import com.fasterxml.jackson.databind.jsontype.NamedType import org.slf4j.Logger import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider import org.springframework.core.type.filter.AssignableTypeFilter import org.springframework.util.ClassUtils -fun findAllSubtypes(log: Logger, clazz: Class<*>, pkg: String): Array> +fun findAllSubtypes(log: Logger, clazz: Class<*>, pkg: String): Array = ClassPathScanningCandidateComponentProvider(false) .apply { addIncludeFilter(AssignableTypeFilter(clazz)) } .findCandidateComponents(pkg) .map { val cls = ClassUtils.resolveClassName(it.beanClassName, ClassUtils.getDefaultClassLoader()) - log.info("Registering ${cls.simpleName}") - return@map cls + + val serializationName = cls.annotations + .filterIsInstance() + .firstOrNull() + ?.value + + log.info("Registering ${cls.simpleName}: $serializationName") + + return@map NamedType(cls, serializationName ) } .toTypedArray() diff --git a/keel-core/src/test/kotlin/com/netflix/spinnaker/keel/dryrun/DryRunIntentLauncherTest.kt b/keel-core/src/test/kotlin/com/netflix/spinnaker/keel/dryrun/DryRunIntentLauncherTest.kt index 057c8834b1..83638cf672 100644 --- a/keel-core/src/test/kotlin/com/netflix/spinnaker/keel/dryrun/DryRunIntentLauncherTest.kt +++ b/keel-core/src/test/kotlin/com/netflix/spinnaker/keel/dryrun/DryRunIntentLauncherTest.kt @@ -29,7 +29,7 @@ import com.netflix.spinnaker.keel.IntentSpec import com.netflix.spinnaker.keel.IntentStatus.ACTIVE import com.netflix.spinnaker.keel.model.Job import com.netflix.spinnaker.keel.model.OrchestrationRequest -import com.netflix.spinnaker.keel.model.Trigger +import com.netflix.spinnaker.keel.model.OrchestrationTrigger import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.doAnswer import com.nhaarman.mockito_kotlin.doReturn @@ -65,7 +65,7 @@ object DryRunIntentLauncherTest { Job("wait", mutableMapOf("waitTime" to 5)), Job("wait", mutableMapOf("name" to "wait for more time", "waitTime" to 5)) ), - Trigger("1", "keel", "keel") + OrchestrationTrigger("1", "keel", "keel") ) ), changeSummary diff --git a/keel-core/src/test/kotlin/com/netflix/spinnaker/keel/model/OrchestrationRequestTest.kt b/keel-core/src/test/kotlin/com/netflix/spinnaker/keel/model/OrchestrationRequestTest.kt index 46773ba36b..4151c59595 100644 --- a/keel-core/src/test/kotlin/com/netflix/spinnaker/keel/model/OrchestrationRequestTest.kt +++ b/keel-core/src/test/kotlin/com/netflix/spinnaker/keel/model/OrchestrationRequestTest.kt @@ -25,7 +25,7 @@ object OrchestrationRequestTest { fun `should build job request`() { val req = OrchestrationRequest("wait for nothing", "keel", "my orchestration", listOf( Job("wait", mutableMapOf("waitTime" to 30)) - ), Trigger("1", "keel", "keel")) + ), OrchestrationTrigger("1", "keel", "keel")) req.name shouldMatch equalTo("wait for nothing") req.application shouldMatch equalTo("keel") diff --git a/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/Front50Service.kt b/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/Front50Service.kt index d428d674a8..8ac36da753 100644 --- a/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/Front50Service.kt +++ b/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/Front50Service.kt @@ -19,8 +19,14 @@ import com.netflix.spinnaker.keel.Intent import com.netflix.spinnaker.keel.IntentSpec import com.netflix.spinnaker.keel.IntentStatus import com.netflix.spinnaker.keel.front50.model.Application +import com.netflix.spinnaker.keel.front50.model.PipelineConfig import retrofit.client.Response -import retrofit.http.* +import retrofit.http.Body +import retrofit.http.DELETE +import retrofit.http.GET +import retrofit.http.POST +import retrofit.http.Path +import retrofit.http.Query interface Front50Service { @@ -41,4 +47,7 @@ interface Front50Service { @GET("/v2/applications/{applicationName}") fun getApplication(@Path("applicationName") applicationName: String): Application + + @GET("/pipelines/{applicationName}") + fun getPipelineConfigs(@Path("applicationName") applicationName: String): List } diff --git a/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/model/Application.kt b/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/model/Application.kt index 2bd53da31f..86fb82a1ab 100644 --- a/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/model/Application.kt +++ b/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/model/Application.kt @@ -28,7 +28,7 @@ data class Application( @Computed val createTs: String? = null, val platformHealthOnly: Boolean, val platformHealthOnlyShowOverride: Boolean, - val owner: String + val owner: String? ) : ComputedPropertyProvider { val details: MutableMap = mutableMapOf() diff --git a/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/model/PipelineConfig.kt b/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/model/PipelineConfig.kt new file mode 100644 index 0000000000..775586c886 --- /dev/null +++ b/keel-front50/src/main/kotlin/com/netflix/spinnaker/keel/front50/model/PipelineConfig.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2018 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.spinnaker.keel.front50.model + +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.netflix.spinnaker.keel.annotation.Computed + +data class PipelineConfig( + val application: String, + val name: String, + val parameterConfig: List>?, + val triggers: List>?, + val notifications: List>?, + val stages: List>, + val spelEvaluator: String?, + val executionEngine: String?, + val limitConcurrent: Boolean?, + val keepWaitingPipelines: Boolean?, + @Computed val id: String?, + @Computed val index: Int?, + @Computed val stageCounter: Int?, + @Computed val lastModifiedBy: String?, + @Computed val updateTs: String? +) { + + private val details: MutableMap = mutableMapOf() + + @JsonAnySetter + fun set(name: String, value: Any?) { + details[name] = value + } + + @JsonAnyGetter + fun details() = details +} diff --git a/keel-intent/src/main/kotlin/com/netflix/spinnaker/config/IntentConfiguration.kt b/keel-intent/src/main/kotlin/com/netflix/spinnaker/config/IntentConfiguration.kt index 441d978cdf..1cc8624f3e 100644 --- a/keel-intent/src/main/kotlin/com/netflix/spinnaker/config/IntentConfiguration.kt +++ b/keel-intent/src/main/kotlin/com/netflix/spinnaker/config/IntentConfiguration.kt @@ -16,6 +16,7 @@ package com.netflix.spinnaker.config import com.netflix.spinnaker.keel.intent.SecurityGroupRule +import com.netflix.spinnaker.keel.intent.Trigger import org.springframework.context.annotation.Bean import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration @@ -30,4 +31,7 @@ open class IntentConfiguration { @Bean open fun securityGroupSubTypeLocator() = KeelSubTypeLocator(SecurityGroupRule::class.java, listOf("com.netflix.spinnaker.keel.intent")) + + @Bean open fun pipelineTriggerSubTypeLocator() = + KeelSubTypeLocator(Trigger::class.java, listOf("com.netflix.spinnaker.keel.intent")) } diff --git a/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/PipelineIntent.kt b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/PipelineIntent.kt index 502d20cd5d..f8f3ec060b 100644 --- a/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/PipelineIntent.kt +++ b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/PipelineIntent.kt @@ -35,30 +35,33 @@ class PipelineIntent schema = CURRENT_SCHEMA, spec = spec ) { - @JsonIgnore override val defaultId = "$KIND:${spec.application}:${spec.slug}" + @JsonIgnore override val defaultId = "$KIND:${spec.application}:${spec.name}" } @JsonTypeName("pipeline") data class PipelineSpec( override val application: String, - // Used for idempotency of id - @ForcesNew val slug: String, - val stages: List>, + // TODO rz - Support renaming without re-creation. Probably require getting all pipelines for an + // application and finding a match on name before writes happen? + @ForcesNew val name: String, + val stages: List, val triggers: List, - val flags: Flags, - val properties: Properties + val parameters: List>, + val notifications: List>, + val flags: PipelineFlags, + val properties: PipelineProperties ) : ApplicationAwareIntentSpec() -class Flags : HashMap() { +class PipelineFlags : HashMap() { - val keepWaitingPipelines: Boolean - get() = get("keepWaitingPipelines") ?: true + val keepWaitingPipelines: Boolean? + get() = get("keepWaitingPipelines") - val limitConcurrent: Boolean - get() = get("limitConcurrent") ?: false + val limitConcurrent: Boolean? + get() = get("limitConcurrent") } -class Properties : HashMap() { +class PipelineProperties : HashMap() { val executionEngine: String? get() = get("executionEngine") @@ -67,26 +70,41 @@ class Properties : HashMap() { get() = get("spelEvaluator") } +class PipelineStage : HashMap() { + + val kind: String + get() = get("kind").toString() + + val refId: String + get() = get("refId").toString() + + val dependsOn: List + get() = if (containsKey("dependsOn")) this["dependsOn"] as List else listOf() +} + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "kind") -abstract class Trigger : HashMap() +interface Trigger +//abstract class Trigger : HashMap { +// @JsonCreator constructor(m: Map) : super(m) +//} @JsonTypeName("cron") -class CronTrigger : Trigger() +class CronTrigger(m: Map) : HashMap(m), Trigger @JsonTypeName("docker") -class DockerTrigger : Trigger() +class DockerTrigger(m: Map) : HashMap(m), Trigger @JsonTypeName("dryRun") -class DryRunTrigger : Trigger() +class DryRunTrigger(m: Map) : HashMap(m), Trigger @JsonTypeName("git") -class GitTrigger : Trigger() +class GitTrigger(m: Map) : HashMap(m), Trigger @JsonTypeName("jenkins") -class JenkinsTrigger : Trigger() +class JenkinsTrigger(m: Map) : HashMap(m), Trigger @JsonTypeName("manual") -class ManualTrigger : Trigger() +class ManualTrigger(m: Map) : HashMap(m), Trigger @JsonTypeName("pipeline") -class PipelineTrigger : Trigger() +class PipelineTrigger(m: Map) : HashMap(m), Trigger diff --git a/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/ApplicationIntentProcessor.kt b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/ApplicationIntentProcessor.kt index 976c3d57cb..34ee9a92bc 100644 --- a/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/ApplicationIntentProcessor.kt +++ b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/ApplicationIntentProcessor.kt @@ -30,7 +30,7 @@ import com.netflix.spinnaker.keel.intent.ApplicationIntent import com.netflix.spinnaker.keel.intent.BaseApplicationSpec import com.netflix.spinnaker.keel.model.Job import com.netflix.spinnaker.keel.model.OrchestrationRequest -import com.netflix.spinnaker.keel.model.Trigger +import com.netflix.spinnaker.keel.model.OrchestrationTrigger import com.netflix.spinnaker.keel.state.FieldMutator import com.netflix.spinnaker.keel.state.StateInspector import com.netflix.spinnaker.keel.tracing.Trace @@ -82,7 +82,7 @@ class ApplicationIntentProcessor ) ) ), - trigger = Trigger(intent.id()) + trigger = OrchestrationTrigger(intent.id()) ) ), changeSummary diff --git a/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/ParrotIntentProcessor.kt b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/ParrotIntentProcessor.kt index a67fc19251..8b49d84cfd 100644 --- a/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/ParrotIntentProcessor.kt +++ b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/ParrotIntentProcessor.kt @@ -23,7 +23,7 @@ import com.netflix.spinnaker.keel.dryrun.ChangeSummary import com.netflix.spinnaker.keel.intent.ParrotIntent import com.netflix.spinnaker.keel.model.Job import com.netflix.spinnaker.keel.model.OrchestrationRequest -import com.netflix.spinnaker.keel.model.Trigger +import com.netflix.spinnaker.keel.model.OrchestrationTrigger import com.netflix.spinnaker.keel.tracing.Trace import com.netflix.spinnaker.keel.tracing.TraceRepository import org.springframework.beans.factory.annotation.Autowired @@ -49,7 +49,7 @@ class ParrotIntentProcessor job = listOf( Job("wait", mutableMapOf("waitTime" to intent.spec.waitTime)) ), - trigger = Trigger(intent.id()) + trigger = OrchestrationTrigger(intent.id()) ) ), changeSummary) } diff --git a/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/PipelineIntentProcessor.kt b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/PipelineIntentProcessor.kt new file mode 100644 index 0000000000..eebdb120ec --- /dev/null +++ b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/PipelineIntentProcessor.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2018 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.spinnaker.keel.intent.processor + +import com.fasterxml.jackson.databind.ObjectMapper +import com.netflix.spinnaker.keel.ConvergeReason +import com.netflix.spinnaker.keel.ConvergeResult +import com.netflix.spinnaker.keel.Intent +import com.netflix.spinnaker.keel.IntentProcessor +import com.netflix.spinnaker.keel.IntentSpec +import com.netflix.spinnaker.keel.dryrun.ChangeSummary +import com.netflix.spinnaker.keel.dryrun.ChangeType +import com.netflix.spinnaker.keel.front50.Front50Service +import com.netflix.spinnaker.keel.front50.model.PipelineConfig +import com.netflix.spinnaker.keel.intent.ANY_MAP_TYPE +import com.netflix.spinnaker.keel.intent.PipelineIntent +import com.netflix.spinnaker.keel.intent.PipelineSpec +import com.netflix.spinnaker.keel.intent.processor.converter.PipelineConverter +import com.netflix.spinnaker.keel.model.OrchestrationRequest +import com.netflix.spinnaker.keel.model.OrchestrationTrigger +import com.netflix.spinnaker.keel.state.StateInspector +import com.netflix.spinnaker.keel.tracing.Trace +import com.netflix.spinnaker.keel.tracing.TraceRepository +import org.springframework.stereotype.Component +import retrofit.RetrofitError + +@Component +class PipelineIntentProcessor( + private val traceRepository: TraceRepository, + private val front50Service: Front50Service, + private val objectMapper: ObjectMapper, + private val pipelineConverter: PipelineConverter +): IntentProcessor { + + override fun supports(intent: Intent) = intent is PipelineIntent + + override fun converge(intent: PipelineIntent): ConvergeResult { + val changeSummary = ChangeSummary(intent.id()) + + val currentState = getPipelineConfig(intent.spec.application, intent.spec.name) + + if (currentStateUpToDate(intent.id(), currentState, intent.spec, changeSummary)) { + changeSummary.addMessage(ConvergeReason.UNCHANGED.reason) + return ConvergeResult(listOf(), changeSummary) + } + + if (missingApplication(intent.spec.application)) { + changeSummary.addMessage("The application this pipeline is meant for is missing: ${intent.spec.application}") + changeSummary.type = ChangeType.FAILED_PRECONDITIONS + return ConvergeResult(listOf(), changeSummary) + } + + changeSummary.type = if (currentState == null) ChangeType.CREATE else ChangeType.UPDATE + + traceRepository.record(Trace( + startingState = if (currentState == null) mapOf() else objectMapper.convertValue(currentState, ANY_MAP_TYPE), + intent = intent + )) + + return ConvergeResult(listOf( + OrchestrationRequest( + name = (if (currentState == null) "Create" else "Update") + " pipeline '${intent.spec.name}'", + application = intent.spec.application, + description = "Converging on desired pipeline state", + job = pipelineConverter.convertToJob(intent.spec, changeSummary), + trigger = OrchestrationTrigger(intent.id()) + ) + ), changeSummary) + } + + private fun currentStateUpToDate(intentId: String, + currentState: PipelineConfig?, + desiredState: PipelineSpec, + changeSummary: ChangeSummary): Boolean { + val desired = pipelineConverter.convertToState(desiredState) + + if (currentState == null) return false + val diff = StateInspector(objectMapper).run { + getDiff( + intentId = intentId, + currentState = currentState, + desiredState = desired, + modelClass = PipelineConfig::class, + specClass = PipelineSpec::class + ) + } + changeSummary.diff = diff + return diff.isEmpty() + } + + private fun getPipelineConfig(application: String, name: String): PipelineConfig? { + try { + return front50Service.getPipelineConfigs(application).firstOrNull { it.name == name } + } catch (e: RetrofitError) { + if (e.notFound()) { + return null + } + throw e + } + } + + private fun missingApplication(application: String): Boolean { + try { + front50Service.getApplication(application) + return false + } catch (e: RetrofitError) { + if (e.notFound()) { + return true + } + throw e + } + } +} diff --git a/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/SecurityGroupIntentProcessor.kt b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/SecurityGroupIntentProcessor.kt index 4773e808e0..ca4adae34b 100644 --- a/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/SecurityGroupIntentProcessor.kt +++ b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/SecurityGroupIntentProcessor.kt @@ -33,7 +33,7 @@ import com.netflix.spinnaker.keel.intent.SecurityGroupIntent import com.netflix.spinnaker.keel.intent.SecurityGroupSpec import com.netflix.spinnaker.keel.intent.processor.converter.SecurityGroupConverter import com.netflix.spinnaker.keel.model.OrchestrationRequest -import com.netflix.spinnaker.keel.model.Trigger +import com.netflix.spinnaker.keel.model.OrchestrationTrigger import com.netflix.spinnaker.keel.state.StateInspector import com.netflix.spinnaker.keel.tracing.Trace import com.netflix.spinnaker.keel.tracing.TraceRepository @@ -75,7 +75,7 @@ class SecurityGroupIntentProcessor if (missingGroups.isNotEmpty()) { changeSummary.addMessage("Some upstream security groups are missing: $missingGroups") changeSummary.type = ChangeType.FAILED_PRECONDITIONS - return ConvergeResult(listOf(),changeSummary) + return ConvergeResult(listOf(), changeSummary) } changeSummary.type = if (currentState.isEmpty()) ChangeType.CREATE else ChangeType.UPDATE @@ -86,7 +86,7 @@ class SecurityGroupIntentProcessor application = intent.spec.application, description = "Converging on desired security group state", job = securityGroupConverter.convertToJob(intent.spec, changeSummary), - trigger = Trigger(intent.id()) + trigger = OrchestrationTrigger(intent.id()) ) ), changeSummary diff --git a/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/converter/PipelineConverter.kt b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/converter/PipelineConverter.kt new file mode 100644 index 0000000000..c3f8336d32 --- /dev/null +++ b/keel-intent/src/main/kotlin/com/netflix/spinnaker/keel/intent/processor/converter/PipelineConverter.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2018 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.spinnaker.keel.intent.processor.converter + +import com.amazonaws.util.Base64 +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.convertValue +import com.netflix.spinnaker.keel.dryrun.ChangeSummary +import com.netflix.spinnaker.keel.front50.model.PipelineConfig +import com.netflix.spinnaker.keel.intent.PipelineFlags +import com.netflix.spinnaker.keel.intent.PipelineProperties +import com.netflix.spinnaker.keel.intent.PipelineSpec +import com.netflix.spinnaker.keel.intent.PipelineStage +import com.netflix.spinnaker.keel.intent.Trigger +import com.netflix.spinnaker.keel.model.Job +import org.springframework.stereotype.Component + +@Component +class PipelineConverter( + private val objectMapper: ObjectMapper +) : SpecConverter { + + override fun convertToState(spec: PipelineSpec) = + PipelineConfig( + application = spec.application, + name = spec.name, + parameterConfig = spec.parameters, + triggers = spec.triggers.map { concreteTrigger -> + objectMapper.convertValue>(concreteTrigger).also { trigger -> + trigger["type"] = trigger["kind"] + trigger.remove("kind") + } + }, + notifications = spec.notifications, + stages = spec.stages.map { + it.toMutableMap().also { stage -> + stage.put("type", it.kind) + stage.put("requisiteStageRefIds", it.dependsOn) + stage.remove("kind") + stage.remove("dependsOn") + } + }, + spelEvaluator = spec.properties.spelEvaluator, + executionEngine = spec.properties.executionEngine, + limitConcurrent = spec.flags.limitConcurrent, + keepWaitingPipelines = spec.flags.keepWaitingPipelines, + id = null, + index = null, + stageCounter = null, + lastModifiedBy = null, + updateTs = null + ) + + override fun convertFromState(state: PipelineConfig) = + PipelineSpec( + application = state.application, + name = state.name, + parameters = state.parameterConfig ?: listOf(), + triggers = state.triggers?.map { triggerMap -> + triggerMap.toMutableMap().let { + it["kind"] = it["type"] + it.remove("type") + objectMapper.convertValue(it) + } + } ?: listOf(), + notifications = state.notifications ?: listOf(), + stages = state.stages.map { rawStage -> + objectMapper.convertValue(rawStage).also { + // TODO rz - This mapping feels kinda funky + it["kind"] = it["type"]!! + it["dependsOn"] = it["requisiteStageRefIds"]!! + it.remove("requisiteStageRefIds") + it.remove("type") + } + }, + properties = PipelineProperties().apply { + if (state.spelEvaluator != null) { + put("spelEvaluator", state.spelEvaluator!!) + } + if (state.executionEngine != null) { + put("executionEngine", state.executionEngine!!) + } + }, + flags = PipelineFlags().apply { + if (state.limitConcurrent != null) { + put("limitConcurrent", state.limitConcurrent!!) + } + if (state.keepWaitingPipelines != null) { + put("keepWaitingPipelines", state.keepWaitingPipelines!!) + } + } + ) + + override fun convertToJob(spec: PipelineSpec, changeSummary: ChangeSummary) = + listOf( + Job( + type = "savePipeline", + m = mutableMapOf( + "pipeline" to Base64.encodeAsString(*objectMapper.writeValueAsBytes(convertToState(spec))) + ) + ) + ) +} diff --git a/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/AvailabilityZoneConfigTest.kt b/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/AvailabilityZoneConfigTest.kt index c3fe45de91..47b25705ae 100644 --- a/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/AvailabilityZoneConfigTest.kt +++ b/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/AvailabilityZoneConfigTest.kt @@ -13,7 +13,7 @@ object AvailabilityZoneConfigTest { val mapper = KeelConfiguration() .apply { properties = KeelProperties() } - .objectMapper(ObjectMapper()) + .objectMapper(ObjectMapper(), listOf()) private val automaticJson = """{"zones":"automatic"}""" private val automaticZones = Container(zones = Automatic) diff --git a/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/LoadBalancerIntentTest.kt b/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/LoadBalancerIntentTest.kt index 5bd81dd9a5..a2b9fa9a5f 100644 --- a/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/LoadBalancerIntentTest.kt +++ b/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/LoadBalancerIntentTest.kt @@ -21,7 +21,7 @@ object LoadBalancerIntentTest { val mapper = KeelConfiguration() .apply { properties = KeelProperties() } - .objectMapper(ObjectMapper()) + .objectMapper(ObjectMapper(), listOf()) @Test fun `can serialize to expected JSON format`() { diff --git a/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/PipelineIntentTest.kt b/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/PipelineIntentTest.kt new file mode 100644 index 0000000000..37778453dc --- /dev/null +++ b/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/PipelineIntentTest.kt @@ -0,0 +1,148 @@ +/* + * Copyright 2018 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.spinnaker.keel.intent + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.convertValue +import com.fasterxml.jackson.module.kotlin.readValue +import com.natpryce.hamkrest.equalTo +import com.natpryce.hamkrest.should.shouldMatch +import com.netflix.spinnaker.config.KeelConfiguration +import com.netflix.spinnaker.config.KeelProperties +import com.netflix.spinnaker.config.KeelSubTypeLocator +import com.netflix.spinnaker.hamkrest.shouldEqual +import org.junit.jupiter.api.Test + +object PipelineIntentTest { + + val mapper = KeelConfiguration() + .apply { properties = KeelProperties() } + .objectMapper( + ObjectMapper(), listOf(KeelSubTypeLocator(Trigger::class.java, listOf("com.netflix.spinnaker.keel.intent"))) + ) + + @Test + fun `can serialize to expected JSON format`() { + val serialized = mapper.convertValue>(pipeline) + val deserialized = mapper.readValue>(json) + + serialized shouldMatch equalTo(deserialized) + } + + @Test + fun `can deserialize from expected JSON format`() { + mapper.readValue(json).apply { + spec shouldEqual pipeline.spec + } + } + + val pipeline = PipelineIntent( + PipelineSpec( + application = "keel", + name = "Bake", + stages = listOf( + PipelineStage().apply { + this["showAdvancedOptions"] = false + this["baseLabel"] = "release" + this["user"] = "example@example.com" + this["kind"] = "bake" + this["storeType"] = "ebs" + this["refId"] = "bake1" + this["dependsOn"] = listOf() + this["enhancedNetworking"] = false + this["regions"] = listOf("us-east-1", "us-west-2") + this["extendedAttributes"] = mapOf() + this["cloudProviderType"] = "aws" + this["vmType"] = "hvm" + this["package"] = "keel" + this["baseOs"] = "xenial" + } + ), + triggers = listOf( + JenkinsTrigger(mapOf( + "enabled" to true, + "master" to "spinnaker", + "job" to "SPINNAKER-package-keel", + "propertyFile" to "" + )) + ), + parameters = listOf(), + notifications = listOf(), + flags = PipelineFlags().apply { + this["limitConcurrent"] = true + this["keepWaitingPipelines"] = true + }, + properties = PipelineProperties().apply { + this["executionEngine"] = "v3" + } + ) + ) + + val json = """ +{ + "kind": "Pipeline", + "spec": { + "application": "keel", + "name": "Bake", + "flags": { + "limitConcurrent": true, + "keepWaitingPipelines": true + }, + "properties": { + "executionEngine": "v3" + }, + "parameters": [], + "notifications": [], + "triggers": [ + { + "kind": "jenkins", + "enabled": true, + "master": "spinnaker", + "job": "SPINNAKER-package-keel", + "propertyFile": "" + } + ], + "stages": [ + { + "kind": "bake", + "refId": "bake1", + "dependsOn": [], + "showAdvancedOptions": false, + "baseLabel": "release", + "user": "example@example.com", + "storeType": "ebs", + "enhancedNetworking": false, + "regions": [ + "us-east-1", + "us-west-2" + ], + "extendedAttributes": {}, + "cloudProviderType": "aws", + "vmType": "hvm", + "package": "keel", + "baseOs": "xenial" + } + ] + }, + "status": "ACTIVE", + "labels": {}, + "attributes": [], + "policies": [], + "id": "Pipeline:keel:Bake", + "schema": "0" +} +""" +} diff --git a/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/processor/PipelineIntentProcessorTest.kt b/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/processor/PipelineIntentProcessorTest.kt new file mode 100644 index 0000000000..af20605e67 --- /dev/null +++ b/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/processor/PipelineIntentProcessorTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2018 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.spinnaker.keel.intent.processor + +import com.fasterxml.jackson.databind.ObjectMapper +import com.natpryce.hamkrest.equalTo +import com.natpryce.hamkrest.should.shouldMatch +import com.netflix.spinnaker.hamkrest.shouldEqual +import com.netflix.spinnaker.keel.dryrun.ChangeSummary +import com.netflix.spinnaker.keel.dryrun.ChangeType +import com.netflix.spinnaker.keel.front50.Front50Service +import com.netflix.spinnaker.keel.front50.model.Application +import com.netflix.spinnaker.keel.front50.model.PipelineConfig +import com.netflix.spinnaker.keel.intent.ApplicationIntent +import com.netflix.spinnaker.keel.intent.PipelineFlags +import com.netflix.spinnaker.keel.intent.PipelineIntent +import com.netflix.spinnaker.keel.intent.PipelineProperties +import com.netflix.spinnaker.keel.intent.PipelineSpec +import com.netflix.spinnaker.keel.intent.processor.converter.PipelineConverter +import com.netflix.spinnaker.keel.tracing.TraceRepository +import com.nhaarman.mockito_kotlin.any +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.doThrow +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.reset +import com.nhaarman.mockito_kotlin.whenever +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import retrofit.RetrofitError +import retrofit.client.Response + +object PipelineIntentProcessorTest { + + val traceRepository = mock() + val front50Service = mock() + val objectMapper = ObjectMapper() + val converter = PipelineConverter(objectMapper) + + val subject = PipelineIntentProcessor(traceRepository, front50Service, objectMapper, converter) + + @AfterEach + fun cleanup() { + reset(SecurityGroupIntentProcessorTest.traceRepository, front50Service) + } + + @Test + fun `should support PipelineIntent`() { + subject.supports(ApplicationIntent(mock())) shouldMatch equalTo(false) + subject.supports(PipelineIntent(PipelineSpec( + application = "foo", + name = "bar", + stages = listOf(), + triggers = listOf(), + parameters = listOf(), + notifications = listOf(), + flags = PipelineFlags(), + properties = PipelineProperties() + ))) shouldMatch equalTo(true) + } + + @Test + fun `should save pipeline`() { + whenever(front50Service.getApplication(any())) doReturn Application( + name = "foo", + description = "description", + email = "example@example.com", + platformHealthOnly = false, + platformHealthOnlyShowOverride = false, + owner = "Example" + ) + whenever(front50Service.getPipelineConfigs("foo")) doReturn listOf() + + val intent = PipelineIntent(PipelineSpec( + application = "foo", + name = "bar", + stages = listOf(), + triggers = listOf(), + parameters = listOf(), + notifications = listOf(), + flags = PipelineFlags(), + properties = PipelineProperties() + )) + + val result = subject.converge(intent) + + result.orchestrations.size shouldMatch equalTo(1) + result.orchestrations[0].name shouldMatch equalTo("Create pipeline 'bar'") + result.orchestrations[0].application shouldMatch equalTo("foo") + result.orchestrations[0].job[0]["type"] shouldMatch equalTo("savePipeline") + + } + + @Test + fun `should skip operation if application is missing`() { + whenever(front50Service.getApplication(any())) doThrow RetrofitError.httpError( + "http", Response("", 404, "Not Found", listOf(), mock()), mock(), mock() + ) + whenever(front50Service.getPipelineConfigs("foo")) doReturn listOf() + + val intent = PipelineIntent(PipelineSpec( + application = "foo", + name = "bar", + stages = listOf(), + triggers = listOf(), + parameters = listOf(), + notifications = listOf(), + flags = PipelineFlags(), + properties = PipelineProperties() + )) + + val result = subject.converge(intent) + + result.orchestrations.size shouldMatch equalTo(0) + result.changeSummary shouldEqual ChangeSummary("Pipeline:foo:bar", mutableListOf( + "The application this pipeline is meant for is missing: foo" + )).apply { type = ChangeType.FAILED_PRECONDITIONS } + } + +} diff --git a/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/processor/converter/PipelineConverterTest.kt b/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/processor/converter/PipelineConverterTest.kt new file mode 100644 index 0000000000..fed696d42f --- /dev/null +++ b/keel-intent/src/test/kotlin/com/netflix/spinnaker/keel/intent/processor/converter/PipelineConverterTest.kt @@ -0,0 +1,179 @@ +/* + * Copyright 2018 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.spinnaker.keel.intent.processor.converter + +import com.fasterxml.jackson.databind.ObjectMapper +import com.netflix.spinnaker.config.KeelConfiguration +import com.netflix.spinnaker.config.KeelProperties +import com.netflix.spinnaker.config.KeelSubTypeLocator +import com.netflix.spinnaker.hamkrest.shouldEqual +import com.netflix.spinnaker.keel.dryrun.ChangeSummary +import com.netflix.spinnaker.keel.front50.model.PipelineConfig +import com.netflix.spinnaker.keel.intent.JenkinsTrigger +import com.netflix.spinnaker.keel.intent.PipelineFlags +import com.netflix.spinnaker.keel.intent.PipelineProperties +import com.netflix.spinnaker.keel.intent.PipelineSpec +import com.netflix.spinnaker.keel.intent.PipelineStage +import com.netflix.spinnaker.keel.intent.Trigger +import org.junit.jupiter.api.Test + +object PipelineConverterTest { + + val mapper = KeelConfiguration() + .apply { properties = KeelProperties() } + .objectMapper( + ObjectMapper(), listOf(KeelSubTypeLocator(Trigger::class.java, listOf("com.netflix.spinnaker.keel.intent"))) + ) + + val subject = PipelineConverter(mapper) + + @Test + fun `should convert spec to system state`() { + subject.convertToState(spec).also { + it.application shouldEqual spec.application + it.name shouldEqual spec.name + it.limitConcurrent shouldEqual spec.flags.limitConcurrent + it.keepWaitingPipelines shouldEqual spec.flags.keepWaitingPipelines + it.executionEngine shouldEqual spec.properties.executionEngine + it.spelEvaluator shouldEqual spec.properties.spelEvaluator + it.stages shouldEqual listOf( + mapOf( + "showAdvancedOptions" to false, + "baseLabel" to "release", + "user" to "example@example.com", + "type" to "bake", + "storeType" to "ebs", + "refId" to "bake1", + "requisiteStageRefIds" to listOf(), + "enhancedNetworking" to false, + "regions" to listOf("us-east-1", "us-west-2"), + "extendedAttributes" to mapOf(), + "cloudProviderType" to "aws", + "vmType" to "hvm", + "package" to "keel", + "baseOs" to "xenial" + ) + ) + it.triggers?.size shouldEqual 1 + it.triggers?.get(0)?.also { trigger -> + trigger["type"] shouldEqual "jenkins" + trigger["enabled"] shouldEqual true + trigger["master"] shouldEqual "spinnaker" + trigger["job"] shouldEqual "SPINNAKER-package-keel" + trigger["propertyFile"] to "" + } + it.parameterConfig shouldEqual listOf() + it.notifications shouldEqual listOf() + } + } + + @Test + fun `should convert system state to spec`() { + val result = subject.convertFromState(state) + result shouldEqual spec + } + + @Test + fun `should convert spec to orchestration job`() { + subject.convertToJob(spec, ChangeSummary("test")).also { + it.size shouldEqual 1 + it[0]["type"] shouldEqual "savePipeline" + } + } + + val spec = PipelineSpec( + application = "keel", + name = "Bake", + stages = listOf( + PipelineStage().apply { + this["showAdvancedOptions"] = false + this["baseLabel"] = "release" + this["user"] = "example@example.com" + this["kind"] = "bake" + this["storeType"] = "ebs" + this["refId"] = "bake1" + this["dependsOn"] = listOf() + this["enhancedNetworking"] = false + this["regions"] = listOf("us-east-1", "us-west-2") + this["extendedAttributes"] = mapOf() + this["cloudProviderType"] = "aws" + this["vmType"] = "hvm" + this["package"] = "keel" + this["baseOs"] = "xenial" + } + ), + triggers = listOf( + JenkinsTrigger(mapOf( + "enabled" to true, + "master" to "spinnaker", + "job" to "SPINNAKER-package-keel", + "propertyFile" to "" + )) + ), + parameters = listOf(), + notifications = listOf(), + flags = PipelineFlags().apply { + this["limitConcurrent"] = true + this["keepWaitingPipelines"] = true + }, + properties = PipelineProperties().apply { + this["executionEngine"] = "v3" + } + ) + + val state = PipelineConfig( + application = "keel", + name = spec.name, + parameterConfig = listOf(), + triggers = listOf( + mapOf( + "type" to "jenkins", + "enabled" to true, + "master" to "spinnaker", + "job" to "SPINNAKER-package-keel", + "propertyFile" to "" + ) + ), + notifications = listOf(), + stages = listOf( + mapOf( + "showAdvancedOptions" to false, + "baseLabel" to "release", + "user" to "example@example.com", + "type" to "bake", + "storeType" to "ebs", + "refId" to "bake1", + "requisiteStageRefIds" to listOf(), + "enhancedNetworking" to false, + "regions" to listOf("us-east-1", "us-west-2"), + "extendedAttributes" to mapOf(), + "cloudProviderType" to "aws", + "vmType" to "hvm", + "package" to "keel", + "baseOs" to "xenial" + ) + ), + spelEvaluator = null, + executionEngine = "v3", + limitConcurrent = true, + keepWaitingPipelines = true, + id = null, + index = null, + stageCounter = null, + lastModifiedBy = null, + updateTs = null + ) +} diff --git a/keel-redis/src/test/kotlin/com/netflix/spinnaker/keel/redis/RedisIntentActivityRepositoryTest.kt b/keel-redis/src/test/kotlin/com/netflix/spinnaker/keel/redis/RedisIntentActivityRepositoryTest.kt index 48b72632b3..701c0a9554 100644 --- a/keel-redis/src/test/kotlin/com/netflix/spinnaker/keel/redis/RedisIntentActivityRepositoryTest.kt +++ b/keel-redis/src/test/kotlin/com/netflix/spinnaker/keel/redis/RedisIntentActivityRepositoryTest.kt @@ -44,7 +44,7 @@ object RedisIntentActivityRepositoryTest { val clock = Clock.systemDefaultZone() val mapper = KeelConfiguration() .apply { properties = keelProperties } - .objectMapper(ObjectMapper()) + .objectMapper(ObjectMapper(), listOf()) val subject = RedisIntentActivityRepository( mainRedisClientDelegate = JedisClientDelegate(jedisPool),