Skip to content

Commit

Permalink
feat(pipelinetemplate): Metadata for UI; template protection (#1238)
Browse files Browse the repository at this point in the history
  • Loading branch information
robzienert committed Mar 23, 2017
1 parent 12197ad commit 9003d06
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 10 deletions.
Expand Up @@ -32,6 +32,7 @@
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.render.Renderer;
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.validator.V1TemplateConfigurationSchemaValidator;
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.validator.V1TemplateSchemaValidator;
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.validator.V1TemplateSchemaValidator.SchemaValidatorContext;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.Errors;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.Errors.Error;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -101,7 +102,7 @@ private Map<String, Object> processInternal(Map<String, Object> pipeline) {
List<PipelineTemplate> templates = templateLoader.load(templateConfiguration.getPipeline().getTemplate());
PipelineTemplate template = TemplateMerge.merge(templates);

new V1TemplateSchemaValidator().validate(template, validationErrors);
new V1TemplateSchemaValidator().validate(template, validationErrors, new SchemaValidatorContext(!templateConfiguration.getStages().isEmpty()));
if (validationErrors.hasErrors(request.plan)) {
return validationErrors.toResponse();
}
Expand Down
Expand Up @@ -31,16 +31,22 @@ public static PipelineTemplate merge(List<PipelineTemplate> templates) {
for (PipelineTemplate template : templates) {
appendSource(result, template);

// TODO rz - should validate var types are the same here or elsewhere?
// If any of the templates are set as protected, configurations won't be allowed to alter stages.
if (template.getProtect()) {
result.setProtect(true);
}

result.setVariables(mergeNamedContent(result.getVariables(), template.getVariables()));

mergeConfiguration(result, template);

// TODO rz - might make sense that any child template must use inject syntax / modules.
result.setStages(mergeIdentifiable(result.getStages(), template.getStages()));

result.setModules(mergeIdentifiable(result.getModules(), template.getModules()));
}

// Apply the last template's metadata to the final result
result.setMetadata(templates.get(templates.size() - 1).getMetadata());

return result;
}

Expand Down
Expand Up @@ -29,11 +29,43 @@ public class PipelineTemplate implements VersionedSchema {
private String schema;
private String id;
private String source;
private Metadata metadata = new Metadata();
private Boolean protect = false;
private List<Variable> variables;
private Configuration configuration;
private List<StageDefinition> stages;
private List<TemplateModule> modules;

public static class Metadata {
private String name;
private String description;
private String owner;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public String getOwner() {
return owner;
}

public void setOwner(String owner) {
this.owner = owner;
}
}

public static class Variable implements NamedContent {
private String name;
private String description;
Expand Down Expand Up @@ -146,6 +178,22 @@ public void setSource(String source) {
this.source = source;
}

public Metadata getMetadata() {
return metadata;
}

public void setMetadata(Metadata metadata) {
this.metadata = metadata;
}

public Boolean getProtect() {
return protect;
}

public void setProtect(Boolean protect) {
this.protect = protect;
}

public List<Variable> getVariables() {
return variables;
}
Expand Down
Expand Up @@ -17,18 +17,24 @@

import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.model.TemplateConfiguration;
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.model.TemplateConfiguration.PipelineDefinition;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.EmptyValidatorContext;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.Errors;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.Errors.Error;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.Errors.Severity;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.SchemaValidator;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.ValidatorContext;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.VersionedSchema;

public class V1TemplateConfigurationSchemaValidator implements SchemaValidator {

private static final String SUPPORTED_VERSION = "1";

@Override
public void validate(VersionedSchema configuration, Errors errors) {
validate(configuration, errors, new EmptyValidatorContext());
}

@Override
public void validate(VersionedSchema configuration, Errors errors, ValidatorContext context) {
if (!(configuration instanceof TemplateConfiguration)) {
throw new IllegalArgumentException("Expected TemplateConfiguration");
}
Expand Down
Expand Up @@ -16,17 +16,19 @@
package com.netflix.spinnaker.orca.pipelinetemplate.v1schema.validator;

import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.model.PipelineTemplate;
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.validator.V1TemplateSchemaValidator.SchemaValidatorContext;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.Errors;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.Errors.Error;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.SchemaValidator;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.ValidatorContext;
import com.netflix.spinnaker.orca.pipelinetemplate.validator.VersionedSchema;

public class V1TemplateSchemaValidator implements SchemaValidator {
public class V1TemplateSchemaValidator<T extends SchemaValidatorContext> implements SchemaValidator<T> {

private static final String SUPPORTED_VERSION = "1";

@Override
public void validate(VersionedSchema pipelineTemplate, Errors errors) {
public void validate(VersionedSchema pipelineTemplate, Errors errors, SchemaValidatorContext context) {
if (!(pipelineTemplate instanceof PipelineTemplate)) {
throw new IllegalArgumentException("Expected PipelineTemplate");
}
Expand All @@ -37,6 +39,13 @@ public void validate(VersionedSchema pipelineTemplate, Errors errors) {
.withMessage("template schema version is unsupported: expected '" + SUPPORTED_VERSION + "', got '" + template.getSchemaVersion() + "'"));
}

if (template.getProtect() && context.configHasStages) {
errors.addError(new Error()
.withMessage("Modification of the stage graph (adding, removing, editing) is disallowed")
.withCause("The template being used has marked itself as protected")
);
}

V1SchemaValidationHelper.validateStageDefinitions(template.getStages(), errors, V1TemplateSchemaValidator::location);

// TODO rz - validate variable type & defaultValue combinations
Expand All @@ -45,4 +54,12 @@ public void validate(VersionedSchema pipelineTemplate, Errors errors) {
private static String location(String location) {
return "template:" + location;
}

public static class SchemaValidatorContext implements ValidatorContext {
boolean configHasStages = false;

public SchemaValidatorContext(boolean configHasStages) {
this.configHasStages = configHasStages;
}
}
}
@@ -0,0 +1,19 @@
/*
* Copyright 2017 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.orca.pipelinetemplate.validator;

public class EmptyValidatorContext implements ValidatorContext {
}
Expand Up @@ -15,7 +15,7 @@
*/
package com.netflix.spinnaker.orca.pipelinetemplate.validator;

public interface SchemaValidator {
public interface SchemaValidator<T extends ValidatorContext> {

void validate(VersionedSchema versionedSchema, Errors errors);
void validate(VersionedSchema versionedSchema, Errors errors, T context);
}
@@ -0,0 +1,19 @@
/*
* Copyright 2017 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.orca.pipelinetemplate.validator;

public interface ValidatorContext {
}
Expand Up @@ -29,6 +29,7 @@ class TemplateMergeSpec extends Specification {
PipelineTemplate t1 = new PipelineTemplate().with {
id = 't1'
schema = '1'
protect = true
variables = [
new Variable(name: 'foo', description: 'foo description', type: 'string', defaultValue: 'foo value'),
new Variable(name: 'bar', description: 'bar description', type: 'list', defaultValue: ['bar value'])
Expand Down Expand Up @@ -89,6 +90,7 @@ class TemplateMergeSpec extends Specification {
result.id == 'mergedTemplate'
result.schema == '1'
result.source == 't1'
result.protect
result.variables*.name == ['foo', 'bar']
result.variables.find { it.name == 'foo' }.defaultValue == 'overridden value'
result.configuration.triggers*.name == ['trigger1', 'trigger2']
Expand Down
Expand Up @@ -16,9 +16,11 @@
package com.netflix.spinnaker.orca.pipelinetemplate.v1schema.validator

import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.model.PipelineTemplate
import com.netflix.spinnaker.orca.pipelinetemplate.v1schema.validator.V1TemplateSchemaValidator.SchemaValidatorContext
import com.netflix.spinnaker.orca.pipelinetemplate.validator.Errors
import spock.lang.Specification
import spock.lang.Subject
import spock.lang.Unroll

class V1TemplateSchemaValidatorSpec extends Specification {

Expand All @@ -31,7 +33,7 @@ class V1TemplateSchemaValidatorSpec extends Specification {
def template = new PipelineTemplate(schema: schema)

when:
subject.validate(template, errors)
subject.validate(template, errors, new SchemaValidatorContext(false))

then:
if (hasErrors) {
Expand All @@ -46,4 +48,24 @@ class V1TemplateSchemaValidatorSpec extends Specification {
"1" | false
"2" | true
}

@Unroll
def "should error if configuration defines stages when template is protected"() {
given:
def errors = new Errors()
def template = new PipelineTemplate(schema: '1', protect: true)

when:
subject.validate(template, errors, new SchemaValidatorContext(hasStages))

then:
if (hasStages) {
errors.hasErrors(true)
} else {
!errors.hasErrors(true)
}

where:
hasStages << [true, false]
}
}

0 comments on commit 9003d06

Please sign in to comment.