Skip to content

Commit

Permalink
fix(clouddriver): Fixing trigger interaction inside deploy strategy c…
Browse files Browse the repository at this point in the history
…odepaths
  • Loading branch information
robzienert authored and robfletcher committed Feb 2, 2018
1 parent 226e9a7 commit 5afdaa8
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@

package com.netflix.spinnaker.orca.kato.pipeline

import javax.annotation.Nonnull
import com.netflix.spinnaker.orca.ExecutionStatus
import com.netflix.spinnaker.orca.Task
import com.netflix.spinnaker.orca.TaskResult
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.CloneServerGroupStage
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.CreateServerGroupStage
import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder
import com.netflix.spinnaker.orca.pipeline.TaskNode
import com.netflix.spinnaker.orca.pipeline.model.PipelineTrigger
import com.netflix.spinnaker.orca.pipeline.model.Stage
import com.netflix.spinnaker.orca.pipeline.model.Trigger
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.stereotype.Component

import javax.annotation.Nonnull

import static com.netflix.spinnaker.orca.pipeline.model.Execution.ExecutionType.PIPELINE
import static com.netflix.spinnaker.orca.pipeline.model.SyntheticStageOwner.STAGE_BEFORE

Expand All @@ -50,38 +54,18 @@ class ParallelDeployStage implements StageDefinitionBuilder {
builder.withTask("completeParallelDeploy", CompleteParallelDeployTask)
}

@Nonnull List<Stage> parallelStages(
@Nonnull Stage stage) {
@Nonnull List<Stage> parallelStages(@Nonnull Stage stage) {
parallelContexts(stage).collect { context ->
def type = isClone(stage) ? CloneServerGroupStage.PIPELINE_CONFIG_TYPE : CreateServerGroupStage.PIPELINE_CONFIG_TYPE
newStage(stage.execution, type, context.name as String, context, stage, STAGE_BEFORE)
}
}

@CompileDynamic
protected Map<String, Object> clusterContext(Stage stage, Map defaultStageContext, Map cluster) {
def type = isClone(stage) ? CloneServerGroupStage.PIPELINE_CONFIG_TYPE : CreateServerGroupStage.PIPELINE_CONFIG_TYPE

if (cluster.providerType && !(cluster.providerType in ['aws', 'titus'])) {
type += "_$cluster.providerType"
}

String baseName = isClone(stage) ? 'Clone' : 'Deploy'
String name = cluster.region ? "$baseName in ${cluster.region}" : "$baseName in ${(cluster.availabilityZones as Map).keySet()[0]}"

return defaultStageContext + [
account: cluster.account ?: cluster.credentials ?: stage.context.account ?: stage.context.credentials,
cluster: cluster,
type : type,
name : name
]
}

@CompileDynamic
protected Collection<Map<String, Object>> parallelContexts(Stage stage) {
if (stage.execution.type == PIPELINE) {
Map trigger = stage.execution.trigger
if (trigger?.parameters?.strategy == true) {
Trigger trigger = stage.execution.trigger
if (trigger?.parameters?.strategy == true && trigger instanceof PipelineTrigger) {
Stage parentStage = trigger.parentExecution.stages.find {
it.id == trigger.parameters.parentStageId
}
Expand All @@ -96,6 +80,10 @@ class ParallelDeployStage implements StageDefinitionBuilder {
}
// the strategy can set it's own enable / disable traffic settings to override the one in advanced settings
if (stage.context.trafficOptions && stage.context.trafficOptions != 'inherit') {
if (!cluster.containsKey("suspendedProcesses")) {
cluster.suspendedProcesses = []
}

String addToLoadBalancer = 'AddToLoadBalancer'.toString()
if (stage.context.trafficOptions == 'enable') {
// explicitly enable traffic
Expand Down Expand Up @@ -144,10 +132,29 @@ class ParallelDeployStage implements StageDefinitionBuilder {
}
}

@CompileDynamic
protected Map<String, Object> clusterContext(Stage stage, Map defaultStageContext, Map cluster) {
def type = isClone(stage) ? CloneServerGroupStage.PIPELINE_CONFIG_TYPE : CreateServerGroupStage.PIPELINE_CONFIG_TYPE

if (cluster.providerType && !(cluster.providerType in ['aws', 'titus'])) {
type += "_$cluster.providerType"
}

String baseName = isClone(stage) ? 'Clone' : 'Deploy'
String name = cluster.region ? "$baseName in ${cluster.region}" : "$baseName in ${(cluster.availabilityZones as Map).keySet()[0]}"

return defaultStageContext + [
account: cluster.account ?: cluster.credentials ?: stage.context.account ?: stage.context.credentials,
cluster: cluster,
type : type,
name : name
]
}

@CompileDynamic
private boolean isClone(Stage stage) {
if (stage.execution.type == PIPELINE) {
Map trigger = stage.execution.trigger
Trigger trigger = stage.execution.trigger

if (trigger?.parameters?.clone == true) {
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ trait DeploymentDetailsAware {
def parentPipelineExecution = getParentPipelineExecution(execution)

if (parentPipelineExecution) {
String parentPipelineStageId = execution.trigger?.parentPipelineStageId
String parentPipelineStageId = (execution.trigger as PipelineTrigger).parentPipelineStageId
Stage parentPipelineStage = parentPipelineExecution.stages?.find {
it.type == "pipeline" && it.id == parentPipelineStageId
}
Expand Down Expand Up @@ -130,7 +130,7 @@ trait DeploymentDetailsAware {
private Execution getParentPipelineExecution(Execution execution) {
// The initial stage execution is a Pipeline, and the ancestor executions are Maps.
if (execution.type == PIPELINE && execution.trigger instanceof PipelineTrigger) {
return execution.trigger.parentExecution
return (execution.trigger as PipelineTrigger).parentExecution
}
return null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,27 @@

package com.netflix.spinnaker.orca.kato.pipeline

import com.netflix.spinnaker.orca.pipeline.model.Execution
import com.netflix.spinnaker.orca.pipeline.model.JenkinsTrigger
import com.netflix.spinnaker.orca.pipeline.model.PipelineTrigger
import com.netflix.spinnaker.orca.pipeline.model.Stage
import spock.lang.Specification
import spock.lang.Unroll

import static com.netflix.spinnaker.orca.test.model.ExecutionBuilder.pipeline
import static com.netflix.spinnaker.orca.test.model.ExecutionBuilder.stage

class ParallelDeployStageSpec extends Specification {

@Unroll
def "should build contexts corresponding to cluster configuration(s)"() {
given:
def bakeStage = new Stage(Execution.newPipeline("orca"), "deploy", "Deploy!", stageContext)
def pipeline = pipeline {
trigger = new JenkinsTrigger("master", "job", 1, null, [:], new JenkinsTrigger.BuildInfo(
"job", 1, "http://url", [], [], "job", false, "result"
), "[email protected]", [:], [])
application = "orca"
}
def bakeStage = new Stage(pipeline, "deploy", "Deploy!", stageContext)
def builder = new ParallelDeployStage()

when:
Expand All @@ -45,6 +56,135 @@ class ParallelDeployStageSpec extends Specification {
[account: "prod", restrictedExecutionWindow: [:], cluster: [availabilityZones: ["europe-west1-b": []], cloudProvider: "gce"], type: "createServerGroup", name: "Deploy in europe-west1-b"]]
}

@Unroll
def "pipeline strategy should #data.scenario"() {
given:
def parentPipeline = pipeline {
trigger = new JenkinsTrigger("master", "job", 1, null, [:], new JenkinsTrigger.BuildInfo(
"job", 1, "http://url", [], [], "job", false, "result"
), "[email protected]", [:], [])
application = "orca"
stage {
name = "parent stage"
type = "createServerGroup"
refId = "parentStage"
context = [
account: "prod",
availabilityZones: ["us-west-1": []],
cloudProvider: "aws",
restrictedExecutionWindow: [:]
].with { putAll(data.parentStageContext); it }
}
}

def strategyPipeline = pipeline {
trigger = new PipelineTrigger(
parentPipeline,
parentPipeline.stageByRef("parentStage").id,
"[email protected]",
data.triggerParams.with { putAll([strategy: true, parentStageId: parentPipeline.stageByRef("parentStage").id]); it },
[]
)
}

and:
def deployStage = new Stage(strategyPipeline, "deploy", "Deploy!", data.stageContext)
def builder = new ParallelDeployStage()

when:
def parallelContexts = builder.parallelContexts(deployStage)

then:
parallelContexts == data.expectedParallelContexts

where:
data << [
[
scenario: "pull contexts from trigger when missing from parent",
parentStageContext: [:],
stageContext: [:],
triggerParams: [
amiName: "ami-1234"
],
expectedParallelContexts: [
[
name: "Deploy in us-west-1",
cluster: [
account: "prod",
availabilityZones: ["us-west-1": []],
cloudProvider: "aws",
restrictedExecutionWindow: [:],
strategy: "none",
amiName: "ami-1234",
],
type: "createServerGroup",
account: "prod"
]
]
],
[
scenario: "inherit traffic options from parent",
parentStageContext: [
amiName: "ami-1234",
trafficOptions: "enable",
suspendedProcesses: [],
],
stageContext: [
trafficOptions: "inherit"
],
triggerParams: [:],
expectedParallelContexts: [
[
name: "Deploy in us-west-1",
cluster: [
account: "prod",
availabilityZones: ["us-west-1": []],
cloudProvider: "aws",
restrictedExecutionWindow: [:],
strategy: "none",
amiName: "ami-1234",
trafficOptions: "enable",
suspendedProcesses: [],
],
trafficOptions: "inherit",
type: "createServerGroup",
account: "prod"
]
]
],
[
scenario: "override traffic options in parent",
parentStageContext: [
amiName: "ami-1234",
trafficOptions: "disable",
suspendedProcesses: ['AddToLoadBalancer'],
],
stageContext: [
trafficOptions: "enable"
],
triggerParams: [:],
expectedParallelContexts: [
[
name: "Deploy in us-west-1",
cluster: [
account: "prod",
availabilityZones: ["us-west-1": []],
cloudProvider: "aws",
restrictedExecutionWindow: [:],
strategy: "none",
amiName: "ami-1234",
trafficOptions: "disable",
suspendedProcesses: []
],
account: "prod",
trafficOptions: "enable",
type: "createServerGroup",
]
]
]
]
}

Map deployStageContext(String account, String cloudProvider, String... availabilityZones) {
def context = ["account": account, restrictedExecutionWindow: [:]]
if (availabilityZones.size() == 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ExecutionBuilder {
@DelegatesTo(value = Execution, strategy = DELEGATE_FIRST)
Closure builder = {}) {
def pipeline = Execution.newPipeline("covfefe")
pipeline.trigger = new ManualTrigger(null, null, [:], [], [])
pipeline.trigger = new ManualTrigger(null, "[email protected]", [:], [], [])
pipeline.buildTime = currentTimeMillis()

builder.delegate = pipeline
Expand Down

0 comments on commit 5afdaa8

Please sign in to comment.