Permalink
Browse files

Merge branch 'docker-compose-validation'

  • Loading branch information...
No3x committed Jan 10, 2019
2 parents 42caa4e + d33afe8 commit 4c7d5466f42e24be55a96730d41865551fc697dd
@@ -69,6 +69,8 @@ dependencies {
implementation 'org.apache.commons:commons-lang3:3.4'
implementation 'com.github.docker-java:docker-java:3.1.0-rc-7'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-validation'
testCompile 'org.hibernate.validator:hibernate-validator-test-utils:6.0.13.Final'
implementation 'com.github.rozidan:logger-spring-boot:b77b432764'
implementation 'com.github.palantir.docker-compose-rule:docker-compose-rule-junit4:0.34.0'
testCompile "org.assertj:assertj-core:3.11.1"
@@ -2,6 +2,7 @@

import com.cloudcomputing.docker.limits.io.DockerComposeReader;
import com.cloudcomputing.docker.limits.io.DockerComposeWriter;
import com.cloudcomputing.docker.limits.model.validator.DockerComposeValidator;
import com.cloudcomputing.docker.limits.services.compose.DockerComposeService;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -26,6 +27,8 @@
private DockerComposeWriter dockerComposeWriter;
@MockBean
private DockerComposeService dockerComposeServiceMock;
@MockBean
private DockerComposeValidator dockerComposeValidator;
@Autowired
private DockerComposeFacade dockerComposeFacade;

@@ -40,6 +43,8 @@ public void startDockerComposeFile() throws IOException {
verifyNoMoreInteractions(dockerComposeReader);
verify(dockerComposeWriter, times(1)).write(any(), any());
verifyNoMoreInteractions(dockerComposeWriter);
verify(dockerComposeValidator, times(1)).validate(any());
verifyNoMoreInteractions(dockerComposeValidator);
// Verify Interaction with mock: they don't call the real method
verify(dockerComposeServiceMock, times(1)).startComposeFile(any());
verifyNoMoreInteractions(dockerComposeServiceMock);
@@ -3,6 +3,7 @@
import com.cloudcomputing.docker.limits.io.DockerComposeReader;
import com.cloudcomputing.docker.limits.io.DockerComposeWriter;
import com.cloudcomputing.docker.limits.model.io.DockerCompose;
import com.cloudcomputing.docker.limits.model.validator.DockerComposeValidator;
import com.cloudcomputing.docker.limits.services.compose.DockerComposeService;
import com.github.rozidan.springboot.logger.Loggable;
import org.apache.commons.io.FileUtils;
@@ -23,34 +24,29 @@
private final DockerComposeReader dockerComposeReader;
private final DockerComposeWriter dockerComposeWriter;
private final DockerComposeService dockerComposeService;
private final DockerComposeValidator dockerComposeValidator;

@Autowired
public DockerComposeFacade(DockerComposeReader dockerComposeReader, DockerComposeWriter dockerComposeWriter, DockerComposeService dockerComposeService) {
public DockerComposeFacade(DockerComposeReader dockerComposeReader, DockerComposeWriter dockerComposeWriter, DockerComposeService dockerComposeService, DockerComposeValidator dockerComposeValidator) {
this.dockerComposeReader = dockerComposeReader;
this.dockerComposeWriter = dockerComposeWriter;
this.dockerComposeService = dockerComposeService;
this.dockerComposeValidator = dockerComposeValidator;
}

public void startDockerComposeFile(File dockerComposeFile) {
try {
final DockerCompose dockerCompose = readDockerCompose(dockerComposeFile);
final InputStream dockerComposeYML = FileUtils.openInputStream(dockerComposeFile);
final DockerCompose dockerCompose = dockerComposeReader.read(dockerComposeYML);
final File usersLatestDockerCompose = new File(FileUtils.getTempDirectory(), dockerCompose.getHsbUsername());
//TODO: validate labels
writeAndStartUsersDockerCompose(dockerCompose, usersLatestDockerCompose);
dockerComposeValidator.validate(dockerCompose);
dockerComposeWriter.write(usersLatestDockerCompose, dockerCompose);
dockerComposeService.startComposeFile(usersLatestDockerCompose);
} catch (IOException e) {
logger.error("IO Failure", e);
}
}

private void writeAndStartUsersDockerCompose(DockerCompose dockerCompose, File usersLatestDockerCompose) throws IOException {
dockerComposeWriter.write(usersLatestDockerCompose, dockerCompose);
dockerComposeService.startComposeFile(usersLatestDockerCompose);
}

private DockerCompose readDockerCompose(File dockerComposeFile) throws IOException {
final InputStream dockerComposeYML = FileUtils.openInputStream(dockerComposeFile);
return dockerComposeReader.read(dockerComposeYML);
}


}
@@ -0,0 +1,21 @@
package com.cloudcomputing.docker.limits.model.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { ContainsLabelKeysValidator.class })
public @interface ContainsLabelKeys {
String message() default "{com.cloudcomputing.docker.limits.model.validator.ContainsLabelKeys.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
String[] value() default {};
}
@@ -0,0 +1,51 @@
package com.cloudcomputing.docker.limits.model.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Collection;

public class ContainsLabelKeysValidator implements ConstraintValidator<ContainsLabelKeys, Collection<String>> {
private static final String LABEL_KV_SEPARATOR = "=";
String[] requiredKeys;
String messageTemplate;

@Override
public void initialize(ContainsLabelKeys constraintAnnotation) {
requiredKeys = constraintAnnotation.value();
messageTemplate = constraintAnnotation.message();
}

/**
* Implements the validation logic.
* The state of {@code value} must not be altered.
* <p>
* This method can be accessed concurrently, thread-safety must be ensured
* by the implementation.
*
* @param value object to validate
* @param context context in which the constraint is evaluated
* @return {@code false} if {@code value} does not pass the constraint
*/
@Override
public boolean isValid(Collection<String> value, ConstraintValidatorContext context) {
if( value == null ) {
return false;
}

boolean isValid = false;
for( String requiredKey : requiredKeys ) {
if (value.stream()
.filter(s -> s.contains(LABEL_KV_SEPARATOR))
.anyMatch(s -> s.split(LABEL_KV_SEPARATOR)[0].equals(requiredKey))) {
isValid = true;
}
}

if ( !isValid ) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(messageTemplate).addConstraintViolation();
}

return isValid;
}
}
@@ -0,0 +1,10 @@
package com.cloudcomputing.docker.limits.model.validator;

import com.cloudcomputing.docker.limits.model.io.DockerCompose;

import javax.validation.ConstraintViolation;
import java.util.Set;

public interface DockerComposeValidator {
Set<ConstraintViolation<DockerCompose>> validate(DockerCompose dockerCompose);
}
@@ -0,0 +1,70 @@
package com.cloudcomputing.docker.limits.model.validator;

import com.cloudcomputing.docker.limits.model.io.DockerCompose;
import com.cloudcomputing.docker.limits.model.io.ServiceSpec;
import com.cloudcomputing.docker.limits.services.label.DockerLabelService;
import com.github.rozidan.springboot.logger.Loggable;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.hibernate.validator.cfg.ConstraintMapping;
import org.hibernate.validator.cfg.GenericConstraintDef;
import org.hibernate.validator.cfg.defs.NotEmptyDef;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;

import static java.lang.annotation.ElementType.FIELD;

@Component
@Loggable
public class DockerComposeValidatorImpl implements DockerComposeValidator {

private Validator validator;

public DockerComposeValidatorImpl() {
init();
}

private void init() {
HibernateValidatorConfiguration configuration = Validation
.byProvider( HibernateValidator.class )
.configure();

ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping
.type( DockerCompose.class )
.ignoreAllAnnotations()
.method( "getVersion" )
.returnValue()
.constraint( new NotEmptyDef() )
.method( "getHsbUsername" )
.returnValue()
.constraint( new NotEmptyDef() )
.method("getServices")
.returnValue()
.valid()
.type( ServiceSpec.class )
.ignoreAllAnnotations()
.property( "labels", FIELD )
.valid()
.constraint( new GenericConstraintDef<>(ContainsLabelKeys.class)
.param( "value", new String[]{ DockerLabelService.LABEL_USER_KEY } ) )
;

validator = configuration.addMapping( constraintMapping )
.buildValidatorFactory()
.getValidator();

}

@Override
public Set<ConstraintViolation<DockerCompose>> validate(DockerCompose dockerCompose) {
Set<ConstraintViolation<DockerCompose>> violations = validator.validate(dockerCompose);
return violations;
}

}
@@ -0,0 +1,52 @@
package com.cloudcomputing.docker.limits.model.io;

import org.assertj.core.api.AbstractAssert;

import javax.validation.ConstraintViolation;
import java.util.Set;
import java.util.stream.Collectors;

public class ConstraintViolationSetAssert extends AbstractAssert<ConstraintViolationSetAssert, Set<? extends ConstraintViolation>> {
public ConstraintViolationSetAssert(Set<? extends ConstraintViolation> actual) {
super(actual, ConstraintViolationSetAssert.class);
}

public static ConstraintViolationSetAssert assertThat(Set<? extends ConstraintViolation> actual) {
return new ConstraintViolationSetAssert(actual);
}

public ConstraintViolationSetAssert hasViolationOnPath(String path) {
isNotNull();

// check condition
if (!containsViolationWithPath(actual, path)) {
failWithMessage("There was no violation with path <%s>. Violation paths: <%s>", path, actual.stream()
.map(Object::toString).collect(
Collectors.toList()));
}

return this;
}

public ConstraintViolationSetAssert hasNoViolations() {
isNotNull();

if (!actual.isEmpty()) {
failWithMessage("Expecting no violations, but there are %s violations: %s", actual.size(), actual.toString());
}

return this;
}

private boolean containsViolationWithPath(Set<? extends ConstraintViolation> violations, String path) {
boolean result = false;
for (ConstraintViolation violation : violations) {
if (violation.getPropertyPath()
.toString()
.equals(path)) {
result = true;
}
}
return result;
}
}
@@ -0,0 +1,77 @@
package com.cloudcomputing.docker.limits.model.io;

import com.cloudcomputing.docker.limits.model.validator.DockerComposeValidator;
import com.cloudcomputing.docker.limits.services.label.DockerLabelService;
import com.cloudcomputing.docker.limits.services.users.UserRoleService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.validation.ConstraintViolation;
import java.util.Set;

import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DockerComposeValidatorTest {

@Mock
private DockerCompose dockerCompose;

@Autowired
private DockerComposeValidator dockerComposeValidator;

@Test
public void validatePositive() {
when(dockerCompose.getVersion()).thenReturn("2.4");
when(dockerCompose.getHsbUsername()).thenReturn("czoeller");

final Set<ConstraintViolation<DockerCompose>> violations = dockerComposeValidator.validate(dockerCompose);

ConstraintViolationSetAssert.assertThat(violations).hasNoViolations();
}

@Test
public void testWithNoVersion() {
when(dockerCompose.getVersion()).thenReturn(null);

final Set<ConstraintViolation<DockerCompose>> violations = dockerComposeValidator.validate(dockerCompose);

ConstraintViolationSetAssert.assertThat(violations).hasViolationOnPath("version");
}

@Test
public void testLabeledService() {
final ServiceSpec nginx = new ServiceSpec();
nginx.labels = ImmutableList.of(DockerLabelService.LABEL_USER_KEY + "=" + UserRoleService.STUDENT);
final ImmutableMap<String, ServiceSpec> services = ImmutableMap.<String, ServiceSpec>builder().put("nginx", nginx).build();

when(dockerCompose.getVersion()).thenReturn("2.4");
when(dockerCompose.getHsbUsername()).thenReturn("czoeller");

// Test with label
when(dockerCompose.getServices()).thenReturn(services);

Set<ConstraintViolation<DockerCompose>> violations = dockerComposeValidator.validate(dockerCompose);
ConstraintViolationSetAssert.assertThat(violations).hasNoViolations();

// Test without label
services.get("nginx").labels = ImmutableList.<String>builder().build();
when(dockerCompose.getServices()).thenReturn(services);
violations = dockerComposeValidator.validate(dockerCompose);
ConstraintViolationSetAssert.assertThat(violations).hasViolationOnPath("services[nginx].labels");

// Test null label
services.get("nginx").labels = null;
when(dockerCompose.getServices()).thenReturn(services);
violations = dockerComposeValidator.validate(dockerCompose);
ConstraintViolationSetAssert.assertThat(violations).hasViolationOnPath("services[nginx].labels");
}

}

0 comments on commit 4c7d546

Please sign in to comment.