Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(pipeline/kubernetes): Introduce validator for kubernetes blue/gr…
…een traffic management strategy (#1176) Introduce blue/green strategy as this is a terminology used more often in the industry compared to the Neflix specific phrasing. First step is to deprecate redblack. When creating new pipelines using fron50 api, if traffic management is enabled and redblack is used a warning message is logged. In the future, after the redblack will be completely remove a validation error will be returned
- Loading branch information
1 parent
c214eb5
commit 7b76e6e
Showing
3 changed files
with
268 additions
and
0 deletions.
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
...om/netflix/spinnaker/front50/validator/pipeline/KubernetesBlueGreenStrategyValidator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* Copyright 2022 Armory, 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.front50.validator.pipeline; | ||
|
||
import com.netflix.spinnaker.front50.api.model.pipeline.Pipeline; | ||
import com.netflix.spinnaker.front50.api.validator.PipelineValidator; | ||
import com.netflix.spinnaker.front50.api.validator.ValidatorErrors; | ||
import java.util.List; | ||
import java.util.Map; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Slf4j | ||
public class KubernetesBlueGreenStrategyValidator implements PipelineValidator { | ||
@Override | ||
public void validate(Pipeline pipeline, ValidatorErrors errors) { | ||
|
||
List<Map<String, Object>> stages = pipeline.getStages(); | ||
|
||
if (stages == null || stages.isEmpty()) { | ||
return; | ||
} | ||
|
||
boolean redBlackStrategy = | ||
stages.stream() | ||
.filter(KubernetesBlueGreenStrategyValidator::kubernetesProvider) | ||
.filter(KubernetesBlueGreenStrategyValidator::deployManifestStage) | ||
.map(KubernetesBlueGreenStrategyValidator::getTrafficManagement) | ||
.filter(KubernetesBlueGreenStrategyValidator::trafficManagementEnabled) | ||
.map(KubernetesBlueGreenStrategyValidator::getTrafficManagementOptions) | ||
.anyMatch(KubernetesBlueGreenStrategyValidator::redBlackStrategy); | ||
|
||
if (redBlackStrategy) { | ||
log.warn( | ||
"Kubernetes traffic management redblack strategy is deprecated and will be removed soon. Please use bluegreen instead"); | ||
// TODO uncomment the line below when we decide to fail the process. also update the test | ||
// errors.reject("Kubernetes traffic management redblack strategy is deprecated and will | ||
// be removed soon. Please use bluegreen instead"); | ||
} | ||
} | ||
|
||
private static Map<String, Object> getTrafficManagementOptions( | ||
Map<String, Object> trafficManagement) { | ||
return (Map<String, Object>) trafficManagement.get("options"); | ||
} | ||
|
||
private static boolean trafficManagementEnabled(Map<String, Object> trafficManagement) { | ||
return Boolean.TRUE.equals(trafficManagement.get("enabled")); | ||
} | ||
|
||
private static Map<String, Object> getTrafficManagement(Map<String, Object> stage) { | ||
return (Map<String, Object>) stage.get("trafficManagement"); | ||
} | ||
|
||
private static boolean kubernetesProvider(Map<String, Object> stage) { | ||
return "kubernetes".equals(stage.get("cloudProvider")); | ||
} | ||
|
||
private static boolean deployManifestStage(Map<String, Object> stage) { | ||
return "deployManifest".equals(stage.get("type")); | ||
} | ||
|
||
private static boolean redBlackStrategy(Map<String, Object> stage) { | ||
return "redblack".equals(stage.get("strategy")); | ||
} | ||
} |
182 changes: 182 additions & 0 deletions
182
...flix/spinnaker/front50/validator/pipeline/KubernetesBlueGreenStrategyValidatorSpec.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
/* | ||
* Copyright 2022 Armory, 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.front50.validator.pipeline | ||
|
||
import ch.qos.logback.classic.Level | ||
import ch.qos.logback.classic.Logger | ||
import ch.qos.logback.classic.LoggerContext | ||
import ch.qos.logback.classic.spi.ILoggingEvent | ||
import ch.qos.logback.core.read.ListAppender | ||
import com.netflix.spinnaker.front50.api.model.pipeline.Pipeline | ||
import com.netflix.spinnaker.front50.api.validator.PipelineValidator | ||
import com.netflix.spinnaker.front50.api.validator.ValidatorErrors | ||
import org.slf4j.LoggerFactory | ||
import spock.lang.Specification | ||
|
||
class KubernetesBlueGreenStrategyValidatorSpec extends Specification { | ||
|
||
private static MemoryAppender memoryAppender | ||
|
||
void setup() { | ||
Logger logger = (Logger) LoggerFactory.getLogger("com.netflix.spinnaker.front50.validator.pipeline") | ||
memoryAppender = new MemoryAppender() | ||
memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory()) | ||
logger.setLevel(Level.DEBUG) | ||
logger.addAppender(memoryAppender) | ||
memoryAppender.start() | ||
} | ||
|
||
|
||
void cleanup() { | ||
memoryAppender.reset() | ||
memoryAppender.stop() | ||
} | ||
|
||
def "should not return error when stage null or empty"() { | ||
setup: | ||
def pipeline = new Pipeline() | ||
def errors = new ValidatorErrors() | ||
|
||
when: | ||
PipelineValidator validator = new KubernetesBlueGreenStrategyValidator() | ||
validator.validate(pipeline, errors) | ||
|
||
then: | ||
!errors.hasErrors() | ||
|
||
when: | ||
pipeline.setStages(List.of()) | ||
validator.validate(pipeline, errors) | ||
|
||
then: | ||
!errors.hasErrors() | ||
} | ||
|
||
def "should not return error cloud provider is not Kubernetes"() { | ||
setup: | ||
def pipeline = new Pipeline() | ||
Map<String, Object> trafficManagement = new LinkedHashMap<>() | ||
trafficManagement.put("enabled", true) | ||
Map<String, Object> options = new LinkedHashMap<>() | ||
options | ||
pipeline.setStages(List.of(Map.of("cloudProvider","aws", "type", "deploy"))) | ||
def errors = new ValidatorErrors() | ||
|
||
when: | ||
PipelineValidator validator = new KubernetesBlueGreenStrategyValidator() | ||
validator.validate(pipeline, errors) | ||
|
||
then: | ||
!errors.hasErrors() | ||
} | ||
|
||
def "should not return error when there is no deployManifestStage"() { | ||
setup: | ||
def pipeline = new Pipeline() | ||
Map<String, Object> trafficManagement = new LinkedHashMap<>() | ||
trafficManagement.put("enabled", true) | ||
Map<String, Object> options = new LinkedHashMap<>() | ||
options | ||
pipeline.setStages(List.of(Map.of("cloudProvider","kubernetes", "type", "patchManifest"))) | ||
def errors = new ValidatorErrors() | ||
|
||
when: | ||
PipelineValidator validator = new KubernetesBlueGreenStrategyValidator() | ||
validator.validate(pipeline, errors) | ||
|
||
then: | ||
!errors.hasErrors() | ||
} | ||
|
||
def "should not return error when trafficManagement not enabled"() { | ||
setup: | ||
def pipeline = new Pipeline() | ||
Map<String, Object> trafficManagement = new LinkedHashMap<>() | ||
trafficManagement.put("enabled", true) | ||
Map<String, Object> options = new LinkedHashMap<>() | ||
options | ||
pipeline.setStages(List.of(Map.of("cloudProvider","kubernetes", "type", "deployManifest", "trafficManagement", Map.of("enabled", false)))) | ||
def errors = new ValidatorErrors() | ||
|
||
when: | ||
PipelineValidator validator = new KubernetesBlueGreenStrategyValidator() | ||
validator.validate(pipeline, errors) | ||
|
||
then: | ||
!errors.hasErrors() | ||
} | ||
|
||
def "should not return error when redblack is not selected"() { | ||
setup: | ||
def pipeline = new Pipeline() | ||
Map<String, Object> trafficManagement = new LinkedHashMap<>() | ||
trafficManagement.put("enabled", true) | ||
Map<String, Object> options = new LinkedHashMap<>() | ||
options | ||
pipeline.setStages(List.of(Map.of("cloudProvider","kubernetes", "type", "deployManifest", "trafficManagement", Map.of("enabled", true, "options", Map.of("strategy", "bluegreen"))))) | ||
def errors = new ValidatorErrors() | ||
|
||
when: | ||
PipelineValidator validator = new KubernetesBlueGreenStrategyValidator() | ||
validator.validate(pipeline, errors) | ||
|
||
then: | ||
!errors.hasErrors() | ||
} | ||
|
||
def "should return error when redblack is used"() { | ||
setup: | ||
def pipeline = new Pipeline() | ||
Map<String, Object> trafficManagement = new LinkedHashMap<>() | ||
trafficManagement.put("enabled", true) | ||
Map<String, Object> options = new LinkedHashMap<>() | ||
options | ||
pipeline.setStages(List.of(Map.of("cloudProvider","kubernetes", | ||
"type", "deployManifest", | ||
"trafficManagement", Map.of("enabled", true, "options", Map.of("strategy", "redblack"))))) | ||
def errors = new ValidatorErrors() | ||
|
||
when: | ||
PipelineValidator validator = new KubernetesBlueGreenStrategyValidator() | ||
validator.validate(pipeline, errors) | ||
|
||
then: | ||
memoryAppender.getSize() ==1 | ||
memoryAppender.contains("Kubernetes traffic management redblack strategy is deprecated and will be removed soon. Please use bluegreen instead", Level.WARN) | ||
!errors.hasErrors() | ||
// errors.getAllErrors().size() == 1 | ||
// errors.getAllErrorsMessage().equals("Kubernetes traffic management redblack strategy is deprecated and will be removed soon. Please use bluegreen instead") | ||
} | ||
|
||
class MemoryAppender extends ListAppender<ILoggingEvent> { | ||
void reset() { | ||
this.list.clear(); | ||
} | ||
int getSize() { | ||
return this.list.size(); | ||
} | ||
|
||
boolean contains(String string, Level level) { | ||
return this.list.stream() | ||
.filter({ event -> | ||
event.toString().contains(string) | ||
}).anyMatch( { event -> | ||
event.getLevel().equals(level) | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters