Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(preconfigured): Allow PreconfiguredJobStage parameters to map to one-dimensional array property values (bp #3830) #3846

Merged
merged 1 commit into from
Aug 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import org.springframework.stereotype.Component

import javax.annotation.Nonnull

import java.util.List

@Component
class PreconfiguredJobStage extends RunJobStage {

Expand Down Expand Up @@ -95,13 +97,38 @@ class PreconfiguredJobStage extends RunJobStage {
String[] props = mapping.split(/\./)
Object current = root
for (int i = 0; i < props.length - 1; i++) {
Object next = current[props[i]]
if (next == null) {
throw new IllegalArgumentException("no property ${props[i]} on $current")
Object next
if(props[i] ==~ /.*\[\d+\]$/) {
next = getValueFromArrayExpression(current, props[i])
} else {
next = current[props[i]]
if (next == null) {
throw new IllegalArgumentException("no property ${props[i]} on $current")
}
}
current = next
}
current[props.last()] = value
}

private static Object getValueFromArrayExpression(Object root, String expression) {
// TODO: Do we need to handle arrays nested in other arrays?
String[] parts = expression.split(/[\[\]]/)
String propName = parts[0]
int index = -1
try {
index = Integer.parseInt(parts[1])
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Unable to parse index from expresion ${expression}", ex)
}
List<Object> nextList = root[propName]
if (nextList == null) {
throw new IllegalArgumentException("no property ${propName} on $root")
}
if (nextList.size() <= index || index < 0) {
throw new IllegalArgumentException("Invalid index $index for list $propName")
}
return nextList.get(index)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import com.netflix.spinnaker.orca.api.preconfigured.jobs.PreconfiguredJobStagePa
import com.netflix.spinnaker.orca.clouddriver.service.JobService
import com.netflix.spinnaker.orca.clouddriver.config.KubernetesPreconfiguredJobProperties
import com.netflix.spinnaker.orca.clouddriver.tasks.job.DestroyJobTask
import io.kubernetes.client.models.V1Container
import io.kubernetes.client.models.V1EnvVar
import io.kubernetes.client.models.V1Job
import spock.lang.Specification

Expand Down Expand Up @@ -99,4 +101,127 @@ class PreconfiguredJobStageSpec extends Specification {
// verify that stage manifest has the correctly overridden name
stage.getContext().get("manifest").metadata.name == overriddenName
}

def "setNestedValue can handle array property mappings"() {
given:
def manifestEnvValue = "defaultValue"
def overriddenValue = "newValue"
def stage = stage {
type = "test"
context = [account: "test"]
}
def property = new KubernetesPreconfiguredJobProperties(
enabled: true,
label: "test",
type: "test",
cloudProvider: "kubernetes",
parameters: [
new PreconfiguredJobStageParameter(
mapping: "manifest.spec.template.spec.containers[0].env[0].value",
defaultValue: overriddenValue,
name: "envVariable"
)
],
manifest: new V1Job(spec: [template: [spec: [containers: [new V1Container(env: [new V1EnvVar(name: "foo", value: manifestEnvValue)])]]]])
)

def jobService = Mock(JobService) {
2 * getPreconfiguredStages() >> {
return [
property
]
}
}

when:
PreconfiguredJobStage preconfiguredJobStage = new PreconfiguredJobStage(Mock(DestroyJobTask), [], Optional.of(jobService))
preconfiguredJobStage.buildTaskGraph(stage)

then:
// verify that underlying job configuration hasn't been modified
def preconfiguredJob = (KubernetesPreconfiguredJobProperties) jobService.getPreconfiguredStages().get(0)
preconfiguredJob.getManifest().getSpec().getTemplate().getSpec().getContainers()[0].getEnv()[0].getValue() == manifestEnvValue
// verify that stage manifest has the correctly overridden name
stage.getContext().get("manifest").spec.template.spec.containers[0].env[0].value == overriddenValue
}

def "setNestedValue throws an error if the index is invalid"() {
given:
def manifestEnvValue = "defaultValue"
def overriddenValue = "newValue"
def stage = stage {
type = "test"
context = [account: "test"]
}
def property = new KubernetesPreconfiguredJobProperties(
enabled: true,
label: "test",
type: "test",
cloudProvider: "kubernetes",
parameters: [
new PreconfiguredJobStageParameter(
mapping: "manifest.spec.template.spec.containers[0].env[1].value",
defaultValue: overriddenValue,
name: "envVariable"
)
],
manifest: new V1Job(spec: [template: [spec: [containers: [new V1Container(env: [new V1EnvVar(name: "foo", value: manifestEnvValue)])]]]])
)

def jobService = Mock(JobService) {
1 * getPreconfiguredStages() >> {
return [
property
]
}
}

when:
PreconfiguredJobStage preconfiguredJobStage = new PreconfiguredJobStage(Mock(DestroyJobTask), [], Optional.of(jobService))
preconfiguredJobStage.buildTaskGraph(stage)

then:
def ex = thrown(IllegalArgumentException)
assert ex.getMessage().startsWith("Invalid index 1 for list")
}

def "setNestedValue throws an error if an array property name is invalid"() {
given:
def manifestEnvValue = "defaultValue"
def overriddenValue = "newValue"
def stage = stage {
type = "test"
context = [account: "test"]
}
def property = new KubernetesPreconfiguredJobProperties(
enabled: true,
label: "test",
type: "test",
cloudProvider: "kubernetes",
parameters: [
new PreconfiguredJobStageParameter(
mapping: "manifest.spec.template.spec.containers[0].missingProperty[0].value",
defaultValue: overriddenValue,
name: "envVariable"
)
],
manifest: new V1Job(spec: [template: [spec: [containers: [new V1Container(env: [new V1EnvVar(name: "foo", value: manifestEnvValue)])]]]])
)

def jobService = Mock(JobService) {
1 * getPreconfiguredStages() >> {
return [
property
]
}
}

when:
PreconfiguredJobStage preconfiguredJobStage = new PreconfiguredJobStage(Mock(DestroyJobTask), [], Optional.of(jobService))
preconfiguredJobStage.buildTaskGraph(stage)

then:
def ex = thrown(IllegalArgumentException)
assert ex.getMessage().startsWith("no property missingProperty on")
}
}