Skip to content

Commit

Permalink
feat(core): adds 'ValidForSpinnakerVersion' property annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
danielpeach committed Aug 4, 2017
1 parent 052e671 commit 3434d2b
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 15 deletions.
1 change: 1 addition & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ hal config features edit [parameters]
* `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment.
* `--jobs`: Allow Spinnaker to run containers in Kubernetes and Titus as Job stages in pipelines.
* `--no-validate`: (*Default*: `false`) Skip validation.
* `--pipeline-templates`: Enable pipeline template support. Read more at https://github.com/spinnaker/dcd-spec.


---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ protected void executeThis() {

features.setChaos(chaos != null ? chaos : features.isChaos());
features.setJobs(jobs != null ? jobs : features.isJobs());
features.setPipelineTemplates(pipelineTemplates != null ? pipelineTemplates : features.isPipelineTemplates());
features.setPipelineTemplates((pipelineTemplates == null || !pipelineTemplates) ? null : true);

if (originalHash == features.hashCode()) {
AnsiUi.failure("No changes supplied.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public NodeIterator getChildren() {
private boolean chaos;
private boolean entityTags;
private boolean jobs;
private boolean pipelineTemplates;
@ValidForSpinnakerVersion(lowerBound = "1.2.0") private Boolean pipelineTemplates;

public boolean isAuth(DeploymentConfiguration deploymentConfiguration) {
return deploymentConfiguration.getSecurity().getAuthn().isEnabled();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2017 Google, 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.halyard.config.model.v1.node;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This annotation defines a strict subset of Spinnaker versions for which the field it annotates is valid.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ValidForSpinnakerVersion {
String lowerBound();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration;
import com.netflix.spinnaker.halyard.config.model.v1.node.Features;
import com.netflix.spinnaker.halyard.config.model.v1.node.NodeFilter;
import com.netflix.spinnaker.halyard.core.problem.v1.ProblemSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

Expand All @@ -31,6 +32,9 @@ public class FeaturesService {

@Autowired
private DeploymentService deploymentService;

@Autowired
private ValidateService validateService;

public Features getFeatures(String deploymentName) {
NodeFilter filter = new NodeFilter().setDeployment(deploymentName).setFeatures();
Expand All @@ -53,4 +57,9 @@ public void setFeatures(String deploymentName, Features newFeatures) {
DeploymentConfiguration deploymentConfiguration = deploymentService.getDeploymentConfiguration(deploymentName);
deploymentConfiguration.setFeatures(newFeatures);
}

public ProblemSet validateFeatures(String deploymentName) {
NodeFilter filter = new NodeFilter().setDeployment(deploymentName).setFeatures();
return validateService.validateMatchingFilter(filter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2017 Google, 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.halyard.config.validate.v1;

import com.netflix.spinnaker.halyard.config.model.v1.node.*;
import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemSetBuilder;
import com.netflix.spinnaker.halyard.core.problem.v1.Problem;
import com.netflix.spinnaker.halyard.core.registry.v1.Versions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Slf4j
public class FieldValidator extends Validator<Node> {
@Override
public void validate(ConfigProblemSetBuilder p, Node n) {
validateFieldForSpinnakerVersion(p, n);
}

private void validateFieldForSpinnakerVersion(ConfigProblemSetBuilder p, Node n) {
DeploymentConfiguration deploymentConfiguration = n.parentOfType(DeploymentConfiguration.class);
String spinnakerVersion = deploymentConfiguration.getVersion();
if (spinnakerVersion == null) {
return;
}

Class clazz = n.getClass();
Arrays.stream(clazz.getDeclaredFields())
.forEach(field -> {
ValidForSpinnakerVersion annotation = field.getDeclaredAnnotation(ValidForSpinnakerVersion.class);
try {
field.setAccessible(true);
boolean fieldNotValid = field.get(n) != null &&
annotation != null &&
Versions.lessThan(spinnakerVersion, annotation.lowerBound());

if (fieldNotValid) {
p.addProblem(
Problem.Severity.WARNING,
"Field " + clazz.getSimpleName() + "." + field.getName() + " not supported for Spinnaker version " + spinnakerVersion + "."
).setRemediation("Use at least " + annotation.lowerBound() + " (It may not have been released yet).");
}
} catch (IllegalArgumentException /* Probably using nightly build */ | IllegalAccessException /* Probably shouldn't happen */ e) {
log.warn("Error validating field " + clazz.getSimpleName() + "." + field.getName() + ": ", e);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public int runAllValidators(ConfigProblemSetBuilder psBuilder, Node node) {
psBuilder.setNode(node);
int validatorRuns = 0;
for (Validator validator : validators) {
validatorRuns += runMatchingValidators(psBuilder, validator, node, node.getClass());
validatorRuns += runMatchingValidators(psBuilder, validator, node, node.getClass()) ? 1 : 0;
}

return validatorRuns;
Expand All @@ -66,26 +66,24 @@ public int runAllValidators(ConfigProblemSetBuilder psBuilder, Node node) {
* @param node is the subject of validation.
* @param c is some super(inclusive) class of node.
*
* @return # of validators run (for logging purposes).
* @return if the validator ran on the node (for logging purposes).
*/
private int runMatchingValidators(ConfigProblemSetBuilder psBuilder, Validator validator, Node node, Class c) {
int result = 0;

if (c == Node.class) {
return result;
private boolean runMatchingValidators(ConfigProblemSetBuilder psBuilder, Validator validator, Node node, Class c) {
if (c == Object.class) {
return false;
}

try {
Method m = validator.getClass().getMethod("validate", ConfigProblemSetBuilder.class, c);
DaemonTaskHandler.message("Validating " + node.getNodeName() + " with " + validator.getClass().getSimpleName());
m.invoke(validator, psBuilder, node);
result = 1;
} catch (NoSuchMethodException e) {
return true;
} catch (InvocationTargetException | NoSuchMethodException e) {
// Do nothing, odds are most validators don't validate every class.
} catch (InvocationTargetException | IllegalAccessException e) {
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to invoke validate() on \"" + validator.getClass().getSimpleName() + "\" for node \"" + c.getSimpleName(), e);
}

return runMatchingValidators(psBuilder, validator, node, c.getSuperclass()) + result;
return runMatchingValidators(psBuilder, validator, node, c.getSuperclass());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ protected void setProfile(Profile profile, DeploymentConfiguration deploymentCon
profile.appendContents("default.vpc.securityGroups: ");
}

profile.appendContents("pipelineTemplate.enabled: " + deploymentConfiguration.getFeatures().isPipelineTemplates());
profile.appendContents("pipelineTemplate.enabled: "
+ Boolean.toString(deploymentConfiguration.getFeatures().getPipelineTemplates() != null ? deploymentConfiguration.getFeatures().getPipelineTemplates() : false));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ protected void setProfile(Profile profile, DeploymentConfiguration deploymentCon
bindings.put("features.chaos", Boolean.toString(features.isChaos()));
bindings.put("features.jobs", Boolean.toString(features.isJobs()));
bindings.put("features.fiat", Boolean.toString(deploymentConfiguration.getSecurity().getAuthz().isEnabled()));
bindings.put("features.pipelineTemplates", Boolean.toString(features.isPipelineTemplates()));
bindings.put("features.pipelineTemplates", Boolean.toString(features.getPipelineTemplates() != null ? features.getPipelineTemplates() : false ));

// Configure Kubernetes
KubernetesProvider kubernetesProvider = deploymentConfiguration.getProviders().getKubernetes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,13 @@ DaemonTask<Halconfig, Void> setFeatures(@PathVariable String deploymentName,
UpdateRequestBuilder builder = new UpdateRequestBuilder();

builder.setUpdate(() -> featuresService.setFeatures(deploymentName, features));
builder.setSeverity(severity);

Supplier<ProblemSet> doValidate = ProblemSet::new;

if (validate) {
doValidate = () -> featuresService.validateFeatures(deploymentName);
}

builder.setValidate(doValidate);
builder.setRevert(() -> halconfigParser.undoChanges());
Expand Down

0 comments on commit 3434d2b

Please sign in to comment.