Skip to content

Commit

Permalink
feat(pipeline_templates): Support jinja expressions in template varia…
Browse files Browse the repository at this point in the history
…bles (#1571)

Imagine the following managed pipeline that is triggered with:
```
shouldWait: true
   regions: us-west-2,us-east-1
```
```
---
schema: "1"
id: waitChain
variables:
- name: shouldWait
  defaultValue: "{{ trigger.parameters.shouldWait == true }}"
- name: regions
  defaultValue: "{{ trigger.parameters.regions | split(',') }}"
stages:
- id: wait1
  type: wait
  config:
    waitTime: 1
  when:
  - "{{ shouldWait == true }}"
- id: bake
  type: bake
  dependsOn: [wait1]
  config:
    ...
    regions: |
      {% for region in regions %}
      - "{{ region }}"
      {% endfor %}
  when:
  - "{{ regions | length > 0 }}"
```
  • Loading branch information
ajordens committed Aug 24, 2017
1 parent a89c57b commit 64fee8b
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,28 @@ private PipelineTemplate getPipelineTemplate(TemplatedPipelineRequest request, T
setTemplateSourceWithJinja(request);
List<PipelineTemplate> templates = templateLoader.load(templateConfiguration.getPipeline().getTemplate());

return TemplateMerge.merge(templates);
PipelineTemplate pipelineTemplate = TemplateMerge.merge(templates);

// ensure that any expressions contained with template variables are rendered
RenderContext context = new DefaultRenderContext(
templateConfiguration.getPipeline().getApplication(), pipelineTemplate, request.getTrigger()
);
renderTemplateVariables(context, pipelineTemplate);

return pipelineTemplate;
}

private void renderTemplateVariables(RenderContext renderContext, PipelineTemplate pipelineTemplate) {
if (pipelineTemplate.getVariables() == null) {
return;
}

pipelineTemplate.getVariables().forEach(v -> {
Object value = v.getDefaultValue();
if (value != null && value instanceof String) {
v.setDefaultValue(renderer.renderGraph(value.toString(), renderContext));
}
});
}

private void setTemplateSourceWithJinja(TemplatedPipelineRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.netflix.spinnaker.orca.pipelinetemplate.exceptions.TemplateLoaderException;
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.model.PipelineTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

Expand All @@ -32,6 +34,8 @@

@Component
public class HttpTemplateSchemeLoader implements TemplateSchemeLoader {
private final Logger log = LoggerFactory.getLogger(getClass());

private final ObjectMapper jsonObjectMapper;
private final ObjectMapper yamlObjectMapper;

Expand All @@ -56,7 +60,11 @@ public PipelineTemplate load(URI uri) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
Stream<String> stream = reader.lines()) {
ObjectMapper objectMapper = isJson(uri) ? jsonObjectMapper : yamlObjectMapper;
return objectMapper.readValue(stream.collect(Collectors.joining("\n")), PipelineTemplate.class);

String template = stream.collect(Collectors.joining("\n"));
log.debug("Loaded Template ({}):\n{}", uri, template);

return objectMapper.readValue(template, PipelineTemplate.class);
} catch (Exception e) {
throw new TemplateLoaderException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.netflix.spectator.api.Clock
import com.netflix.spectator.api.Registry
import com.netflix.spectator.api.Timer
import com.netflix.spinnaker.orca.extensionpoint.pipeline.PipelinePreprocessor
import com.netflix.spinnaker.orca.front50.Front50Service
import com.netflix.spinnaker.orca.pipelinetemplate.loader.FileTemplateSchemeLoader
import com.netflix.spinnaker.orca.pipelinetemplate.loader.TemplateLoader
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.model.PipelineTemplate
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.render.DefaultRenderContext
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.render.JinjaRenderer
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.render.Renderer
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.render.YamlRenderedValueConverter
import org.unitils.reflectionassert.ReflectionComparatorMode
import org.yaml.snakeyaml.Yaml
import spock.lang.Specification
import spock.lang.Subject
import spock.lang.Unroll
Expand All @@ -38,7 +41,9 @@ class PipelineTemplatePipelinePreprocessorSpec extends Specification {

TemplateLoader templateLoader = new TemplateLoader([new FileTemplateSchemeLoader(objectMapper)])

Renderer renderer = new JinjaRenderer(objectMapper, Mock(Front50Service), [])
Renderer renderer = new JinjaRenderer(
new YamlRenderedValueConverter(new Yaml()), objectMapper, Mock(Front50Service), []
)

Registry registry = Mock() {
clock() >> Mock(Clock) {
Expand All @@ -48,7 +53,7 @@ class PipelineTemplatePipelinePreprocessorSpec extends Specification {
}

@Subject
PipelinePreprocessor subject = new PipelineTemplatePipelinePreprocessor(
PipelineTemplatePipelinePreprocessor subject = new PipelineTemplatePipelinePreprocessor(
objectMapper,
templateLoader,
renderer,
Expand Down Expand Up @@ -222,6 +227,33 @@ class PipelineTemplatePipelinePreprocessorSpec extends Specification {
result.errors != null
}

@Unroll
def 'should render jinja expressions contained within template variables'() {
given:
def pipelineTemplate = new PipelineTemplate(variables: variables.collect {
new PipelineTemplate.Variable(defaultValue: it)
})

def renderContext = new DefaultRenderContext("spinnaker", pipelineTemplate, [
parameters: [
"list" : "us-west-2,us-east-1",
"boolean": "true",
"string" : "this is a string"
]
])

when:
subject.renderTemplateVariables(renderContext, pipelineTemplate)

then:
pipelineTemplate.variables*.defaultValue == expectedDefaultValues

where:
variables || expectedDefaultValues
["string1", "string2"] || ["string1", "string2"]
["{{ trigger.parameters.string }}", "string2"] || ["this is a string", "string2"]
["{{ trigger.parameters.list | split(',') }}", "string2"] || [["us-west-2", "us-east-1"], "string2"]
}

Map<String, Object> createTemplateRequest(String templatePath, Map<String, Object> variables = [:], List<Map<String, Object>> stages = [], boolean plan = false) {
return [
Expand Down

0 comments on commit 64fee8b

Please sign in to comment.