Compare commits

..

3 Commits

Author SHA1 Message Date
Michael Hoennig
93774aebe3 add SystemProcess as wrapper for ProcessBuilder using Strings for simplified input+output 2024-07-04 13:07:03 +02:00
Michael Hoennig
977bd595d8 fix test for all properties 2024-07-04 13:06:05 +02:00
Michael Hoennig
105bd2313c split A+AAAA RRs and add parenthesis-variant in record-data 2024-07-04 13:01:22 +02:00
4 changed files with 170 additions and 9 deletions

View File

@ -5,7 +5,6 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import java.util.regex.Pattern;
import static java.util.Arrays.stream;
import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf;
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
@ -13,11 +12,13 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidator {
// according to RFC 1035 (section 5) and RFC 1034
static final String RR_REGEX_NAME = "([a-z0-9\\.-]+|@)\\s+";
static final String RR_REGEX_TTL = "(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*";
static final String RR_REGEX_IN = "IN\\s+"; // record class IN for Internet
static final String RR_RECORD_TYPE = "[A-Z]+\\s+";
static final String RR_RECORD_DATA = "([a-z0-9\\.-]+|\"[^\"]*\")\\s*";
static final String RR_RECORD_DATA_X = "([a-z0-9\\.-]+|\"[^\"]*\")\\s*"; // FIXME: (...) and multiline?
static final String RR_RECORD_DATA = "([a-z0-9\\.-]+|\\([^\\)]*\\)|\"[^\"]*\")\\s*";
static final String RR_COMMENT = "(;.*)*";
static final String RR_REGEX_TTL_IN =
@ -36,12 +37,16 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato
booleanProperty("auto-SOA-RR").withDefault(true),
booleanProperty("auto-NS-RR").withDefault(true),
booleanProperty("auto-MX-RR").withDefault(true),
booleanProperty("auto-A+AAAA-RR").withDefault(true),
booleanProperty("auto-MAILSERVICES-RR").withDefault(true), // TODO. specl: incl. AUTOCONFIG_RR, AUTODISCOVER_RR?
booleanProperty("auto-A-RR").withDefault(true),
booleanProperty("auto-AAAA-RR").withDefault(true),
booleanProperty("auto-MAILSERVICES-RR").withDefault(true),
booleanProperty("auto-AUTOCONFIG-RR").withDefault(true), // TODO.spec: does that already exist?
booleanProperty("auto-AUTODISCOVER-RR").withDefault(true),
booleanProperty("auto-DKIM-RR").withDefault(true),
booleanProperty("auto-SPF-RR").withDefault(true),
booleanProperty("auto-WILDCARD-MX-RR").withDefault(true),
booleanProperty("auto-WILDCARD-A+AAAA-RR").withDefault(true),
booleanProperty("auto-WILDCARD-A-RR").withDefault(true),
booleanProperty("auto-WILDCARD-AAAA-RR").withDefault(true),
booleanProperty("auto-WILDCARD-DKIM-RR").withDefault(true), // TODO.spec: @Peter
booleanProperty("auto-WILDCARD-SPF-RR").withDefault(true),
arrayOf(
@ -70,5 +75,4 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato
.replace("{ttl}", getPropertyValue(assetEntity, "TTL"))
.replace("{userRRs}", getPropertyValues(assetEntity, "user-RR") );
}
}

View File

@ -0,0 +1,57 @@
package net.hostsharing.hsadminng.system;
import lombok.Getter;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class SystemProcess {
private final ProcessBuilder processBuilder;
@Getter
private String stdOut;
@Getter
private String stdErr;
public SystemProcess(final String... command) {
this.processBuilder = new ProcessBuilder(command);
}
public int execute() throws IOException, InterruptedException {
final var process = processBuilder.start();
stdOut = fetchOutput(process.getInputStream()); // yeah, twisted ProcessBuilder API
stdErr = fetchOutput(process.getErrorStream());
return process.waitFor();
}
public int execute(final String input) throws IOException, InterruptedException {
final var process = processBuilder.start();
feedInput(input, process);
stdOut = fetchOutput(process.getInputStream()); // yeah, twisted ProcessBuilder API
stdErr = fetchOutput(process.getErrorStream());
return process.waitFor();
}
private static void feedInput(final String input, final Process process) throws IOException {
try (
final OutputStreamWriter stdIn = new OutputStreamWriter(process.getOutputStream()); // yeah, twisted ProcessBuilder API
final BufferedWriter writer = new BufferedWriter(stdIn)) {
writer.write(input);
writer.flush();
}
}
private static String fetchOutput(final InputStream inputStream) throws IOException {
final var output = new StringBuilder();
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
for (String line; (line = reader.readLine()) != null; ) {
output.append(line).append(System.lineSeparator());
}
}
return output.toString();
}
}

View File

@ -72,12 +72,30 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
}
@Test
void containsNoProperties() {
void containsExpectedProperties() {
// when
final var validator = HsHostingAssetEntityValidatorRegistry.forType(CLOUD_SERVER);
final var validator = HsHostingAssetEntityValidatorRegistry.forType(DOMAIN_DNS_SETUP);
// then
assertThat(validator.properties()).map(Map::toString).isEmpty();
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=TTL, min=0, defaultValue=21600}",
"{type=boolean, propertyName=auto-SOA-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-NS-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-MX-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-A-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-AAAA-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-MAILSERVICES-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-AUTOCONFIG-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-AUTODISCOVER-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-DKIM-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-SPF-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-MX-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-A-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-AAAA-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-DKIM-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-SPF-RR, defaultValue=true}",
"{type=string[], propertyName=user-RR, elementsOf={type=string, propertyName=user-RR, matchesRegEx=[([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+([a-z0-9\\.-]+|\\([^\\)]*\\)|\"[^\"]*\")\\s*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+([a-z0-9\\.-]+|\\([^\\)]*\\)|\"[^\"]*\")\\s*(;.*)*], required=true}}"
);
}
@Test
@ -122,6 +140,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
assertThat("example.com.").matches(RR_RECORD_DATA);
assertThat("123.123.123.123").matches(RR_RECORD_DATA);
assertThat("(some more complex argument in parenthesis)").matches(RR_RECORD_DATA);
assertThat("\"some more complex argument; including a semicolon\"").matches(RR_RECORD_DATA);
assertThat("; whatever ; \" really anything").matches(RR_COMMENT);

View File

@ -0,0 +1,81 @@
package net.hostsharing.hsadminng.system;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.junit.jupiter.api.condition.OS.LINUX;
class SystemProcessTest {
@Test
@EnabledOnOs(LINUX)
void shouldExecuteAndFetchOutput() throws IOException, InterruptedException {
// given
final var process = new SystemProcess("bash", "-c", "echo 'Hello, World!'; echo 'Error!' >&2");
// when
final var returnCode = process.execute();
// then
assertThat(returnCode).isEqualTo(0);
assertThat(process.getStdOut()).isEqualTo("Hello, World!\n");
assertThat(process.getStdErr()).isEqualTo("Error!\n");
}
@Test
@EnabledOnOs(LINUX)
void shouldReturnErrorCode() throws IOException, InterruptedException {
// given
final var process = new SystemProcess("false");
// when
final int returnCode = process.execute();
// then
assertThat(returnCode).isEqualTo(1);
}
@Test
@EnabledOnOs(LINUX)
void shouldExecuteAndFeedInput() throws IOException, InterruptedException {
// given
final var process = new SystemProcess("tr", "[:lower:]", "[:upper:]");
// when
final int returnCode = process.execute("Hallo");
// then
assertThat(returnCode).isEqualTo(0);
assertThat(process.getStdOut()).isEqualTo("HALLO\n");
}
@Test
void shouldThrowExceptionIfProgramNotFound() {
// given
final var process = new SystemProcess("non-existing program");
// when
final var exception = catchThrowable(process::execute);
// then
assertThat(exception).isInstanceOf(IOException.class)
.hasMessage("Cannot run program \"non-existing program\": error=2, No such file or directory");
}
@Test
void shouldBeAbleToRunMultipleTimes() throws IOException, InterruptedException {
// given
final var process = new SystemProcess("true");
// when
process.execute();
final int returnCode = process.execute();
// then
assertThat(returnCode).isEqualTo(0);
}
}