import-hosting-domain-assets (#84)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #84
This commit is contained in:
Michael Hoennig 2024-08-08 10:40:34 +02:00
parent 085876c772
commit 5046e9a296
19 changed files with 1174 additions and 250 deletions

View File

@ -33,4 +33,38 @@
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
</component>
<configuration default="false" name="ImportOfficeData" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":importOfficeData" />
<option value="--tests" />
<option value="&quot;net.hostsharing.hsadminng.hs.office.migration.ImportOfficeData&quot;" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
<extension name="coverage" sample_coverage="false" />
</EXTENSION>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -15,15 +15,16 @@ import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanPro
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty;
class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator {
// TODO.impl: make package private once we've migrated the legacy data
public class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator {
// 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 = "[^;].*";
static final String RR_COMMENT = "(;.*)*";
static final String RR_REGEX_NAME = "(\\*\\.)?([a-zA-Z0-9\\._-]+|@)[ \t]+";
static final String RR_REGEX_TTL = "(([1-9][0-9]*[mMhHdDwW]?)+[ \t]+)?";
static final String RR_REGEX_IN = "[iI][nN][ \t]+"; // record class IN for Internet
static final String RR_RECORD_TYPE = "[a-zA-Z]+[ \t]+";
static final String RR_RECORD_DATA = "(([^;]+)|(\".*\")|(\\(.*\\)))[ \t]*";
static final String RR_COMMENT = "(;.*)?";
static final String RR_REGEX_TTL_IN =
RR_REGEX_NAME + RR_REGEX_TTL + RR_REGEX_IN + RR_RECORD_TYPE + RR_RECORD_DATA + RR_COMMENT;
@ -32,26 +33,27 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
RR_REGEX_NAME + RR_REGEX_IN + RR_REGEX_TTL + RR_RECORD_TYPE + RR_RECORD_DATA + RR_COMMENT;
public static final String IDENTIFIER_SUFFIX = "|DNS";
private static List<String> zoneFileErrors = null; // TODO.impl: remove once legacy data is migrated
HsDomainDnsSetupHostingAssetValidator() {
super(
DOMAIN_DNS_SETUP,
AlarmContact.isOptional(),
integerProperty("TTL").min(0).withDefault(21600),
booleanProperty("auto-SOA-RR").withDefault(true),
booleanProperty("auto-SOA").withDefault(true),
booleanProperty("auto-NS-RR").withDefault(true),
booleanProperty("auto-MX-RR").withDefault(true),
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-AUTOCONFIG-RR").withDefault(true),
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-RR").withDefault(true),
booleanProperty("auto-WILDCARD-AAAA-RR").withDefault(true),
booleanProperty("auto-WILDCARD-DKIM-RR").withDefault(true), // TODO.spec: check, if that really works
booleanProperty("auto-WILDCARD-SPF-RR").withDefault(true),
arrayOf(
stringProperty("user-RR").matchesRegEx(RR_REGEX_TTL_IN, RR_REGEX_IN_TTL).required()
@ -60,7 +62,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
@Override
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
return Pattern.compile("^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX) + "$");
return Pattern.compile("^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX) + "$");
}
@Override
@ -78,33 +80,105 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
// TODO.spec: define which checks should get raised to error level
final var namedCheckZone = new SystemProcess("named-checkzone", fqdn(assetEntity));
if (namedCheckZone.execute(toZonefileString(assetEntity)) != 0) {
// yes, named-checkzone writes error messages to stdout
final var zonefileString = toZonefileString(assetEntity);
final var zoneFileErrorResult = zoneFileErrors != null ? zoneFileErrors : result;
if (namedCheckZone.execute(zonefileString) != 0) {
// yes, named-checkzone writes error messages to stdout, not stderr
stream(namedCheckZone.getStdOut().split("\n"))
.map(line -> line.replaceAll(" stream-0x[0-9a-f:]+", ""))
.forEach(result::add);
.map(line -> line.replaceAll(" stream-0x[0-9a-f]+:", "line "))
.map(line -> "[" + assetEntity.getIdentifier() + "] " + line)
.forEach(zoneFileErrorResult::add);
if (!namedCheckZone.getStdErr().isEmpty()) {
result.add("unexpected stderr output for " + namedCheckZone.getCommand() + ": " + namedCheckZone.getStdErr());
}
}
return result;
}
String toZonefileString(final HsHostingAsset assetEntity) {
// TODO.spec: we need to expand the templates (auto-...) in the same way as in Saltstack
// TODO.spec: we need to expand the templates (auto-...) in the same way as in Saltstack, with proper IP-numbers etc.
return """
$ORIGIN {domain}.
$TTL {ttl}
$TTL {ttl}
; these records are just placeholders to create a valid zonefile for the validation
@ 1814400 IN SOA {domain}. root.{domain} ( 1999010100 10800 900 604800 86400 )
@ IN NS ns
{userRRs}
"""
.replace("{domain}", fqdn(assetEntity))
.replace("{ttl}", getPropertyValue(assetEntity, "TTL"))
.replace("{userRRs}", getPropertyValues(assetEntity, "user-RR") );
{auto-SOA}
{auto-NS-RR}
{auto-MX-RR}
{auto-A-RR}
{auto-AAAA-RR}
{auto-DKIM-RR}
{auto-SPF-RR}
{auto-WILDCARD-MX-RR}
{auto-WILDCARD-A-RR}
{auto-WILDCARD-AAAA-RR}
{auto-WILDCARD-SPF-RR}
{userRRs}
"""
.replace("{ttl}", assetEntity.getDirectValue("TTL", Integer.class, 43200).toString())
.replace("{auto-SOA}", assetEntity.getDirectValue("auto-SOA", Boolean.class, false).equals(true)
? """
{domain}. IN SOA h00.hostsharing.net. hostmaster.hostsharing.net. (
1303649373 ; serial secs since Jan 1 1970
6H ; refresh (>=10000)
1H ; retry (>=1800)
1W ; expire
1H ; minimum
)
"""
: "; no auto-SOA"
)
.replace("{auto-NS-RR}", assetEntity.getDirectValue("auto-NS-RR", Boolean.class, true)
? """
{domain}. IN NS dns1.hostsharing.net.
{domain}. IN NS dns2.hostsharing.net.
{domain}. IN NS dns3.hostsharing.net.
"""
: "; no auto-NS-RR")
.replace("{auto-MX-RR}", assetEntity.getDirectValue("auto-MX-RR", Boolean.class, true)
? """
{domain}. IN MX 30 mailin1.hostsharing.net.
{domain}. IN MX 30 mailin2.hostsharing.net.
{domain}. IN MX 30 mailin3.hostsharing.net.
"""
: "; no auto-MX-RR")
.replace("{auto-A-RR}", assetEntity.getDirectValue("auto-A-RR", Boolean.class, true)
? "{domain}. IN A 83.223.95.160" // arbitrary IP-number
: "; no auto-A-RR")
.replace("{auto-AAAA-RR}", assetEntity.getDirectValue("auto-AAA-RR", Boolean.class, true)
? "{domain}. IN AAAA 2a01:37:1000::53df:5fa0:0" // arbitrary IP-number
: "; no auto-AAAA-RR")
.replace("{auto-DKIM-RR}", assetEntity.getDirectValue("auto-DKIM-RR", Boolean.class, true)
? "default._domainkey 21600 IN TXT \"v=DKIM1; h=sha256; k=rsa; s=email; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmdM9d15bqe94zbHVcKKpUF875XoCWHKRap/sG3NJZ9xZ/BjfGXmqoEYeFNpX3CB7pOXhH5naq4N+6gTjArTviAiVThHXyebhrxaf1dVS4IUC6raTEyQrWPZUf7ZxXmcCYvOdV4jIQ8GRfxwxqibIJcmMiufXTLIgRUif5uaTgFwIDAQAB\""
: "; no auto-DKIM-RR")
.replace("{auto-SPF-RR}", assetEntity.getDirectValue("auto-SPF-RR", Boolean.class, true)
? "{domain}. IN TXT \"v=spf1 include:spf.hostsharing.net ?all\""
: "; no auto-SPF-RR")
.replace("{auto-WILDCARD-MX-RR}", assetEntity.getDirectValue("auto-SPF-RR", Boolean.class, true)
? """
*.{domain}. IN MX 30 mailin1.hostsharing.net.
*.{domain}. IN MX 30 mailin1.hostsharing.net.
*.{domain}. IN MX 30 mailin1.hostsharing.net.
"""
: "; no auto-WILDCARD-MX-RR")
.replace("{auto-WILDCARD-A-RR}", assetEntity.getDirectValue("auto-WILDCARD-A-RR", Boolean.class, true)
? "*.{domain}. IN A 83.223.95.160" // arbitrary IP-number
: "; no auto-WILDCARD-A-RR")
.replace("{auto-WILDCARD-AAAA-RR}", assetEntity.getDirectValue("auto-WILDCARD-AAAA-RR", Boolean.class, true)
? "*.{domain}. IN AAAA 2a01:37:1000::53df:5fa0:0" // arbitrary IP-number
: "; no auto-WILDCARD-AAAA-RR")
.replace("{auto-WILDCARD-SPF-RR}", assetEntity.getDirectValue("auto-WILDCARD-SPF-RR", Boolean.class, true)
? "*.{domain}. IN TXT \"v=spf1 include:spf.hostsharing.net ?all\""
: "; no auto-WILDCARD-SPF-RR")
.replace("{domain}", fqdn(assetEntity))
.replace("{userRRs}", getPropertyValues(assetEntity, "user-RR"));
}
private String fqdn(final HsHostingAsset assetEntity) {
return assetEntity.getIdentifier().substring(0, assetEntity.getIdentifier().length()-IDENTIFIER_SUFFIX.length());
return assetEntity.getIdentifier().substring(0, assetEntity.getIdentifier().length() - IDENTIFIER_SUFFIX.length());
}
public static void addZonefileErrorsTo(final List<String> zoneFileErrors) {
HsDomainDnsSetupHostingAssetValidator.zoneFileErrors = zoneFileErrors;
}
}

View File

@ -13,8 +13,8 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
class HsDomainHttpSetupHostingAssetValidator extends HostingAssetEntityValidator {
public static final String IDENTIFIER_SUFFIX = "|HTTP";
public static final String FILESYSTEM_PATH = "^/";
public static final String PARTIAL_DOMAIN_NAME_REGEX = "(?!-)[A-Za-z0-9-]{1,63}(?<!-)";
public static final String FILESYSTEM_PATH = "^/.*";
public static final String SUBDOMAIN_NAME_REGEX = "(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-))";
HsDomainHttpSetupHostingAssetValidator() {
super(
@ -37,7 +37,7 @@ class HsDomainHttpSetupHostingAssetValidator extends HostingAssetEntityValidator
stringProperty("passenger-python").matchesRegEx(FILESYSTEM_PATH).provided("/usr/bin/python3").withDefault("/usr/bin/python3"),
stringProperty("passenger-ruby").matchesRegEx(FILESYSTEM_PATH).provided("/usr/bin/ruby").withDefault("/usr/bin/ruby"),
arrayOf(
stringProperty("subdomains").matchesRegEx(PARTIAL_DOMAIN_NAME_REGEX).required()
stringProperty("subdomains").matchesRegEx(SUBDOMAIN_NAME_REGEX).required()
).optional());
}

View File

@ -9,7 +9,7 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMA
class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
public static final String FQDN_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}";
public static final String FQDN_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}";
private final Pattern identifierPattern;

View File

@ -145,4 +145,8 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
}
return "";
}
public ValidatableProperty<?, ?> getProperty(final String propertyName) {
return stream(propertyValidators).filter(pv -> pv.propertyName().equals(propertyName)).findFirst().orElse(null);
}
}

View File

@ -1,22 +1,27 @@
package net.hostsharing.hsadminng.mapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.SneakyThrows;
import org.apache.commons.lang3.tuple.ImmutablePair;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.joining;
/** This class wraps another (usually persistent) map and
* supports applying `PatchMap` as well as a toString method with stable entry order.
*/
public class PatchableMapWrapper<T> implements Map<String, T> {
private static final ObjectMapper jsonWriter = new ObjectMapper()
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
.configure(SerializationFeature.INDENT_OUTPUT, true);
private final Map<String, T> delegate;
private PatchableMapWrapper(final Map<String, T> map) {
@ -53,24 +58,9 @@ public class PatchableMapWrapper<T> implements Map<String, T> {
});
}
@SneakyThrows
public String toString() {
return "{\n"
+ (
keySet().stream().sorted()
.map(k -> " \"" + k + "\": " + formatted(get(k))))
.collect(joining(",\n")
)
+ "\n}\n";
}
private Object formatted(final Object value) {
if ( value == null || value instanceof Number || value instanceof Boolean ) {
return value;
}
if ( value.getClass().isArray() ) {
return "\"" + Arrays.toString( (Object[]) value) + "\"";
}
return "\"" + value + "\"";
return jsonWriter.writeValueAsString(delegate);
}
// --- below just delegating methods --------------------------------

View File

@ -21,6 +21,11 @@ public class SystemProcess {
this.processBuilder = new ProcessBuilder(command);
}
public String getCommand() {
return processBuilder.command().toString();
}
public int execute() throws IOException, InterruptedException {
final var process = processBuilder.start();
stdOut = fetchOutput(process.getInputStream()); // yeah, twisted ProcessBuilder API

View File

@ -35,12 +35,27 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
.assignedToAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
.identifier("example.org|DNS")
.config(Map.ofEntries(
entry("TTL", 21600),
entry("auto-SOA", true),
entry("auto-NS-RR", true),
entry("auto-MX-RR", true),
entry("auto-A-RR", true),
entry("auto-AAAA-RR", true),
entry("auto-MAILSERVICES-RR", true),
entry("auto-AUTOCONFIG-RR", true),
entry("auto-AUTODISCOVER-RR", true),
entry("auto-DKIM-RR", true),
entry("auto-SPF-RR", true),
entry("auto-WILDCARD-MX-RR", true),
entry("auto-WILDCARD-A-RR", true),
entry("auto-WILDCARD-AAAA-RR", true),
entry("auto-WILDCARD-SPF-RR", true),
entry("user-RR", Array.of(
"@ 1814400 IN XXX example.org. root.example.org ( 1234 10800 900 604800 86400 )",
"www IN CNAME example.com. ; www.example.com is an alias for example.com",
"test1 IN 1h30m CNAME example.com.",
"test2 1h30m IN CNAME example.com.",
"ns IN A 192.0.2.2; IPv4 address for ns.example.com")
"ns IN A 192.0.2.2; IPv4 address for ns.example.com",
"_acme-challenge.PAULCHEN-VS.core.example.org. 60 IN CNAME _acme-challenge.core.example.org.acme-pki.de.")
)
));
}
@ -53,7 +68,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// then
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-SOA, 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}",
@ -66,9 +81,8 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
"{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+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*], required=true}}"
"{type=string[], propertyName=user-RR, elementsOf={type=string, propertyName=user-RR, matchesRegEx=[(\\*\\.)?([a-zA-Z0-9\\._-]+|@)[ \t]+(([1-9][0-9]*[mMhHdDwW]?)+[ \t]+)?[iI][nN][ \t]+[a-zA-Z]+[ \t]+(([^;]+)|(\".*\")|(\\(.*\\)))[ \t]*(;.*)?, (\\*\\.)?([a-zA-Z0-9\\._-]+|@)[ \t]+[iI][nN][ \t]+(([1-9][0-9]*[mMhHdDwW]?)+[ \t]+)?[a-zA-Z]+[ \t]+(([^;]+)|(\".*\")|(\\(.*\\)))[ \t]*(;.*)?], required=true}}"
);
}
@ -135,7 +149,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
}
@Test
void acceptsValidEntity() {
void acceptsValidEntityItself() {
// given
final var givenEntity = validEntityBuilder().build();
final var validator = HostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
@ -147,6 +161,19 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
assertThat(errors).isEmpty();
}
@Test
void acceptsValidEntityInContext() {
// given
final var givenEntity = validEntityBuilder().build();
final var validator = HostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when
final var errors = validator.validateContext(givenEntity);
// then
assertThat(errors).isEmpty();
}
@Test
void rejectsInvalidProperties() {
// given
@ -166,35 +193,56 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'DOMAIN_DNS_SETUP:example.org|DNS.config.TTL' is expected to be of type Integer, but is of type String",
"'DOMAIN_DNS_SETUP:example.org|DNS.config.user-RR' is expected to match any of [([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*] but '@ 1814400 IN 1814400 BAD1 TTL only allowed once' does not match any",
"'DOMAIN_DNS_SETUP:example.org|DNS.config.user-RR' is expected to match any of [([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*] but 'www BAD1 Record-Class missing / not enough columns' does not match any");
"'DOMAIN_DNS_SETUP:example.org|DNS.config.user-RR' is expected to match any of [(\\*\\.)?([a-zA-Z0-9\\._-]+|@)[ \t]+(([1-9][0-9]*[mMhHdDwW]?)+[ \t]+)?[iI][nN][ \t]+[a-zA-Z]+[ \t]+(([^;]+)|(\".*\")|(\\(.*\\)))[ \t]*(;.*)?, (\\*\\.)?([a-zA-Z0-9\\._-]+|@)[ \t]+[iI][nN][ \t]+(([1-9][0-9]*[mMhHdDwW]?)+[ \t]+)?[a-zA-Z]+[ \t]+(([^;]+)|(\".*\")|(\\(.*\\)))[ \t]*(;.*)?] but '@ 1814400 IN 1814400 BAD1 TTL only allowed once' does not match any",
"'DOMAIN_DNS_SETUP:example.org|DNS.config.user-RR' is expected to match any of [(\\*\\.)?([a-zA-Z0-9\\._-]+|@)[ \t]+(([1-9][0-9]*[mMhHdDwW]?)+[ \t]+)?[iI][nN][ \t]+[a-zA-Z]+[ \t]+(([^;]+)|(\".*\")|(\\(.*\\)))[ \t]*(;.*)?, (\\*\\.)?([a-zA-Z0-9\\._-]+|@)[ \t]+[iI][nN][ \t]+(([1-9][0-9]*[mMhHdDwW]?)+[ \t]+)?[a-zA-Z]+[ \t]+(([^;]+)|(\".*\")|(\\(.*\\)))[ \t]*(;.*)?] but 'www BAD1 Record-Class missing / not enough columns' does not match any");
}
@Test
void validStringMatchesRegEx() {
void validNameMatchesRegEx() {
assertThat("@ ").matches(RR_REGEX_NAME);
assertThat("ns ").matches(RR_REGEX_NAME);
assertThat("example.com. ").matches(RR_REGEX_NAME);
assertThat("example.ORG. ").matches(RR_REGEX_NAME);
}
@Test
void validTtlMatchesRegEx() {
assertThat("12400 ").matches(RR_REGEX_TTL);
assertThat("12400\t\t ").matches(RR_REGEX_TTL);
assertThat("12400 \t\t").matches(RR_REGEX_TTL);
assertThat("1h30m ").matches(RR_REGEX_TTL);
assertThat("30m ").matches(RR_REGEX_TTL);
}
@Test
void validInMatchesRegEx() {
assertThat("in ").matches(RR_REGEX_IN);
assertThat("IN ").matches(RR_REGEX_IN);
assertThat("IN\t\t ").matches(RR_REGEX_IN);
assertThat("IN \t\t").matches(RR_REGEX_IN);
}
@Test
void validRecordTypeMatchesRegEx() {
assertThat("a ").matches(RR_RECORD_TYPE);
assertThat("CNAME ").matches(RR_RECORD_TYPE);
assertThat("CNAME\t\t ").matches(RR_RECORD_TYPE);
assertThat("CNAME \t\t").matches(RR_RECORD_TYPE);
}
@Test
void validRecordDataMatchesRegEx() {
assertThat("example.com.").matches(RR_RECORD_DATA);
assertThat("example.com. ").matches(RR_RECORD_DATA);
assertThat("123.123.123.123").matches(RR_RECORD_DATA);
assertThat("123.123.123.123 ").matches(RR_RECORD_DATA);
assertThat("_acme-challenge.core.example.org.acme-pki.de.").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);
}
@Test
void validCommentMatchesRegEx() {
assertThat("; whatever ; \" really anything").matches(RR_COMMENT);
}
@ -209,18 +257,42 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// then
assertThat(zonefile).isEqualTo("""
$ORIGIN example.org.
$TTL 21600
; these records are just placeholders to create a valid zonefile for the validation
@ 1814400 IN SOA example.org. root.example.org ( 1999010100 10800 900 604800 86400 )
@ IN NS ns
example.org. IN SOA h00.hostsharing.net. hostmaster.hostsharing.net. (
1303649373 ; serial secs since Jan 1 1970
6H ; refresh (>=10000)
1H ; retry (>=1800)
1W ; expire
1H ; minimum
)
example.org. IN NS dns1.hostsharing.net.
example.org. IN NS dns2.hostsharing.net.
example.org. IN NS dns3.hostsharing.net.
example.org. IN MX 30 mailin1.hostsharing.net.
example.org. IN MX 30 mailin2.hostsharing.net.
example.org. IN MX 30 mailin3.hostsharing.net.
example.org. IN A 83.223.95.160
example.org. IN AAAA 2a01:37:1000::53df:5fa0:0
default._domainkey 21600 IN TXT "v=DKIM1; h=sha256; k=rsa; s=email; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmdM9d15bqe94zbHVcKKpUF875XoCWHKRap/sG3NJZ9xZ/BjfGXmqoEYeFNpX3CB7pOXhH5naq4N+6gTjArTviAiVThHXyebhrxaf1dVS4IUC6raTEyQrWPZUf7ZxXmcCYvOdV4jIQ8GRfxwxqibIJcmMiufXTLIgRUif5uaTgFwIDAQAB"
example.org. IN TXT "v=spf1 include:spf.hostsharing.net ?all"
*.example.org. IN MX 30 mailin1.hostsharing.net.
*.example.org. IN MX 30 mailin1.hostsharing.net.
*.example.org. IN MX 30 mailin1.hostsharing.net.
*.example.org. IN A 83.223.95.160
*.example.org. IN AAAA 2a01:37:1000::53df:5fa0:0
*.example.org. IN TXT "v=spf1 include:spf.hostsharing.net ?all"
@ 1814400 IN XXX example.org. root.example.org ( 1234 10800 900 604800 86400 )
www IN CNAME example.com. ; www.example.com is an alias for example.com
test1 IN 1h30m CNAME example.com.
test2 1h30m IN CNAME example.com.
ns IN A 192.0.2.2; IPv4 address for ns.example.com
_acme-challenge.PAULCHEN-VS.core.example.org. 60 IN CNAME _acme-challenge.core.example.org.acme-pki.de.
""");
}
@ -229,7 +301,8 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// given
final var givenEntity = validEntityBuilder().config(Map.ofEntries(
entry("user-RR", Array.of(
"example.org. 1814400 IN SOA example.org. root.example.org (1234 10800 900 604800 86400)"
"example.org. 1814400 IN SOA example.org. root.example.org (1234 10800 900 604800 86400)",
"example.org. 1814400 IN SOA example.org. root.example.org (4321 10800 900 604800 86400)"
))
))
.build();
@ -240,9 +313,9 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// then
assertThat(errors).containsExactlyInAnyOrder(
"dns_master_load: example.org: multiple RRs of singleton type",
"zone example.org/IN: loading from master file (null) failed: multiple RRs of singleton type",
"zone example.org/IN: not loaded due to errors."
"[example.org|DNS] dns_master_load:line 26: example.org: multiple RRs of singleton type",
"[example.org|DNS] zone example.org/IN: loading from master file (null) failed: multiple RRs of singleton type",
"[example.org|DNS] zone example.org/IN: not loaded due to errors."
);
}
}

View File

@ -30,7 +30,8 @@ class HsDomainHttpSetupHostingAssetValidatorUnitTest {
.assignedToAsset(HsHostingAssetEntity.builder().type(UNIX_USER).build())
.identifier("example.org|HTTP")
.config(Map.ofEntries(
entry("passenger-errorpage", true),
entry("passenger-errorpage", true),
entry("fcgi-php-bin", "/usr/bin/whatsoever"),
entry("subdomains", Array.of("www", "test")
)
));
@ -54,10 +55,10 @@ class HsDomainHttpSetupHostingAssetValidatorUnitTest {
"{type=boolean, propertyName=includes, defaultValue=true}",
"{type=boolean, propertyName=letsencrypt, defaultValue=true}",
"{type=boolean, propertyName=multiviews, defaultValue=true}",
"{type=string, propertyName=fcgi-php-bin, matchesRegEx=[^/], provided=[/usr/lib/cgi-bin/php], defaultValue=/usr/lib/cgi-bin/php}",
"{type=string, propertyName=passenger-nodejs, matchesRegEx=[^/], provided=[/usr/bin/node], defaultValue=/usr/bin/node}",
"{type=string, propertyName=passenger-python, matchesRegEx=[^/], provided=[/usr/bin/python3], defaultValue=/usr/bin/python3}",
"{type=string, propertyName=passenger-ruby, matchesRegEx=[^/], provided=[/usr/bin/ruby], defaultValue=/usr/bin/ruby}",
"{type=string, propertyName=fcgi-php-bin, matchesRegEx=[^/.*], provided=[/usr/lib/cgi-bin/php], defaultValue=/usr/lib/cgi-bin/php}",
"{type=string, propertyName=passenger-nodejs, matchesRegEx=[^/.*], provided=[/usr/bin/node], defaultValue=/usr/bin/node}",
"{type=string, propertyName=passenger-python, matchesRegEx=[^/.*], provided=[/usr/bin/python3], defaultValue=/usr/bin/python3}",
"{type=string, propertyName=passenger-ruby, matchesRegEx=[^/.*], provided=[/usr/bin/ruby], defaultValue=/usr/bin/ruby}",
"{type=string[], propertyName=subdomains, elementsOf={type=string, propertyName=subdomains, matchesRegEx=[(?!-)[A-Za-z0-9-]{1,63}(?<!-)], required=true}}"
);
}
@ -155,7 +156,7 @@ class HsDomainHttpSetupHostingAssetValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.htdocsfallback' is expected to be of type Boolean, but is of type String",
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.fcgi-php-bin' is expected to match any of [^/] but 'false' does not match",
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.fcgi-php-bin' is expected to match any of [^/.*] but 'false' does not match",
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.subdomains' is expected to match any of [(?!-)[A-Za-z0-9-]{1,63}(?<!-)] but '' does not match",
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.subdomains' is expected to match any of [(?!-)[A-Za-z0-9-]{1,63}(?<!-)] but '@' does not match",
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.subdomains' is expected to match any of [(?!-)[A-Za-z0-9-]{1,63}(?<!-)] but 'example.com' does not match");

View File

@ -49,7 +49,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
// then
assertThat(result).containsExactly(
"'identifier' expected to match '^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}', but is '"+testCase.domainName+"'"
"'identifier' expected to match '^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}', but is '"+testCase.domainName+"'"
);
}

View File

@ -80,6 +80,27 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
"'EMAIL_ALIAS:xyz00-office.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$, ^:include:/.*$, ^\\|.*$, ^/dev/null$] but 'garbage' does not match any");
}
@Test
void rejectsEmptyTargetArray() {
// given
final var emailAliasHostingAssetEntity = HsHostingAssetEntity.builder()
.type(EMAIL_ALIAS)
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
.identifier("xyz00-office")
.config(Map.ofEntries(
entry("target", new String[0])
))
.build();
final var validator = HostingAssetEntityValidatorRegistry.forType(emailAliasHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(emailAliasHostingAssetEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'EMAIL_ALIAS:xyz00-office.config.target' length is expected to be at min 1 but length of [[]] is 0");
}
@Test
void rejectsInvalidIndentifier() {
// given

View File

@ -11,6 +11,7 @@ import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;
import org.opentest4j.AssertionFailedError;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.mock.mockito.MockBean;
@ -20,9 +21,9 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
@ -30,9 +31,9 @@ import java.io.StringWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@ -40,7 +41,6 @@ import java.util.UUID;
import java.util.stream.Collectors;
import static java.lang.Boolean.parseBoolean;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
@ -77,7 +77,7 @@ public class CsvDataImport extends ContextBasedTest {
@MockBean
HttpServletRequest request;
static final List<String> errors = new ArrayList<>();
static final LinkedHashSet<String> errors = new LinkedHashSet<>();
public List<String[]> readAllLines(Reader reader) throws Exception {
@ -115,7 +115,20 @@ public class CsvDataImport extends ContextBasedTest {
}
protected Reader resourceReader(@NotNull final String resourcePath) {
return new InputStreamReader(requireNonNull(getClass().getClassLoader().getResourceAsStream(resourcePath)));
try {
return new InputStreamReader(requireNonNull(getClass().getClassLoader().getResourceAsStream(resourcePath)));
} catch (Exception exc) {
throw new AssertionFailedError("cannot open '" + resourcePath + "'");
}
}
protected String resourceAsString(@NotNull final String resourcePath) {
try (InputStream inputStream = requireNonNull(getClass().getClassLoader().getResourceAsStream(resourcePath));
final var reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
return reader.lines().collect(Collectors.joining(System.lineSeparator()));
} catch (Exception exc) {
throw new AssertionFailedError("cannot open '" + resourcePath + "'");
}
}
protected List<String[]> withoutHeader(final List<String[]> records) {
@ -127,7 +140,9 @@ public class CsvDataImport extends ContextBasedTest {
try (final var reader = new CSVReader(new StringReader(csvLine))) {
return stream(ofNullable(reader.readNext()).orElse(emptyArray(String.class)))
.map(String::trim)
.map(target -> target.startsWith("'") && target.endsWith("'") ? target.substring(1, target.length()-1) : target)
.map(target -> target.startsWith("'") && target.endsWith("'") ?
target.substring(1, target.length() - 1) :
target)
.toArray(String[]::new);
}
}
@ -147,7 +162,7 @@ public class CsvDataImport extends ContextBasedTest {
//noinspection unchecked
return (T) persistViaSql(id, ha);
}
return persistViaEM(id, entity);
return persistViaEM(id, entity);
} catch (Exception exc) {
errors.add("failed to persist #" + entity.hashCode() + ": " + entity);
errors.add(exc.toString());
@ -171,38 +186,40 @@ public class CsvDataImport extends ContextBasedTest {
}
final var query = em.createNativeQuery("""
insert into hs_hosting_asset(
uuid,
type,
bookingitemuuid,
parentassetuuid,
assignedtoassetuuid,
alarmcontactuuid,
identifier,
caption,
config,
version)
values (
:uuid,
:type,
:bookingitemuuid,
:parentassetuuid,
:assignedtoassetuuid,
:alarmcontactuuid,
:identifier,
:caption,
cast(:config as jsonb),
:version)
""")
insert into hs_hosting_asset(
uuid,
type,
bookingitemuuid,
parentassetuuid,
assignedtoassetuuid,
alarmcontactuuid,
identifier,
caption,
config,
version)
values (
:uuid,
:type,
:bookingitemuuid,
:parentassetuuid,
:assignedtoassetuuid,
:alarmcontactuuid,
:identifier,
:caption,
cast(:config as jsonb),
:version)
""")
.setParameter("uuid", entity.getUuid())
.setParameter("type", entity.getType().name())
.setParameter("bookingitemuuid", ofNullable(entity.getBookingItem()).map(BaseEntity::getUuid).orElse(null))
.setParameter("parentassetuuid", ofNullable(entity.getParentAsset()).map(BaseEntity::getUuid).orElse(null))
.setParameter("assignedtoassetuuid", ofNullable(entity.getAssignedToAsset()).map(BaseEntity::getUuid).orElse(null))
.setParameter(
"assignedtoassetuuid",
ofNullable(entity.getAssignedToAsset()).map(BaseEntity::getUuid).orElse(null))
.setParameter("alarmcontactuuid", ofNullable(entity.getAlarmContact()).map(BaseEntity::getUuid).orElse(null))
.setParameter("identifier", entity.getIdentifier())
.setParameter("caption", entity.getCaption())
.setParameter("config", entity.getConfig().toString())
.setParameter("config", entity.getConfig().toString().replace("\t", "\\t"))
.setParameter("version", entity.getVersion());
final var count = query.executeUpdate();
@ -212,17 +229,18 @@ public class CsvDataImport extends ContextBasedTest {
return entity;
}
protected <E> String toFormattedString(final Map<Integer, E> map) {
protected <E> String toJsonFormattedString(final Map<Integer, E> map) {
if ( map.isEmpty() ) {
return "{}";
}
return "{\n" +
final var json = "{\n" +
map.keySet().stream()
.map(id -> " " + id + "=" + map.get(id).toString())
.map(e -> e.replaceAll("\n ", " ").replace("\n", ""))
.map(e -> e.replaceAll("\n ", " ").replace("\n", "").replace(" : ", ": ").replace("{ ", "{").replace(", ", ", "))
.sorted()
.collect(Collectors.joining(",\n")) +
"\n}\n";
return json;
}
protected void deleteTestDataFromHsOfficeTables() {
@ -292,44 +310,26 @@ public class CsvDataImport extends ContextBasedTest {
try {
assertion.run();
} catch (final AssertionError exc) {
errors.add(exc.toString());
logError(exc.getMessage());
}
}
void logErrors() {
final var errorsToLog = new ArrayList<>(errors);
public static void logError(final String error) {
errors.add(error);
}
protected static void expectError(final String expectedError) {
final var found = errors.remove(expectedError);
if (!found) {
logError("expected but not found: " + expectedError);
}
}
protected final void assertNoErrors() {
final var errorsToLog = new LinkedHashSet<>(errors);
errors.clear();
assertThat(errorsToLog).isEmpty();
}
void expectErrors(final String... expectedErrors) {
assertContainsExactlyInAnyOrderIgnoringWhitespace(errors, expectedErrors);
}
private static class IgnoringWhitespaceComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
return s1.replaceAll("\\s", "").compareTo(s2.replaceAll("\\s", ""));
}
}
public static void assertContainsExactlyInAnyOrderIgnoringWhitespace(final List<String> expected, final List<String> actual) {
final var sortedExpected = expected.stream()
.map(m -> m.replaceAll("\\s+", " "))
.map(m -> m.replaceAll("^ ", ""))
.map(m -> m.replaceAll(" $", ""))
.toList();
final var sortedActual = actual.stream()
.map(m -> m.replaceAll("\\s+", " "))
.map(m -> m.replaceAll("^ ", ""))
.map(m -> m.replaceAll(" $", ""))
.toArray(String[]::new);
assertThat(sortedExpected).containsExactlyInAnyOrder(sortedActual);
}
public static void assertContainsExactlyInAnyOrderIgnoringWhitespace(final List<String> expected, final String... actual) {
assertContainsExactlyInAnyOrderIgnoringWhitespace(expected, asList(actual));
}
}
class Columns {
@ -373,7 +373,7 @@ class Record {
boolean getBoolean(final String columnName) {
final String value = getString(columnName);
return isNotBlank(value) &&
( parseBoolean(value.trim()) || value.trim().startsWith("t"));
(parseBoolean(value.trim()) || value.trim().startsWith("t"));
}
Integer getInteger(final String columnName) {
@ -408,7 +408,9 @@ class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback {
@Override
public void testFailed(final ExtensionContext context, final Throwable cause) {
previousTestsPassed = previousTestsPassed && context.getElement().map(e -> e.isAnnotationPresent(ContinueOnFailure.class)).orElse(false);
previousTestsPassed = previousTestsPassed && context.getElement()
.map(e -> e.isAnnotationPresent(ContinueOnFailure.class))
.orElse(false);
}
@Override

View File

@ -1,5 +1,7 @@
package net.hostsharing.hsadminng.hs.migration;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hash.HashGenerator;
import net.hostsharing.hsadminng.hash.HashGenerator.Algorithm;
@ -10,6 +12,8 @@ import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityV
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntityValidatorRegistry;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.MethodOrderer;
@ -18,12 +22,15 @@ import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.Commit;
import org.springframework.test.annotation.DirtiesContext;
import java.io.Reader;
import java.net.IDN;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -32,6 +39,8 @@ import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.Arrays.stream;
import static java.util.Map.entry;
@ -40,6 +49,11 @@ import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_DNS_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_HTTP_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_MBOX_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SMTP_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ALIAS;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.IPV4_NUMBER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
@ -112,6 +126,12 @@ public class ImportHostingAssets extends ImportOfficeData {
static final Integer DBINSTANCE_ID_OFFSET = 6000000;
static final Integer DBUSER_ID_OFFSET = 7000000;
static final Integer DB_ID_OFFSET = 8000000;
static final Integer DOMAIN_SETUP_OFFSET = 10000000;
static final Integer DOMAIN_DNS_SETUP_OFFSET = 11000000;
static final Integer DOMAIN_HTTP_SETUP_OFFSET = 12000000;
static final Integer DOMAIN_MBOX_SETUP_OFFSET = 13000000;
static final Integer DOMAIN_SMTP_SETUP_OFFSET = 14000000;
static List<String> zonefileErrors = new ArrayList<>();
record Hive(int hive_id, String hive_name, int inet_addr_id, AtomicReference<HsHostingAssetRealEntity> serverRef) {}
@ -120,6 +140,9 @@ public class ImportHostingAssets extends ImportOfficeData {
static Map<Integer, Hive> hives = new WriteOnceMap<>();
static Map<Integer, HsHostingAssetRealEntity> hostingAssets = new WriteOnceMap<>(); // TODO.impl: separate maps for each type?
static Map<String, HsHostingAssetRealEntity> dbUsersByEngineAndName = new WriteOnceMap<>();
static Map<String, HsHostingAssetRealEntity> domainSetupsByName = new WriteOnceMap<>();
final ObjectMapper jsonMapper = new ObjectMapper();
@Test
@Order(11010)
@ -175,7 +198,7 @@ public class ImportHostingAssets extends ImportOfficeData {
void verifyHives() {
assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(first(5, hives))).isEqualToIgnoringWhitespace("""
assertThat(toJsonFormattedString(first(5, hives))).isEqualToIgnoringWhitespace("""
{
2000001=Hive[hive_id=1, hive_name=h00, inet_addr_id=358, serverRef=null],
2000002=Hive[hive_id=2, hive_name=h01, inet_addr_id=359, serverRef=null],
@ -267,15 +290,15 @@ public class ImportHostingAssets extends ImportOfficeData {
HsBookingItemType.MANAGED_WEBSPACE))
.isEqualToIgnoringWhitespace("""
{
3000630=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_WEBSPACE, [2001-06-01,), BI hsh00, { "HDD": 10, "Multi": 25, "SLA-Platform": "EXT24H", "SSD": 16, "Traffic": 50}),
3000968=HsBookingItemEntity(D-1015200:rar default project, MANAGED_SERVER, [2013-04-01,), BI vm1061, { "CPU": 6, "HDD": 250, "RAM": 14, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 375, "Traffic": 250}),
3000978=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2013-04-01,), BI vm1050, { "CPU": 4, "HDD": 250, "RAM": 32, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 150, "Traffic": 250}),
3001061=HsBookingItemEntity(D-1000300:mim default project, MANAGED_SERVER, [2013-08-19,), BI vm1068, { "CPU": 2, "HDD": 250, "RAM": 4, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT2H", "SLA-Web": true, "Traffic": 250}),
3001094=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-10,), BI lug00, { "Multi": 5, "SLA-Platform": "EXT24H", "SSD": 1, "Traffic": 10}),
3001112=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-17,), BI mim00, { "Multi": 5, "SLA-Platform": "EXT24H", "SSD": 3, "Traffic": 20}),
3001447=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2014-11-28,), BI vm1093, { "CPU": 6, "HDD": 500, "RAM": 16, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 300, "Traffic": 250}),
3019959=HsBookingItemEntity(D-1101900:dph default project, MANAGED_WEBSPACE, [2021-06-02,), BI dph00, { "Multi": 1, "SLA-Platform": "EXT24H", "SSD": 25, "Traffic": 20}),
3023611=HsBookingItemEntity(D-1101800:wws default project, CLOUD_SERVER, [2022-08-10,), BI vm2097, { "CPU": 8, "RAM": 12, "SLA-Infrastructure": "EXT4H", "SSD": 25, "Traffic": 250})
3000630=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_WEBSPACE, [2001-06-01,), BI hsh00, {"HDD" : 10, "Multi" : 25, "SLA-Platform" : "EXT24H", "SSD" : 16, "Traffic" : 50}),
3000968=HsBookingItemEntity(D-1015200:rar default project, MANAGED_SERVER, [2013-04-01,), BI vm1061, {"CPU" : 6, "HDD" : 250, "RAM" : 14, "SLA-EMail" : true, "SLA-Maria" : true, "SLA-Office" : true, "SLA-PgSQL" : true, "SLA-Platform" : "EXT4H", "SLA-Web" : true, "SSD" : 375, "Traffic" : 250}),
3000978=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2013-04-01,), BI vm1050, {"CPU" : 4, "HDD" : 250, "RAM" : 32, "SLA-EMail" : true, "SLA-Maria" : true, "SLA-Office" : true, "SLA-PgSQL" : true, "SLA-Platform" : "EXT4H", "SLA-Web" : true, "SSD" : 150, "Traffic" : 250}),
3001061=HsBookingItemEntity(D-1000300:mim default project, MANAGED_SERVER, [2013-08-19,), BI vm1068, {"CPU" : 2, "HDD" : 250, "RAM" : 4, "SLA-EMail" : true, "SLA-Maria" : true, "SLA-Office" : true, "SLA-PgSQL" : true, "SLA-Platform" : "EXT2H", "SLA-Web" : true, "Traffic" : 250}),
3001094=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-10,), BI lug00, {"Multi" : 5, "SLA-Platform" : "EXT24H", "SSD" : 1, "Traffic" : 10}),
3001112=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-17,), BI mim00, {"Multi" : 5, "SLA-Platform" : "EXT24H", "SSD" : 3, "Traffic" : 20}),
3001447=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2014-11-28,), BI vm1093, {"CPU" : 6, "HDD" : 500, "RAM" : 16, "SLA-EMail" : true, "SLA-Maria" : true, "SLA-Office" : true, "SLA-PgSQL" : true, "SLA-Platform" : "EXT4H", "SLA-Web" : true, "SSD" : 300, "Traffic" : 250}),
3019959=HsBookingItemEntity(D-1101900:dph default project, MANAGED_WEBSPACE, [2021-06-02,), BI dph00, {"Multi" : 1, "SLA-Platform" : "EXT24H", "SSD" : 25, "Traffic" : 20}),
3023611=HsBookingItemEntity(D-1101800:wws default project, CLOUD_SERVER, [2022-08-10,), BI vm2097, {"CPU" : 8, "RAM" : 12, "SLA-Infrastructure" : "EXT4H", "SSD" : 25, "Traffic" : 250})
}
""");
}
@ -298,20 +321,20 @@ public class ImportHostingAssets extends ImportOfficeData {
assertThat(firstOfEachType(15, UNIX_USER)).isEqualToIgnoringWhitespace("""
{
4005803=HsHostingAssetRealEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102090}),
4005805=HsHostingAssetRealEntity(UNIX_USER, lug00-wla.1, Paul Klemm, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102091}),
4005809=HsHostingAssetRealEntity(UNIX_USER, lug00-wla.2, Walter Müller, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 8, "SSD soft quota": 4, "locked": false, "shell": "/bin/bash", "userid": 102093}),
4005811=HsHostingAssetRealEntity(UNIX_USER, lug00-ola.a, LUG OLA - POP a, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102094}),
4005813=HsHostingAssetRealEntity(UNIX_USER, lug00-ola.b, LUG OLA - POP b, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102095}),
4005835=HsHostingAssetRealEntity(UNIX_USER, lug00-test, Test, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 1024, "SSD soft quota": 1024, "locked": false, "shell": "/usr/bin/passwd", "userid": 102106}),
4005964=HsHostingAssetRealEntity(UNIX_USER, mim00, Michael Mellis, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102147}),
4005966=HsHostingAssetRealEntity(UNIX_USER, mim00-1981, Jahrgangstreffen 1981, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 256, "SSD soft quota": 128, "locked": false, "shell": "/bin/bash", "userid": 102148}),
4005990=HsHostingAssetRealEntity(UNIX_USER, mim00-mail, Mailbox, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102160}),
4100705=HsHostingAssetRealEntity(UNIX_USER, hsh00-mim, Michael Mellis, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/false", "userid": 10003}),
4100824=HsHostingAssetRealEntity(UNIX_USER, hsh00, Hostsharing Paket, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 10000}),
4167846=HsHostingAssetRealEntity(UNIX_USER, hsh00-dph, hsh00-uph, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/false", "userid": 110568}),
4169546=HsHostingAssetRealEntity(UNIX_USER, dph00, Reinhard Wiese, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110593}),
4169596=HsHostingAssetRealEntity(UNIX_USER, dph00-uph, Domain admin, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110594})
4005803=HsHostingAssetRealEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, {"SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102090}),
4005805=HsHostingAssetRealEntity(UNIX_USER, lug00-wla.1, Paul Klemm, MANAGED_WEBSPACE:lug00, {"SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102091}),
4005809=HsHostingAssetRealEntity(UNIX_USER, lug00-wla.2, Walter Müller, MANAGED_WEBSPACE:lug00, {"SSD hard quota": 8, "SSD soft quota": 4, "locked": false, "shell": "/bin/bash", "userid": 102093}),
4005811=HsHostingAssetRealEntity(UNIX_USER, lug00-ola.a, LUG OLA - POP a, MANAGED_WEBSPACE:lug00, {"SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102094}),
4005813=HsHostingAssetRealEntity(UNIX_USER, lug00-ola.b, LUG OLA - POP b, MANAGED_WEBSPACE:lug00, {"SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102095}),
4005835=HsHostingAssetRealEntity(UNIX_USER, lug00-test, Test, MANAGED_WEBSPACE:lug00, {"SSD hard quota": 1024, "SSD soft quota": 1024, "locked": false, "shell": "/usr/bin/passwd", "userid": 102106}),
4005964=HsHostingAssetRealEntity(UNIX_USER, mim00, Michael Mellis, MANAGED_WEBSPACE:mim00, {"SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102147}),
4005966=HsHostingAssetRealEntity(UNIX_USER, mim00-1981, Jahrgangstreffen 1981, MANAGED_WEBSPACE:mim00, {"SSD hard quota": 256, "SSD soft quota": 128, "locked": false, "shell": "/bin/bash", "userid": 102148}),
4005990=HsHostingAssetRealEntity(UNIX_USER, mim00-mail, Mailbox, MANAGED_WEBSPACE:mim00, {"SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102160}),
4100705=HsHostingAssetRealEntity(UNIX_USER, hsh00-mim, Michael Mellis, MANAGED_WEBSPACE:hsh00, {"HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/false", "userid": 10003}),
4100824=HsHostingAssetRealEntity(UNIX_USER, hsh00, Hostsharing Paket, MANAGED_WEBSPACE:hsh00, {"HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 10000}),
4167846=HsHostingAssetRealEntity(UNIX_USER, hsh00-dph, hsh00-uph, MANAGED_WEBSPACE:hsh00, {"HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/false", "